diff --git a/voxels-client/src/main/java/client/app/GameClient.java b/voxels-client/src/main/java/client/app/GameClient.java index e7e0c988..786f6965 100644 --- a/voxels-client/src/main/java/client/app/GameClient.java +++ b/voxels-client/src/main/java/client/app/GameClient.java @@ -11,6 +11,7 @@ import client.ui.View; import client.world.ChunkManager; import client.world.ClientWorld; +import client.world.ClientWorldOrigin; import common.network.NetworkPackets; import engine.application.BasicApplication; import engine.scene.camera.Camera; @@ -77,6 +78,10 @@ public ClientWorld getWorld() { return world; } + public ClientWorldOrigin getWorldOrigin() { + return world.getOrigin(); + } + public ClientPlayer getPlayer() { return player; } diff --git a/voxels-client/src/main/java/client/world/ChunkManager.java b/voxels-client/src/main/java/client/world/ChunkManager.java index 3e89bec7..9dff1afd 100644 --- a/voxels-client/src/main/java/client/world/ChunkManager.java +++ b/voxels-client/src/main/java/client/world/ChunkManager.java @@ -26,6 +26,8 @@ */ public class ChunkManager extends AbstractComponent implements RenderableComponent { + private static final int DEFAULT_ORIGIN_SHIFT_THRESHOLD_CHUNKS = 4; + private int renderDistance; private int bufferDistance; @@ -52,6 +54,8 @@ public class ChunkManager extends AbstractComponent implements RenderableCompone private ChunkRenderer chunkRenderer; private ClientWorld world; + private final ClientWorldOrigin origin; + private int originShiftThresholdChunks = DEFAULT_ORIGIN_SHIFT_THRESHOLD_CHUNKS; // Füge eine Map für den Lösch-Timer hinzu private final Map deletionQueue = new ConcurrentHashMap<>(); @@ -59,6 +63,7 @@ public class ChunkManager extends AbstractComponent implements RenderableCompone public ChunkManager(GameClient client) { this.world = client.getWorld(); + this.origin = world.getOrigin(); this.chunkRenderer = new BasicChunkRenderer(client); setRenderDistance(GameSettings.renderDistance); } @@ -72,6 +77,11 @@ public void onUpdate(float tpf) { playerChunkX = WorldMath.worldToChunkX(playerPosition); playerChunkZ = WorldMath.worldToChunkZ(playerPosition); + if (origin.isOutsideShiftThreshold( + playerChunkX, playerChunkZ, originShiftThresholdChunks)) { + origin.setOriginChunk(playerChunkX, playerChunkZ); + } + if (playerChunkX != lastPlayerChunkX || playerChunkZ != lastPlayerChunkZ) { lastPlayerChunkX = playerChunkX; @@ -399,6 +409,21 @@ public void updatePlayerPosition(float x, float y, float z) { playerPosition.set(x, y, z); } + public ClientWorldOrigin getOrigin() { + return origin; + } + + public int getOriginShiftThresholdChunks() { + return originShiftThresholdChunks; + } + + public void setOriginShiftThresholdChunks(int originShiftThresholdChunks) { + if (originShiftThresholdChunks < 0) { + throw new IllegalArgumentException("Origin shift threshold must be >= 0."); + } + this.originShiftThresholdChunks = originShiftThresholdChunks; + } + @Override public void onAttach() {} diff --git a/voxels-client/src/main/java/client/world/ClientWorld.java b/voxels-client/src/main/java/client/world/ClientWorld.java index d4b31656..69f5b7da 100644 --- a/voxels-client/src/main/java/client/world/ClientWorld.java +++ b/voxels-client/src/main/java/client/world/ClientWorld.java @@ -10,6 +10,7 @@ public class ClientWorld extends World { public static final int TICKS_PER_SECOND = 20; private float fractionalTickCounter = 0; + private final ClientWorldOrigin origin = new ClientWorldOrigin(); private ChunkManager chunkManager; // Warteschlange für Datenpakete vom Server @@ -55,6 +56,10 @@ public void setChunkManager(ChunkManager chunkManager) { this.chunkManager = chunkManager; } + public ClientWorldOrigin getOrigin() { + return origin; + } + public void update(float tpf) { // Wir addieren den Fortschritt auf einen float-Counter fractionalTickCounter += tpf * TICKS_PER_SECOND; diff --git a/voxels-client/src/main/java/client/world/ClientWorldOrigin.java b/voxels-client/src/main/java/client/world/ClientWorldOrigin.java new file mode 100644 index 00000000..ec2dd24a --- /dev/null +++ b/voxels-client/src/main/java/client/world/ClientWorldOrigin.java @@ -0,0 +1,66 @@ +package client.world; + +import common.world.ChunkData; + +/** + * Maintains the client-side floating-origin anchor. + * + *

The server and world simulation continue to use absolute world/chunk coordinates. The client + * can rebase rendering-related systems around the current origin chunk to keep local coordinates + * numerically stable. + */ +public class ClientWorldOrigin { + + private int originChunkX; + private int originChunkZ; + + public int getOriginChunkX() { + return originChunkX; + } + + public int getOriginChunkZ() { + return originChunkZ; + } + + public void setOriginChunk(int chunkX, int chunkZ) { + this.originChunkX = chunkX; + this.originChunkZ = chunkZ; + } + + public float getOriginWorldX() { + return originChunkX * (float) ChunkData.WIDTH; + } + + public float getOriginWorldZ() { + return originChunkZ * (float) ChunkData.DEPTH; + } + + public float toLocalX(float absoluteX) { + return absoluteX - getOriginWorldX(); + } + + public float toLocalY(float absoluteY) { + return absoluteY; + } + + public float toLocalZ(float absoluteZ) { + return absoluteZ - getOriginWorldZ(); + } + + public float toAbsoluteX(float localX) { + return getOriginWorldX() + localX; + } + + public float toAbsoluteY(float localY) { + return localY; + } + + public float toAbsoluteZ(float localZ) { + return getOriginWorldZ() + localZ; + } + + public boolean isOutsideShiftThreshold(int chunkX, int chunkZ, int thresholdInChunks) { + return Math.abs(chunkX - originChunkX) > thresholdInChunks + || Math.abs(chunkZ - originChunkZ) > thresholdInChunks; + } +}