Actually Tilted

- Unsightly amendments to the track graph for a smoother ride
This commit is contained in:
simibubi 2023-02-17 15:41:18 +01:00
parent 7dc6fc7576
commit 0dd8c3a4f1
13 changed files with 542 additions and 56 deletions

View file

@ -226,6 +226,7 @@ import com.simibubi.create.content.logistics.trains.track.StandardBogeyBlock;
import com.simibubi.create.content.logistics.trains.track.TrackBlock;
import com.simibubi.create.content.logistics.trains.track.TrackBlockItem;
import com.simibubi.create.content.logistics.trains.track.TrackBlockStateGenerator;
import com.simibubi.create.content.logistics.trains.track.TrackModel;
import com.simibubi.create.content.schematics.block.SchematicTableBlock;
import com.simibubi.create.content.schematics.block.SchematicannonBlock;
import com.simibubi.create.foundation.block.BlockStressDefaults;
@ -1541,6 +1542,7 @@ public class AllBlocks {
.noOcclusion())
.addLayer(() -> RenderType::cutoutMipped)
.transform(pickaxeOnly())
.onRegister(CreateRegistrate.blockModel(() -> TrackModel::new))
.blockstate(new TrackBlockStateGenerator()::generate)
.tag(AllBlockTags.RELOCATION_NOT_SUPPORTED.tag)
.lang("Train Track")

View file

@ -6,9 +6,11 @@ import com.jozufozu.flywheel.util.transform.TransformStack;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.PoseStack.Pose;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.logistics.trains.track.TrackBlockEntityTilt;
import com.simibubi.create.content.logistics.trains.track.TrackRenderer;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.NBTHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos;
@ -37,6 +39,7 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
public Couple<Vec3> starts;
public Couple<Vec3> axes;
public Couple<Vec3> normals;
public Couple<Integer> smoothing;
public boolean primary;
public boolean hasGirder;
@ -66,8 +69,15 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
}
public BezierConnection secondary() {
return new BezierConnection(tePositions.swap(), starts.swap(), axes.swap(), normals.swap(), !primary,
hasGirder);
BezierConnection bezierConnection =
new BezierConnection(tePositions.swap(), starts.swap(), axes.swap(), normals.swap(), !primary, hasGirder);
if (smoothing != null)
bezierConnection.smoothing = smoothing.swap();
return bezierConnection;
}
public BezierConnection clone() {
return secondary().secondary();
}
public BezierConnection(CompoundTag compound, BlockPos localTo) {
@ -78,6 +88,10 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
Couple.deserializeEach(compound.getList("Axes", Tag.TAG_COMPOUND), VecHelper::readNBTCompound),
Couple.deserializeEach(compound.getList("Normals", Tag.TAG_COMPOUND), VecHelper::readNBTCompound),
compound.getBoolean("Primary"), compound.getBoolean("Girder"));
if (compound.contains("Smoothing"))
smoothing =
Couple.deserializeEach(compound.getList("Smoothing", Tag.TAG_COMPOUND), NBTHelper::intFromCompound);
}
public CompoundTag write(BlockPos localTo) {
@ -91,6 +105,10 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
compound.put("Starts", starts.serializeEach(VecHelper::writeNBTCompound));
compound.put("Axes", axes.serializeEach(VecHelper::writeNBTCompound));
compound.put("Normals", normals.serializeEach(VecHelper::writeNBTCompound));
if (smoothing != null)
compound.put("Smoothing", smoothing.serializeEach(NBTHelper::intToCompound));
return compound;
}
@ -98,6 +116,8 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
this(Couple.create(buffer::readBlockPos), Couple.create(() -> VecHelper.read(buffer)),
Couple.create(() -> VecHelper.read(buffer)), Couple.create(() -> VecHelper.read(buffer)),
buffer.readBoolean(), buffer.readBoolean());
if (buffer.readBoolean())
smoothing = Couple.create(buffer::readVarInt);
}
public void write(FriendlyByteBuf buffer) {
@ -107,6 +127,9 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
normals.forEach(v -> VecHelper.write(v, buffer));
buffer.writeBoolean(primary);
buffer.writeBoolean(hasGirder);
buffer.writeBoolean(smoothing != null);
if (smoothing != null)
smoothing.forEach(buffer::writeVarInt);
}
public BlockPos getKey() {
@ -117,6 +140,16 @@ public class BezierConnection implements Iterable<BezierConnection.Segment> {
return primary;
}
public int yOffsetAt(Vec3 end) {
if (smoothing == null)
return 0;
if (TrackBlockEntityTilt.compareHandles(starts.getFirst(), end))
return smoothing.getFirst();
if (TrackBlockEntityTilt.compareHandles(starts.getSecond(), end))
return smoothing.getSecond();
return 0;
}
// Runtime information
public double getLength() {

View file

@ -38,6 +38,10 @@ public interface ITrackBlock {
public Vec3 getCurveStart(BlockGetter world, BlockPos pos, BlockState state, Vec3 axis);
public default int getYOffsetAt(BlockGetter world, BlockPos pos, BlockState state, Vec3 end) {
return 0;
}
public BlockState getBogeyAnchor(BlockGetter world, BlockPos pos, BlockState state); // should be on bogey side
public boolean trackEquals(BlockState state1, BlockState state2);
@ -71,10 +75,17 @@ public interface ITrackBlock {
.add(0, getElevationAtCenter(world, pos, state), 0);
List<DiscoveredLocation> list = new ArrayList<>();
TrackShape shape = state.getValue(TrackBlock.SHAPE);
getTrackAxes(world, pos, state).forEach(axis -> {
addToListIfConnected(connectedTo, list, (d, b) -> axis.scale(b ? d : -d)
.add(center), b -> shape.getNormal(), b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD,
axis, null);
List<Vec3> trackAxes = getTrackAxes(world, pos, state);
trackAxes.forEach(axis -> {
BiFunction<Double, Boolean, Vec3> offsetFactory = (d, b) -> axis.scale(b ? d : -d)
.add(center);
Function<Boolean, ResourceKey<Level>> dimensionFactory =
b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD;
Function<Vec3, Integer> yOffsetFactory = v -> getYOffsetAt(world, pos, state, v);
addToListIfConnected(connectedTo, list, offsetFactory, b -> shape.getNormal(), dimensionFactory,
yOffsetFactory, axis, null);
});
return list;
@ -82,22 +93,28 @@ public interface ITrackBlock {
public static void addToListIfConnected(@Nullable TrackNodeLocation fromEnd, Collection<DiscoveredLocation> list,
BiFunction<Double, Boolean, Vec3> offsetFactory, Function<Boolean, Vec3> normalFactory,
Function<Boolean, ResourceKey<Level>> dimensionFactory, Vec3 axis, BezierConnection viaTurn) {
Function<Boolean, ResourceKey<Level>> dimensionFactory, Function<Vec3, Integer> yOffsetFactory, Vec3 axis,
BezierConnection viaTurn) {
Vec3 firstOffset = offsetFactory.apply(0.5d, true);
DiscoveredLocation firstLocation =
new DiscoveredLocation(dimensionFactory.apply(true), offsetFactory.apply(0.5d, true)).viaTurn(viaTurn)
new DiscoveredLocation(dimensionFactory.apply(true), firstOffset).viaTurn(viaTurn)
.withNormal(normalFactory.apply(true))
.withDirection(axis);
.withDirection(axis)
.withYOffset(yOffsetFactory.apply(firstOffset));
Vec3 secondOffset = offsetFactory.apply(0.5d, false);
DiscoveredLocation secondLocation =
new DiscoveredLocation(dimensionFactory.apply(false), offsetFactory.apply(0.5d, false)).viaTurn(viaTurn)
new DiscoveredLocation(dimensionFactory.apply(false), secondOffset).viaTurn(viaTurn)
.withNormal(normalFactory.apply(false))
.withDirection(axis);
.withDirection(axis)
.withYOffset(yOffsetFactory.apply(secondOffset));
if (!firstLocation.dimension.equals(secondLocation.dimension)) {
firstLocation.forceNode();
secondLocation.forceNode();
}
boolean skipFirst = false;
boolean skipSecond = false;

View file

@ -67,6 +67,7 @@ public class TrackGraphHelper {
TrackNode frontNode = null;
TrackNode backNode = null;
double position = 0;
boolean singleTrackPiece = true;
for (DiscoveredLocation current : ends) {
Vec3 offset = current.getLocation()
@ -74,14 +75,19 @@ public class TrackGraphHelper {
.normalize()
.scale(length);
boolean forward = offset.distanceToSqr(axis.scale(-1)) < 1 / 4096f;
boolean backwards = offset.distanceToSqr(axis) < 1 / 4096f;
Vec3 compareOffset = offset.multiply(1, 0, 1)
.normalize();
boolean forward = compareOffset.distanceToSqr(axis.multiply(-1, 0, -1)
.normalize()) < 1 / 4096f;
boolean backwards = compareOffset.distanceToSqr(axis.multiply(1, 0, 1)
.normalize()) < 1 / 4096f;
if (!forward && !backwards)
continue;
DiscoveredLocation previous = null;
double distance = 0;
for (int i = 0; i < 100 && distance < 32; i++) {
DiscoveredLocation loc = current;
if (graph == null)
@ -89,6 +95,7 @@ public class TrackGraphHelper {
.getGraph(level, loc);
if (graph == null || graph.locateNode(loc) == null) {
singleTrackPiece = false;
Collection<DiscoveredLocation> list = ITrackBlock.walkConnectedTracks(level, loc, true);
for (DiscoveredLocation discoveredLocation : list) {
if (discoveredLocation == previous)
@ -121,6 +128,13 @@ public class TrackGraphHelper {
if (frontNode == null || backNode == null)
return null;
if (singleTrackPiece)
position = frontNode.getLocation()
.getLocation()
.distanceTo(backNode.getLocation()
.getLocation())
/ 2.0;
GraphLocation graphLocation = new GraphLocation();
graphLocation.edge = Couple.create(backNode.getLocation(), frontNode.getLocation());
graphLocation.position = position;
@ -143,6 +157,9 @@ public class TrackGraphHelper {
return null;
TrackNodeLocation targetLoc = new TrackNodeLocation(bc.starts.getSecond()).in(level);
if (bc.smoothing != null)
targetLoc.yOffsetPixels = bc.smoothing.getSecond();
for (DiscoveredLocation location : track.getConnected(level, pos, state, true, null)) {
TrackGraph graph = Create.RAILWAYS.sided(level)
.getGraph(level, location);

View file

@ -19,13 +19,14 @@ import net.minecraft.world.phys.Vec3;
public class TrackNodeLocation extends Vec3i {
public ResourceKey<Level> dimension;
public int yOffsetPixels;
public TrackNodeLocation(Vec3 vec) {
this(vec.x, vec.y, vec.z);
}
public TrackNodeLocation(double p_121865_, double p_121866_, double p_121867_) {
super(Math.round(p_121865_ * 2), Math.floor(p_121866_ * 2), Math.round(p_121867_ * 2));
super(Math.round(p_121865_ * 2), Math.floor(p_121866_) * 2, Math.round(p_121867_ * 2));
}
public TrackNodeLocation in(Level level) {
@ -46,7 +47,7 @@ public class TrackNodeLocation extends Vec3i {
}
public Vec3 getLocation() {
return new Vec3(getX() / 2.0, getY() / 2.0, getZ() / 2.0);
return new Vec3(getX() / 2.0, getY() / 2.0 + yOffsetPixels / 16.0, getZ() / 2.0);
}
public ResourceKey<Level> getDimension() {
@ -60,18 +61,20 @@ public class TrackNodeLocation extends Vec3i {
}
public boolean equalsIgnoreDim(Object pOther) {
return super.equals(pOther);
return super.equals(pOther) && pOther instanceof TrackNodeLocation tnl && tnl.yOffsetPixels == yOffsetPixels;
}
@Override
public int hashCode() {
return (this.getY() + (this.getZ() * 31 + dimension.hashCode()) * 31) * 31 + this.getX();
return (getY() + ((getZ() + yOffsetPixels * 31) * 31 + dimension.hashCode()) * 31) * 31 + getX();
}
public CompoundTag write(DimensionPalette dimensions) {
CompoundTag c = NbtUtils.writeBlockPos(new BlockPos(this));
if (dimensions != null)
c.putInt("D", dimensions.encode(dimension));
if (yOffsetPixels != 0)
c.putInt("YO", yOffsetPixels);
return c;
}
@ -79,13 +82,15 @@ public class TrackNodeLocation extends Vec3i {
TrackNodeLocation location = fromPackedPos(NbtUtils.readBlockPos(tag));
if (dimensions != null)
location.dimension = dimensions.decode(tag.getInt("D"));
location.yOffsetPixels = tag.getInt("YO");
return location;
}
public void send(FriendlyByteBuf buffer, DimensionPalette dimensions) {
buffer.writeVarInt(this.getX());
buffer.writeShort(this.getY());
buffer.writeVarInt(this.getZ());
buffer.writeVarInt(getX());
buffer.writeShort(getY());
buffer.writeVarInt(getZ());
buffer.writeVarInt(yOffsetPixels);
buffer.writeVarInt(dimensions.encode(dimension));
}
@ -95,13 +100,14 @@ public class TrackNodeLocation extends Vec3i {
buffer.readShort(),
buffer.readVarInt()
));
location.yOffsetPixels = buffer.readVarInt();
location.dimension = dimensions.decode(buffer.readVarInt());
return location;
}
public Collection<BlockPos> allAdjacent() {
Set<BlockPos> set = new HashSet<>();
Vec3 vec3 = getLocation();
Vec3 vec3 = getLocation().subtract(0, yOffsetPixels / 16.0, 0);
double step = 1 / 8f;
for (int x : Iterate.positiveAndNegative)
for (int y : Iterate.positiveAndNegative)
@ -147,6 +153,11 @@ public class TrackNodeLocation extends Vec3i {
this.normal = normal;
return this;
}
public DiscoveredLocation withYOffset(int yOffsetPixels) {
this.yOffsetPixels = yOffsetPixels;
return this;
}
public DiscoveredLocation withDirection(Vec3 direction) {
this.direction = direction == null ? null : direction.normalize();

View file

@ -124,7 +124,7 @@ public class TrackBlock extends Block
protected void createBlockStateDefinition(Builder<Block, BlockState> p_49915_) {
super.createBlockStateDefinition(p_49915_.add(SHAPE, HAS_BE, WATERLOGGED));
}
@Override
public BlockPathTypes getAiPathNodeType(BlockState state, BlockGetter world, BlockPos pos, Mob entity) {
return BlockPathTypes.RAIL;
@ -208,7 +208,7 @@ public class TrackBlock extends Block
return;
withBlockEntityDo(pLevel, pPos, be -> {
be.cancelDrops = true;
be.removeInboundConnections();
be.removeInboundConnections(true);
});
}
@ -233,6 +233,7 @@ public class TrackBlock extends Block
@Override
public void tick(BlockState state, ServerLevel level, BlockPos pos, Random p_60465_) {
TrackPropagator.onRailAdded(level, pos, state);
withBlockEntityDo(level, pos, tbe -> tbe.tilt.undoSmoothing());
if (!state.getValue(SHAPE)
.isPortal())
connectToNether(level, pos, state);
@ -297,12 +298,14 @@ public class TrackBlock extends Block
Player player = level.getNearestPlayer(pos.getX(), pos.getY(), pos.getZ(), 10, Predicates.alwaysTrue());
if (player == null)
return;
player.displayClientMessage(Components.literal("<!> ").append(Lang.translateDirect("portal_track.failed"))
player.displayClientMessage(Components.literal("<!> ")
.append(Lang.translateDirect("portal_track.failed"))
.withStyle(ChatFormatting.GOLD), false);
MutableComponent component =
failPos != null ? Lang.translateDirect("portal_track." + fail, failPos.getX(), failPos.getY(), failPos.getZ())
: Lang.translateDirect("portal_track." + fail);
player.displayClientMessage(Components.literal(" - ").withStyle(ChatFormatting.GRAY)
MutableComponent component = failPos != null
? Lang.translateDirect("portal_track." + fail, failPos.getX(), failPos.getY(), failPos.getZ())
: Lang.translateDirect("portal_track." + fail);
player.displayClientMessage(Components.literal(" - ")
.withStyle(ChatFormatting.GRAY)
.append(component.withStyle(st -> st.withColor(0xFFD3B4))), false);
}
@ -361,6 +364,12 @@ public class TrackBlock extends Block
return state;
}
@Override
public int getYOffsetAt(BlockGetter world, BlockPos pos, BlockState state, Vec3 end) {
return getBlockEntityOptional(world, pos).map(tbe -> tbe.tilt.getYOffsetForAxisEnd(end))
.orElse(0);
}
@Override
public Collection<DiscoveredLocation> getConnected(BlockGetter worldIn, BlockPos pos, BlockState state,
boolean linear, TrackNodeLocation connectedTo) {
@ -378,8 +387,8 @@ public class TrackBlock extends Block
ITrackBlock.addToListIfConnected(connectedTo, list,
(d, b) -> axis.scale(b ? 0 : fromCenter ? -d : d)
.add(center),
b -> shape.getNormal(), b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD, axis,
null);
b -> shape.getNormal(), b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD, v -> 0,
axis, null);
} else
list = ITrackBlock.super.getConnected(world, pos, state, linear, connectedTo);
@ -395,7 +404,7 @@ public class TrackBlock extends Block
Map<BlockPos, BezierConnection> connections = trackTE.getConnections();
connections.forEach((connectedPos, bc) -> ITrackBlock.addToListIfConnected(connectedTo, list,
(d, b) -> d == 1 ? Vec3.atLowerCornerOf(bc.tePositions.get(b)) : bc.starts.get(b), bc.normals::get,
b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD, null, bc));
b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD, bc::yOffsetAt, null, bc));
if (trackTE.boundLocation == null || !(world instanceof ServerLevel level))
return list;
@ -421,7 +430,7 @@ public class TrackBlock extends Block
getTrackAxes(world, pos, state).forEach(axis -> {
ITrackBlock.addToListIfConnected(connectedTo, list, (d, b) -> (b ? axis : boundAxis).scale(d)
.add(b ? center : boundCenter), b -> (b ? shape : boundShape).getNormal(),
b -> b ? level.dimension() : otherLevel.dimension(), axis, null);
b -> b ? level.dimension() : otherLevel.dimension(), v -> 0, axis, null);
});
return list;
@ -444,8 +453,10 @@ public class TrackBlock extends Block
boolean removeBE = false;
if (pState.getValue(HAS_BE) && (!pState.is(pNewState.getBlock()) || !pNewState.getValue(HAS_BE))) {
BlockEntity blockEntity = pLevel.getBlockEntity(pPos);
if (blockEntity instanceof TrackBlockEntity && !pLevel.isClientSide)
((TrackBlockEntity) blockEntity).removeInboundConnections();
if (blockEntity instanceof TrackBlockEntity tbe && !pLevel.isClientSide) {
tbe.cancelDrops |= pNewState.getBlock() == this;
tbe.removeInboundConnections(true);
}
removeBE = true;
}
@ -468,7 +479,7 @@ public class TrackBlock extends Block
if (!entry.getValue()
.isInside(pos))
continue;
if (world.getBlockEntity(entry.getKey()) instanceof StationBlockEntity station)
if (world.getBlockEntity(entry.getKey())instanceof StationBlockEntity station)
if (station.trackClicked(player, hand, this, state, pos))
return InteractionResult.SUCCESS;
}
@ -484,7 +495,7 @@ public class TrackBlock extends Block
BlockPos girderPos = pPos.below()
.offset(vec3.z * side, 0, vec3.x * side);
BlockState girderState = pLevel.getBlockState(girderPos);
if (girderState.getBlock() instanceof GirderBlock girderBlock
if (girderState.getBlock()instanceof GirderBlock girderBlock
&& !blockTicks.hasScheduledTick(girderPos, girderBlock))
pLevel.scheduleTick(girderPos, girderBlock, 1);
}
@ -689,7 +700,7 @@ public class TrackBlock extends Block
Vec3 normal = null;
Vec3 offset = null;
if (bezierPoint != null && world.getBlockEntity(pos) instanceof TrackBlockEntity trackTE) {
if (bezierPoint != null && world.getBlockEntity(pos)instanceof TrackBlockEntity trackTE) {
BezierConnection bc = trackTE.connections.get(bezierPoint.curveTarget());
if (bc != null) {
double length = Mth.floor(bc.getLength() * 2);
@ -734,6 +745,16 @@ public class TrackBlock extends Block
msr.rotateCentered(Direction.UP, Mth.PI);
}
if (bezierPoint == null && world.getBlockEntity(pos)instanceof TrackBlockEntity trackTE && trackTE.isTilted()) {
double yOffset = 0;
for (BezierConnection bc : trackTE.connections.values())
yOffset += bc.starts.getFirst().y - pos.getY();
msr.centre()
.rotateX(-direction.getStep() * trackTE.tilt.smoothingAngle.get())
.unCentre()
.translate(0, yOffset / 2, 0);
}
return switch (type) {
case DUAL_SIGNAL -> AllBlockPartials.TRACK_SIGNAL_DUAL_OVERLAY;
case OBSERVER -> AllBlockPartials.TRACK_OBSERVER_OVERLAY;
@ -779,8 +800,7 @@ public class TrackBlock extends Block
public static class RenderProperties extends ReducedDestroyEffects implements MultiPosDestructionHandler {
@Override
@Nullable
public Set<BlockPos> getExtraPositions(ClientLevel level, BlockPos pos, BlockState blockState,
int progress) {
public Set<BlockPos> getExtraPositions(ClientLevel level, BlockPos pos, BlockState blockState, int progress) {
BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity instanceof TrackBlockEntity track) {
return new HashSet<>(track.connections.keySet());

View file

@ -5,6 +5,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
@ -44,6 +45,8 @@ import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.data.ModelDataMap;
import net.minecraftforge.fml.DistExecutor;
public class TrackBlockEntity extends SmartBlockEntity implements ITransformableTE, IMergeableBE {
@ -52,11 +55,13 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
boolean cancelDrops;
public Pair<ResourceKey<Level>, BlockPos> boundLocation;
public TrackBlockEntityTilt tilt;
public TrackBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
connections = new HashMap<>();
setLazyTickRate(100);
tilt = new TrackBlockEntityTilt(this);
}
public Map<BlockPos, BezierConnection> getConnections() {
@ -70,6 +75,12 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
registerToCurveInteraction();
}
@Override
public void tick() {
super.tick();
tilt.undoSmoothing();
}
@Override
public void lazyTick() {
for (BezierConnection connection : connections.values())
@ -103,8 +114,10 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
continue;
}
if (!trackTE.connections.containsKey(worldPosition))
if (!trackTE.connections.containsKey(worldPosition)) {
trackTE.addConnection(bc.secondary());
trackTE.tilt.tryApplySmoothing();
}
}
for (BlockPos blockPos : invalid)
@ -121,6 +134,9 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
}
public void removeConnection(BlockPos target) {
if (isTilted())
tilt.captureSmoothingHandles();
BezierConnection removed = connections.remove(target);
notifyUpdate();
@ -138,19 +154,20 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
AllPackets.channel.send(packetTarget(), new RemoveBlockEntityPacket(worldPosition));
}
public void removeInboundConnections() {
public void removeInboundConnections(boolean dropAndDiscard) {
for (BezierConnection bezierConnection : connections.values()) {
BlockEntity blockEntity = level.getBlockEntity(bezierConnection.getKey());
if (!(blockEntity instanceof TrackBlockEntity))
if (!(level.getBlockEntity(bezierConnection.getKey())instanceof TrackBlockEntity tbe))
return;
TrackBlockEntity other = (TrackBlockEntity) blockEntity;
other.removeConnection(bezierConnection.tePositions.getFirst());
tbe.removeConnection(bezierConnection.tePositions.getFirst());
if (!dropAndDiscard)
continue;
if (!cancelDrops)
bezierConnection.spawnItems(level);
bezierConnection.spawnDestroyParticles(level);
}
AllPackets.channel.send(packetTarget(), new RemoveBlockEntityPacket(worldPosition));
if (dropAndDiscard)
AllPackets.channel.send(packetTarget(), new RemoveBlockEntityPacket(worldPosition));
}
public void bind(ResourceKey<Level> boundDimension, BlockPos boundLocation) {
@ -158,16 +175,22 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
setChanged();
}
public boolean isTilted() {
return tilt.smoothingAngle.isPresent();
}
@Override
public void writeSafe(CompoundTag tag) {
super.writeSafe(tag);
writeTurns(tag);
writeTurns(tag, true);
}
@Override
protected void write(CompoundTag tag, boolean clientPacket) {
super.write(tag, clientPacket);
writeTurns(tag);
writeTurns(tag, false);
if (isTilted())
tag.putDouble("Smoothing", tilt.smoothingAngle.get());
if (boundLocation == null)
return;
tag.put("BoundLocation", NbtUtils.writeBlockPos(boundLocation.getSecond()));
@ -176,10 +199,11 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
.toString());
}
private void writeTurns(CompoundTag tag) {
private void writeTurns(CompoundTag tag, boolean restored) {
ListTag listTag = new ListTag();
for (BezierConnection bezierConnection : connections.values())
listTag.add(bezierConnection.write(worldPosition));
listTag.add((restored ? tilt.restoreToOriginalCurve(bezierConnection.clone()) : bezierConnection)
.write(worldPosition));
tag.put("Connections", listTag);
}
@ -194,6 +218,13 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
connections.put(connection.getKey(), connection);
}
boolean smoothingPreviously = tilt.smoothingAngle.isPresent();
tilt.smoothingAngle = Optional.ofNullable(tag.contains("Smoothing") ? tag.getDouble("Smoothing") : null);
if (smoothingPreviously != tilt.smoothingAngle.isPresent() && clientPacket) {
requestModelDataUpdate();
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 16);
}
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> InstancedRenderDispatcher.enqueueUpdate(this));
if (hasInteractableConnections())
@ -233,6 +264,15 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
@Override
public void transform(StructureTransform transform) {
Map<BlockPos, BezierConnection> restoredConnections = new HashMap<>();
for (Entry<BlockPos, BezierConnection> entry : connections.entrySet())
restoredConnections.put(entry.getKey(),
tilt.restoreToOriginalCurve(tilt.restoreToOriginalCurve(entry.getValue()
.secondary())
.secondary()));
connections = restoredConnections;
tilt.smoothingAngle = Optional.empty();
if (transform.rotationAxis != Axis.Y)
return;
@ -300,6 +340,15 @@ public class TrackBlockEntity extends SmartBlockEntity implements ITransformable
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> this::removeFromCurveInteractionUnsafe);
}
@Override
public IModelData getModelData() {
if (!isTilted())
return super.getModelData();
return new ModelDataMap.Builder()
.withInitial(TrackBlockEntityTilt.ASCENDING_PROPERTY, tilt.smoothingAngle.get())
.build();
}
@OnlyIn(Dist.CLIENT)
private void registerToCurveInteractionUnsafe() {
TrackBlockOutline.TRACKS_WITH_TURNS.get(level)

View file

@ -0,0 +1,236 @@
package com.simibubi.create.content.logistics.trains.track;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import com.simibubi.create.content.logistics.trains.BezierConnection;
import com.simibubi.create.content.logistics.trains.ITrackBlock;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.content.logistics.trains.TrackPropagator;
import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.client.model.data.ModelProperty;
public class TrackBlockEntityTilt {
public static final ModelProperty<Double> ASCENDING_PROPERTY = new ModelProperty<>();
public Optional<Double> smoothingAngle;
private Couple<Pair<Vec3, Integer>> previousSmoothingHandles;
private TrackBlockEntity blockEntity;
public TrackBlockEntityTilt(TrackBlockEntity blockEntity) {
this.blockEntity = blockEntity;
smoothingAngle = Optional.empty();
}
public void tryApplySmoothing() {
if (smoothingAngle.isPresent())
return;
Couple<BezierConnection> discoveredSlopes = Couple.create(null, null);
Vec3 axis = null;
BlockState blockState = blockEntity.getBlockState();
BlockPos worldPosition = blockEntity.getBlockPos();
Level level = blockEntity.getLevel();
if (!(blockState.getBlock()instanceof ITrackBlock itb))
return;
List<Vec3> axes = itb.getTrackAxes(level, worldPosition, blockState);
if (axes.size() != 1)
return;
if (axes.get(0).y != 0)
return;
if (blockEntity.boundLocation != null)
return;
for (BezierConnection bezierConnection : blockEntity.connections.values()) {
if (bezierConnection.starts.getFirst().y == bezierConnection.starts.getSecond().y)
continue;
if (bezierConnection.axes.getSecond().y != 0)
continue;
Vec3 normedAxis = bezierConnection.axes.getFirst()
.normalize();
if (axis != null) {
if (discoveredSlopes.getSecond() != null)
return;
if (normedAxis.dot(axis) > -1 + 1 / 64.0)
return;
discoveredSlopes.setSecond(bezierConnection);
continue;
}
axis = normedAxis;
discoveredSlopes.setFirst(bezierConnection);
}
if (discoveredSlopes.either(Objects::isNull))
return;
if (discoveredSlopes.getFirst().starts.getSecond().y > discoveredSlopes.getSecond().starts.getSecond().y)
discoveredSlopes = discoveredSlopes.swap();
Couple<Vec3> lowStarts = discoveredSlopes.getFirst().starts;
Couple<Vec3> highStarts = discoveredSlopes.getSecond().starts;
Vec3 lowestPoint = lowStarts.getSecond();
Vec3 highestPoint = highStarts.getSecond();
if (lowestPoint.y > lowStarts.getFirst().y)
return;
if (highestPoint.y < highStarts.getFirst().y)
return;
blockEntity.removeInboundConnections(false);
blockEntity.connections.clear();
TrackPropagator.onRailRemoved(level, worldPosition, blockState);
double hDistance = discoveredSlopes.getFirst()
.getLength()
+ discoveredSlopes.getSecond()
.getLength();
Vec3 baseAxis = discoveredSlopes.getFirst().axes.getFirst();
double baseAxisLength = baseAxis.x != 0 && baseAxis.z != 0 ? Math.sqrt(2) : 1;
double vDistance = highestPoint.y - lowestPoint.y;
double m = vDistance / (hDistance);
Vec3 diff = highStarts.getFirst()
.subtract(lowStarts.getFirst());
boolean flipRotation = diff.dot(new Vec3(1, 0, 2).normalize()) <= 0;
smoothingAngle = Optional.of(Math.toDegrees(Mth.atan2(m, 1)) * (flipRotation ? -1 : 1));
int smoothingParam = Mth.clamp((int) (m * baseAxisLength * 16), 0, 15);
Couple<Integer> smoothingResult = Couple.create(0, smoothingParam);
Vec3 raisedOffset = diff.normalize()
.add(0, Mth.clamp(m, 0, 1 - 1 / 512.0), 0)
.normalize()
.scale(baseAxisLength);
highStarts.setFirst(lowStarts.getFirst()
.add(raisedOffset));
boolean first = true;
for (BezierConnection bezierConnection : discoveredSlopes) {
int smoothingToApply = smoothingResult.get(first);
if (bezierConnection.smoothing == null)
bezierConnection.smoothing = Couple.create(0, 0);
bezierConnection.smoothing.setFirst(smoothingToApply);
bezierConnection.axes.setFirst(bezierConnection.axes.getFirst()
.add(0, (first ? 1 : -1) * -m, 0)
.normalize());
first = false;
BlockPos otherPosition = bezierConnection.getKey();
BlockState otherState = level.getBlockState(otherPosition);
if (!(otherState.getBlock() instanceof TrackBlock))
continue;
level.setBlockAndUpdate(otherPosition, otherState.setValue(TrackBlock.HAS_BE, true));
BlockEntity otherBE = level.getBlockEntity(otherPosition);
if (otherBE instanceof TrackBlockEntity tbe) {
blockEntity.addConnection(bezierConnection);
tbe.addConnection(bezierConnection.secondary());
}
}
}
public void captureSmoothingHandles() {
boolean first = true;
previousSmoothingHandles = Couple.create(null, null);
for (BezierConnection bezierConnection : blockEntity.connections.values()) {
previousSmoothingHandles.set(first, Pair.of(bezierConnection.starts.getFirst(),
bezierConnection.smoothing == null ? 0 : bezierConnection.smoothing.getFirst()));
first = false;
}
}
public void undoSmoothing() {
if (smoothingAngle.isEmpty())
return;
if (previousSmoothingHandles == null)
return;
if (blockEntity.connections.size() == 2)
return;
BlockState blockState = blockEntity.getBlockState();
BlockPos worldPosition = blockEntity.getBlockPos();
Level level = blockEntity.getLevel();
List<BezierConnection> validConnections = new ArrayList<>();
for (BezierConnection bezierConnection : blockEntity.connections.values()) {
BlockPos otherPosition = bezierConnection.getKey();
BlockEntity otherBE = level.getBlockEntity(otherPosition);
if (otherBE instanceof TrackBlockEntity tbe && tbe.connections.containsKey(worldPosition))
validConnections.add(bezierConnection);
}
blockEntity.removeInboundConnections(false);
TrackPropagator.onRailRemoved(level, worldPosition, blockState);
blockEntity.connections.clear();
smoothingAngle = Optional.empty();
for (BezierConnection bezierConnection : validConnections) {
blockEntity.addConnection(restoreToOriginalCurve(bezierConnection));
BlockPos otherPosition = bezierConnection.getKey();
BlockState otherState = level.getBlockState(otherPosition);
if (!(otherState.getBlock() instanceof TrackBlock))
continue;
level.setBlockAndUpdate(otherPosition, otherState.setValue(TrackBlock.HAS_BE, true));
BlockEntity otherBE = level.getBlockEntity(otherPosition);
if (otherBE instanceof TrackBlockEntity tbe)
tbe.addConnection(bezierConnection.secondary());
}
blockEntity.notifyUpdate();
previousSmoothingHandles = null;
TrackPropagator.onRailAdded(level, worldPosition, blockState);
}
public BezierConnection restoreToOriginalCurve(BezierConnection bezierConnection) {
if (bezierConnection.smoothing != null) {
bezierConnection.smoothing.setFirst(0);
if (bezierConnection.smoothing.getFirst() == 0 && bezierConnection.smoothing.getSecond() == 0)
bezierConnection.smoothing = null;
}
Vec3 raisedStart = bezierConnection.starts.getFirst();
bezierConnection.starts.setFirst(new TrackNodeLocation(raisedStart).getLocation());
bezierConnection.axes.setFirst(bezierConnection.axes.getFirst()
.multiply(1, 0, 1)
.normalize());
return bezierConnection;
}
public int getYOffsetForAxisEnd(Vec3 end) {
if (smoothingAngle.isEmpty())
return 0;
for (BezierConnection bezierConnection : blockEntity.connections.values())
if (compareHandles(bezierConnection.starts.getFirst(), end))
return bezierConnection.yOffsetAt(end);
if (previousSmoothingHandles == null)
return 0;
for (Pair<Vec3, Integer> handle : previousSmoothingHandles)
if (handle != null && compareHandles(handle.getFirst(), end))
return handle.getSecond();
return 0;
}
public static boolean compareHandles(Vec3 handle1, Vec3 handle2) {
return new TrackNodeLocation(handle1).getLocation()
.multiply(1, 0, 1)
.distanceToSqr(new TrackNodeLocation(handle2).getLocation()
.multiply(1, 0, 1)) < 1 / 512f;
}
}

View file

@ -66,6 +66,13 @@ public class TrackBlockItem extends BlockItem {
.withStyle(ChatFormatting.RED), true);
return InteractionResult.SUCCESS;
}
if (level.getBlockEntity(pos) instanceof TrackBlockEntity tbe && tbe.isTilted()) {
if (!level.isClientSide)
player.displayClientMessage(Lang.translateDirect("track.turn_start")
.withStyle(ChatFormatting.RED), true);
return InteractionResult.SUCCESS;
}
if (select(level, pos, lookAngle, stack)) {
level.playSound(null, pos, SoundEvents.ITEM_FRAME_ADD_ITEM, SoundSource.BLOCKS, 0.75f, 1);

View file

@ -191,9 +191,11 @@ public class TrackBlockOutline {
boolean holdingTrack = AllBlocks.TRACK.isIn(Minecraft.getInstance().player.getMainHandItem());
TrackShape shape = blockstate.getValue(TrackBlock.SHAPE);
boolean isJunction = shape.isJunction();
boolean canConnectFrom = !shape.isJunction()
&& !(mc.level.getBlockEntity(pos)instanceof TrackBlockEntity tbe && tbe.isTilted());
walkShapes(shape, TransformStack.cast(ms), s -> {
renderShape(s, ms, vb, holdingTrack ? !isJunction : null);
renderShape(s, ms, vb, holdingTrack ? canConnectFrom : null);
event.setCanceled(true);
});
@ -288,8 +290,8 @@ public class TrackBlockOutline {
renderer.accept(LONG_ORTHO);
}
public static record BezierPointSelection(TrackBlockEntity blockEntity, BezierTrackPointLocation loc, Vec3 vec, Vec3 angles,
Vec3 direction) {
public static record BezierPointSelection(TrackBlockEntity blockEntity, BezierTrackPointLocation loc, Vec3 vec,
Vec3 angles, Vec3 direction) {
}
}

View file

@ -0,0 +1,78 @@
package com.simibubi.create.content.logistics.trains.track;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.UnaryOperator;
import com.simibubi.create.foundation.block.render.QuadHelper;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.Direction;
import net.minecraft.core.Direction.Axis;
import net.minecraft.util.Mth;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.client.model.BakedModelWrapper;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.data.ModelDataMap;
public class TrackModel extends BakedModelWrapper<BakedModel> {
public TrackModel(BakedModel originalModel) {
super(originalModel);
}
@Override
public List<BakedQuad> getQuads(BlockState state, Direction side, Random rand, IModelData extraData) {
List<BakedQuad> templateQuads = super.getQuads(state, side, rand, extraData);
if (templateQuads.isEmpty())
return templateQuads;
if (!(extraData instanceof ModelDataMap mdm) || !mdm.hasProperty(TrackBlockEntityTilt.ASCENDING_PROPERTY))
return templateQuads;
double angleIn = mdm.getData(TrackBlockEntityTilt.ASCENDING_PROPERTY);
double angle = Math.abs(angleIn);
boolean flip = angleIn < 0;
TrackShape trackShape = state.getValue(TrackBlock.SHAPE);
double hAngle = switch (trackShape) {
case XO -> 0;
case PD -> 45;
case ZO -> 90;
case ND -> 135;
default -> 0;
};
Vec3 verticalOffset = new Vec3(0, -0.25, 0);
Vec3 diagonalRotationPoint =
(trackShape == TrackShape.ND || trackShape == TrackShape.PD) ? new Vec3((Mth.SQRT_OF_TWO - 1) / 2, 0, 0)
: Vec3.ZERO;
UnaryOperator<Vec3> transform = v -> {
v = v.add(verticalOffset);
v = VecHelper.rotateCentered(v, hAngle, Axis.Y);
v = v.add(diagonalRotationPoint);
v = VecHelper.rotate(v, angle, Axis.Z);
v = v.subtract(diagonalRotationPoint);
v = VecHelper.rotateCentered(v, -hAngle + (flip ? 180 : 0), Axis.Y);
v = v.subtract(verticalOffset);
return v;
};
int size = templateQuads.size();
List<BakedQuad> quads = new ArrayList<>();
for (int i = 0; i < size; i++) {
BakedQuad quad = QuadHelper.clone(templateQuads.get(i));
int[] vertexData = quad.getVertices();
for (int j = 0; j < 4; j++)
QuadHelper.setXYZ(vertexData, j, transform.apply(QuadHelper.getXYZ(vertexData, j)));
quads.add(quad);
}
return quads;
}
}

View file

@ -150,6 +150,8 @@ public class TrackPlacement {
.tooJumbly();
if (!state1.hasProperty(TrackBlock.HAS_BE))
return info.withMessage("original_missing");
if (level.getBlockEntity(pos2) instanceof TrackBlockEntity tbe && tbe.isTilted())
return info.withMessage("turn_start");
if (axis1.dot(end2.subtract(end1)) < 0) {
axis1 = axis1.scale(-1);
@ -556,6 +558,8 @@ public class TrackPlacement {
tte1.addConnection(info.curve);
tte2.addConnection(info.curve.secondary());
tte1.tilt.tryApplySmoothing();
tte2.tilt.tryApplySmoothing();
return info;
}

View file

@ -107,5 +107,15 @@ public class NBTHelper {
return inbt;
return new CompoundTag();
}
public static CompoundTag intToCompound(int i) {
CompoundTag compoundTag = new CompoundTag();
compoundTag.putInt("V", i);
return compoundTag;
}
public static int intFromCompound(CompoundTag compoundTag) {
return compoundTag.getInt("V");
}
}