From ec64c46c52ce7d6bc43655f4c6ea2106b6b0b2dd Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:36:51 +0100 Subject: [PATCH] Fix item pipe smoothing (#105) - Change smoothing updates to be based on client ticks instead of BER invocations. - Make sure that traveling items only get ticked once per tick, both on the client and on the server side. This fixes jitters when an item moves from one pipe to the other, caused by the item being moved twice in the same tick. --- .../client/ber/PipeBlockEntityRenderer.java | 2 +- .../moderndynamics/network/item/ItemHost.java | 21 +++++++++++++ .../network/item/TravelingItem.java | 1 + .../item/sync/ClientTravelingItem.java | 1 + .../sync/ClientTravelingItemSmoothing.java | 31 ++++++++++++++----- 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/main/java/dev/technici4n/moderndynamics/client/ber/PipeBlockEntityRenderer.java b/src/main/java/dev/technici4n/moderndynamics/client/ber/PipeBlockEntityRenderer.java index 9cd7d55..dd55a43 100644 --- a/src/main/java/dev/technici4n/moderndynamics/client/ber/PipeBlockEntityRenderer.java +++ b/src/main/java/dev/technici4n/moderndynamics/client/ber/PipeBlockEntityRenderer.java @@ -53,7 +53,7 @@ public void render(PipeBlockEntity pipe, float tickDelta, PoseStack matrices, Mu Vec3 from, to; double ratio; - var distance = Mth.frac(item.traveledDistance()) + ClientTravelingItemSmoothing.getAndUpdateDistanceDelta(item) + var distance = Mth.frac(item.traveledDistance()) + ClientTravelingItemSmoothing.getDistanceDelta(item, tickDelta) + item.speed() * tickDelta; if (distance <= 0.5) { from = findFaceMiddle(item.in().getOpposite()); diff --git a/src/main/java/dev/technici4n/moderndynamics/network/item/ItemHost.java b/src/main/java/dev/technici4n/moderndynamics/network/item/ItemHost.java index 4cc4143..1f4adbc 100644 --- a/src/main/java/dev/technici4n/moderndynamics/network/item/ItemHost.java +++ b/src/main/java/dev/technici4n/moderndynamics/network/item/ItemHost.java @@ -299,11 +299,21 @@ public void tickMovingItems() { return; } + long curTick = getLevel().getGameTime(); + // List of items that moved out of this pipe. List movedOut = new ArrayList<>(); for (var iterator = travelingItems.iterator(); iterator.hasNext();) { var travelingItem = iterator.next(); + + // Make sure we only update items once per tick. + // This makes sure that we don't update items twice if they get moved to another pipe. + if (travelingItem.lastTick == curTick) { + continue; + } + travelingItem.lastTick = curTick; + // Calculate in which path segment the item is now, and in which segment it is after moving it int currentIndex = (int) travelingItem.traveledDistance; travelingItem.traveledDistance += travelingItem.getSpeed(); @@ -538,9 +548,20 @@ public void readClientNbt(CompoundTag tag) { @Override public void clientTick() { + long curTick = getLevel().getGameTime(); + for (var it = clientTravelingItems.iterator(); it.hasNext();) { ClientTravelingItem travelingItem = it.next(); + + // Make sure we only update items once per tick. + // This makes sure that we don't update items twice if they get moved to another pipe. + if (travelingItem.lastTick == curTick) { + continue; + } + travelingItem.lastTick = curTick; + travelingItem.traveledDistance += travelingItem.speed(); + ClientTravelingItemSmoothing.onTickItem(travelingItem); if (Mth.frac(travelingItem.traveledDistance) < travelingItem.speed()) { // Goes out of this pipe! diff --git a/src/main/java/dev/technici4n/moderndynamics/network/item/TravelingItem.java b/src/main/java/dev/technici4n/moderndynamics/network/item/TravelingItem.java index 1f9140c..9cccc10 100644 --- a/src/main/java/dev/technici4n/moderndynamics/network/item/TravelingItem.java +++ b/src/main/java/dev/technici4n/moderndynamics/network/item/TravelingItem.java @@ -34,6 +34,7 @@ public class TravelingItem { public final FailedInsertStrategy strategy; public final double speedMultiplier; public double traveledDistance; + public long lastTick; public TravelingItem(ItemVariant variant, int amount, ItemPath path, FailedInsertStrategy strategy, double speedMultiplier, double traveledDistance) { diff --git a/src/main/java/dev/technici4n/moderndynamics/network/item/sync/ClientTravelingItem.java b/src/main/java/dev/technici4n/moderndynamics/network/item/sync/ClientTravelingItem.java index 6a01443..1391011 100644 --- a/src/main/java/dev/technici4n/moderndynamics/network/item/sync/ClientTravelingItem.java +++ b/src/main/java/dev/technici4n/moderndynamics/network/item/sync/ClientTravelingItem.java @@ -30,6 +30,7 @@ public final class ClientTravelingItem { public Direction in; public Direction out; final double speed; + public long lastTick; public ClientTravelingItem(int id, ItemVariant variant, int amount, double totalPathDistance, double traveledDistance, Direction in, Direction out, double speed) { diff --git a/src/main/java/dev/technici4n/moderndynamics/network/item/sync/ClientTravelingItemSmoothing.java b/src/main/java/dev/technici4n/moderndynamics/network/item/sync/ClientTravelingItemSmoothing.java index e9679de..e173df2 100644 --- a/src/main/java/dev/technici4n/moderndynamics/network/item/sync/ClientTravelingItemSmoothing.java +++ b/src/main/java/dev/technici4n/moderndynamics/network/item/sync/ClientTravelingItemSmoothing.java @@ -27,11 +27,14 @@ * Also keeps track of a client tick counter. */ public class ClientTravelingItemSmoothing { - public static void onUnpausedTick() { - // Cleanup items that are not used anymore for (var it = INFOS.values().iterator(); it.hasNext();) { SmoothingInfo info = it.next(); + + // Apply 1 tick worth of smoothing + info.wrongOffset *= smooth(1); + + // Cleanup items that are not used anymore info.deadTimer--; if (info.deadTimer <= 0) { it.remove(); @@ -55,16 +58,30 @@ public static void onReceiveItem(ClientTravelingItem item) { info.resetTimer(); } - public static double getAndUpdateDistanceDelta(ClientTravelingItem item) { + public static void onTickItem(ClientTravelingItem item) { var info = INFOS.get(item.id); if (info == null) { - return 0; + return; } info.resetTimer(); info.lastTraveledDistance = item.traveledDistance; - double offset = info.wrongOffset; - info.wrongOffset *= 0.95; // might want to adjust based on FPS or similar - return offset; + } + + public static double getDistanceDelta(ClientTravelingItem item, float partialTick) { + var info = INFOS.get(item.id); + if (info == null) { + return 0; + } + return info.wrongOffset * smooth(partialTick); + } + + /** + * Smoothing constant. It takes ~1 second to smooth out 95% of the offset. + */ + private static final double SMOOTHING_EXP = 0.15; + + private static double smooth(double ticks) { + return Math.exp(-SMOOTHING_EXP * ticks); } public static long getClientTick() {