From 0dd8c3a4f145b676750dcb38e48d24d70a7caa65 Mon Sep 17 00:00:00 2001 From: simibubi <31564874+simibubi@users.noreply.github.com> Date: Fri, 17 Feb 2023 15:41:18 +0100 Subject: [PATCH] Actually Tilted - Unsightly amendments to the track graph for a smoother ride --- .../java/com/simibubi/create/AllBlocks.java | 2 + .../logistics/trains/BezierConnection.java | 37 ++- .../content/logistics/trains/ITrackBlock.java | 37 ++- .../logistics/trains/TrackGraphHelper.java | 21 +- .../logistics/trains/TrackNodeLocation.java | 27 +- .../logistics/trains/track/TrackBlock.java | 56 +++-- .../trains/track/TrackBlockEntity.java | 73 +++++- .../trains/track/TrackBlockEntityTilt.java | 236 ++++++++++++++++++ .../trains/track/TrackBlockItem.java | 7 + .../trains/track/TrackBlockOutline.java | 10 +- .../logistics/trains/track/TrackModel.java | 78 ++++++ .../trains/track/TrackPlacement.java | 4 + .../create/foundation/utility/NBTHelper.java | 10 + 13 files changed, 542 insertions(+), 56 deletions(-) create mode 100644 src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntityTilt.java create mode 100644 src/main/java/com/simibubi/create/content/logistics/trains/track/TrackModel.java diff --git a/src/main/java/com/simibubi/create/AllBlocks.java b/src/main/java/com/simibubi/create/AllBlocks.java index 3fccb0916..d7cb680bd 100644 --- a/src/main/java/com/simibubi/create/AllBlocks.java +++ b/src/main/java/com/simibubi/create/AllBlocks.java @@ -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") diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/BezierConnection.java b/src/main/java/com/simibubi/create/content/logistics/trains/BezierConnection.java index 5c543536d..a4f3274fc 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/BezierConnection.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/BezierConnection.java @@ -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 { public Couple starts; public Couple axes; public Couple normals; + public Couple smoothing; public boolean primary; public boolean hasGirder; @@ -66,8 +69,15 @@ public class BezierConnection implements Iterable { } 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 { 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 { 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 { 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 { 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 { 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() { diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/ITrackBlock.java b/src/main/java/com/simibubi/create/content/logistics/trains/ITrackBlock.java index 33946c2a5..710451666 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/ITrackBlock.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/ITrackBlock.java @@ -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 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 trackAxes = getTrackAxes(world, pos, state); + + trackAxes.forEach(axis -> { + BiFunction offsetFactory = (d, b) -> axis.scale(b ? d : -d) + .add(center); + Function> dimensionFactory = + b -> world instanceof Level l ? l.dimension() : Level.OVERWORLD; + Function 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 list, BiFunction offsetFactory, Function normalFactory, - Function> dimensionFactory, Vec3 axis, BezierConnection viaTurn) { + Function> dimensionFactory, Function 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; diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphHelper.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphHelper.java index f4d453563..33ca6a7c8 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphHelper.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackGraphHelper.java @@ -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 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); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/TrackNodeLocation.java b/src/main/java/com/simibubi/create/content/logistics/trains/TrackNodeLocation.java index 509eedd23..621e27d95 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/TrackNodeLocation.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/TrackNodeLocation.java @@ -19,13 +19,14 @@ import net.minecraft.world.phys.Vec3; public class TrackNodeLocation extends Vec3i { public ResourceKey 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 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 allAdjacent() { Set 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(); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlock.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlock.java index 19d9bc20b..e508e6b3e 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlock.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlock.java @@ -124,7 +124,7 @@ public class TrackBlock extends Block protected void createBlockStateDefinition(Builder 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 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 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 getExtraPositions(ClientLevel level, BlockPos pos, BlockState blockState, - int progress) { + public Set 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()); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntity.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntity.java index 67631abe5..4e77ed30a 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntity.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntity.java @@ -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, 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 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 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 restoredConnections = new HashMap<>(); + for (Entry 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) diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntityTilt.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntityTilt.java new file mode 100644 index 000000000..31a8eef1b --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockEntityTilt.java @@ -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 ASCENDING_PROPERTY = new ModelProperty<>(); + + public Optional smoothingAngle; + private Couple> previousSmoothingHandles; + + private TrackBlockEntity blockEntity; + + public TrackBlockEntityTilt(TrackBlockEntity blockEntity) { + this.blockEntity = blockEntity; + smoothingAngle = Optional.empty(); + } + + public void tryApplySmoothing() { + if (smoothingAngle.isPresent()) + return; + + Couple 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 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 lowStarts = discoveredSlopes.getFirst().starts; + Couple 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 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 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 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; + } + +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockItem.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockItem.java index a8b5929dc..1a1980c32 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockItem.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockItem.java @@ -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); diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockOutline.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockOutline.java index d26c708de..5992ec099 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockOutline.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackBlockOutline.java @@ -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) { } } diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackModel.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackModel.java new file mode 100644 index 000000000..ab6606118 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackModel.java @@ -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 { + + public TrackModel(BakedModel originalModel) { + super(originalModel); + } + + @Override + public List getQuads(BlockState state, Direction side, Random rand, IModelData extraData) { + List 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 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 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; + } + +} diff --git a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackPlacement.java b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackPlacement.java index bd7505c63..d6c34b8f8 100644 --- a/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackPlacement.java +++ b/src/main/java/com/simibubi/create/content/logistics/trains/track/TrackPlacement.java @@ -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; } diff --git a/src/main/java/com/simibubi/create/foundation/utility/NBTHelper.java b/src/main/java/com/simibubi/create/foundation/utility/NBTHelper.java index 267a343eb..603e313e9 100644 --- a/src/main/java/com/simibubi/create/foundation/utility/NBTHelper.java +++ b/src/main/java/com/simibubi/create/foundation/utility/NBTHelper.java @@ -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"); + } }