diff --git a/core/src/main/java/nl/andrewl/aos_core/MathUtils.java b/core/src/main/java/nl/andrewl/aos_core/MathUtils.java index 3d86851..19b195f 100644 --- a/core/src/main/java/nl/andrewl/aos_core/MathUtils.java +++ b/core/src/main/java/nl/andrewl/aos_core/MathUtils.java @@ -17,4 +17,12 @@ public class MathUtils { if (value < min) return min; return Math.min(value, max); } + + public static float min(float... values) { + float m = Float.MAX_VALUE; + for (float v : values) { + if (v < m) m = v; + } + return m; + } } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/World.java b/core/src/main/java/nl/andrewl/aos_core/model/World.java index 6cec009..5ebb4c8 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/World.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/World.java @@ -1,18 +1,24 @@ package nl.andrewl.aos_core.model; import nl.andrewl.aos_core.Directions; +import nl.andrewl.aos_core.MathUtils; import org.joml.Math; import org.joml.Vector3f; import org.joml.Vector3i; import org.joml.Vector3ic; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; /** * A world is just a collection of chunks that together form the environment * that players can interact in. */ public class World { + private static final float DELTA = 0.00001f; + protected final Map chunkMap = new HashMap<>(); protected ColorPalette palette; @@ -99,42 +105,35 @@ public class World { public Hit getLookingAtPos(Vector3f eyePos, Vector3f eyeDir, float limit) { if (eyeDir.lengthSquared() == 0 || limit <= 0) return null; Vector3f pos = new Vector3f(eyePos); - Vector3f movement = new Vector3f(); // Pre-allocate this vector. while (pos.distance(eyePos) < limit) { - // Find the coordinates of the next blocks on the x, y, and z axes. - float stepX = getNextStep(pos.x, eyeDir.x); - float stepY = getNextStep(pos.y, eyeDir.y); - float stepZ = getNextStep(pos.z, eyeDir.z); - // Get the distance from our current position to the next block on the x, y, and z axes. - float distX = Math.abs(pos.x - stepX); - float distY = Math.abs(pos.y - stepY); - float distZ = Math.abs(pos.z - stepZ); - // Get the factor required to multiply each component by to get to its next step. - float factorX = Math.abs(distX / eyeDir.x); - float factorY = Math.abs(distY / eyeDir.y); - float factorZ = Math.abs(distZ / eyeDir.z); - float minFactor = Float.MAX_VALUE; - if (factorX > 0 && factorX < minFactor) minFactor = factorX; - if (factorY > 0 && factorY < minFactor) minFactor = factorY; - if (factorZ > 0 && factorZ < minFactor) minFactor = factorZ; - // We should add dir * lowest factor to step to the first next block. - movement.set(eyeDir).mul(minFactor); - pos.add(movement); + stepToNextBlock(pos, eyeDir); if (getBlockAt(pos) > 0) { - Vector3f prevPos = new Vector3f(pos).sub(movement); Vector3i hitPos = new Vector3i( (int) Math.floor(pos.x), (int) Math.floor(pos.y), (int) Math.floor(pos.z) ); - Vector3ic hitNorm = null; + Vector3ic hitNorm; - if (prevPos.y > hitPos.y + 1) hitNorm = Directions.UP; - else if (prevPos.y < hitPos.y) hitNorm = Directions.DOWN; - else if (prevPos.x > hitPos.x + 1) hitNorm = Directions.POSITIVE_X; - else if (prevPos.x < hitPos.x) hitNorm = Directions.NEGATIVE_X; - else if (prevPos.z > hitPos.z + 1) hitNorm = Directions.POSITIVE_Z; - else if (prevPos.z < hitPos.z) hitNorm = Directions.NEGATIVE_Z; + // Determine the face that was hit based on which face was closest to the hit point. + float minYDist = Math.abs(pos.y - hitPos.y); + float maxYDist = Math.abs(pos.y - (hitPos.y + 1)); + float minXDist = Math.abs(pos.x - hitPos.x); + float maxXDist = Math.abs(pos.x - (hitPos.x + 1)); + float minZDist = Math.abs(pos.z - hitPos.z); + float maxZDist = Math.abs(pos.z - (hitPos.z + 1)); + float minDist = MathUtils.min(minYDist, maxYDist, minXDist, maxXDist, minZDist, maxZDist); + + if (minDist == maxYDist) hitNorm = Directions.UP; + else if (minDist == minYDist) hitNorm = Directions.DOWN; + else if (minDist == maxXDist) hitNorm = Directions.POSITIVE_X; + else if (minDist == minXDist) hitNorm = Directions.NEGATIVE_X; + else if (minDist == maxZDist) hitNorm = Directions.POSITIVE_Z; + else if (minDist == minZDist) hitNorm = Directions.NEGATIVE_Z; + else { + hitNorm = Directions.UP; + System.err.println("Invalid hit!"); + } return new Hit(hitPos, hitNorm); } @@ -143,27 +142,54 @@ public class World { } /** - * Helper function to find the next whole number, given a current number and - * an indication of which direction the number is increasing. - * @param n The current number. - * @param sign An indication of which way the number is increasing. - * @return The next whole number up from the current number. + * Increments the given position until it hits a new block space, moving in + * the specified direction. Note that we move slightly into a block if + * needed, to ensure accuracy, so the position may not be a whole number. + * @param pos The position. + * @param dir The direction to move in. */ - private static float getNextStep(float n, float sign) { - if (sign > 0) { - if (Math.ceil(n) == n) { - return n + 1; + private static void stepToNextBlock(Vector3f pos, Vector3f dir) { + if (dir.lengthSquared() == 0) return; + // Find the amount we'd have to multiply dir by to get pos to move to the next block on that axis. + float factorX = Float.MAX_VALUE; + float factorY = Float.MAX_VALUE; + float factorZ = Float.MAX_VALUE; + + if (dir.x != 0) { + float nextValue; + if (dir.x > 0) { + nextValue = (Math.ceil(pos.x) == pos.x) ? pos.x + 1 : Math.ceil(pos.x); } else { - return Math.ceil(n); - } - } else if (sign < 0) { - if (Math.floor(n) == n) { - return n - 1; - } else { - return Math.floor(n); + nextValue = Math.floor(pos.x) - DELTA; } + float diff = nextValue - pos.x; + factorX = Math.abs(diff / dir.x); } - return n; + + if (dir.y != 0) { + float nextValue; + if (dir.y > 0) { + nextValue = (Math.ceil(pos.y) == pos.y) ? pos.y + 1 : Math.ceil(pos.y); + } else { + nextValue = Math.floor(pos.y) - DELTA; + } + float diff = nextValue - pos.y; + factorY = Math.abs(diff / dir.y); + } + + if (dir.z != 0) { + float nextValue; + if (dir.z > 0) { + nextValue = (Math.ceil(pos.z) == pos.z) ? pos.z + 1 : Math.ceil(pos.z); + } else { + nextValue = Math.floor(pos.z) - DELTA; + } + float diff = nextValue - pos.z; + factorZ = Math.abs(diff / dir.z); + } + + float minFactor = Math.min(factorX, Math.min(factorY, factorZ)); + dir.mulAdd(minFactor, pos, pos); } /** diff --git a/core/src/test/java/nl/andrewl/aos_core/model/WorldTest.java b/core/src/test/java/nl/andrewl/aos_core/model/WorldTest.java index 76edc2c..b1a250a 100644 --- a/core/src/test/java/nl/andrewl/aos_core/model/WorldTest.java +++ b/core/src/test/java/nl/andrewl/aos_core/model/WorldTest.java @@ -1,5 +1,6 @@ package nl.andrewl.aos_core.model; +import nl.andrewl.aos_core.Directions; import org.joml.Vector3f; import org.joml.Vector3i; import org.junit.jupiter.api.Test; @@ -35,79 +36,22 @@ public class WorldTest { @Test public void testGetLookingAtPos() { - World world = Worlds.testingWorld(); - // Spawn a block high in the air. - Vector3i blockPos = new Vector3i(20, 20, 20); - world.setBlockAt(blockPos.x, blockPos.y, blockPos.z, (byte) 5); - assertEquals( // Looking straight down onto block. - blockPos, - world.getLookingAtPos( - new Vector3f(20.5f, 25, 20.5f), - new Vector3f(0, -1, 0), - 15 - ) - ); - assertEquals( // Looking straight up. - blockPos, - world.getLookingAtPos( - new Vector3f(20.5f, 15, 20.5f), - new Vector3f(0, 1, 0), - 15 - ) - ); - assertEquals( // Looking towards -Z - blockPos, - world.getLookingAtPos( - new Vector3f(20.5f, 20.5f, 26f), - new Vector3f(0, 0, -1), - 15 - ) - ); - assertEquals( // Looking towards +Z - blockPos, - world.getLookingAtPos( - new Vector3f(20.5f, 20.5f, 15f), - new Vector3f(0, 0, 1), - 15 - ) - ); - assertEquals( // Looking towards -X - blockPos, - world.getLookingAtPos( - new Vector3f(26f, 20.5f, 20.5f), - new Vector3f(-1, 0, 0), - 15 - ) - ); - assertEquals( // Looking towards +X - blockPos, - world.getLookingAtPos( - new Vector3f(15f, 20.5f, 20.5f), - new Vector3f(1, 0, 0), - 15 - ) - ); + World world = new World(); + Chunk chunk = new Chunk(0, 0, 0); + world.addChunk(chunk); + // Spawn a block in the middle for testing. + Vector3i blockPos = new Vector3i(7, 7, 7); + world.setBlockAt(blockPos.x, blockPos.y, blockPos.z, (byte) 1); + Hit hit; - // Looking up into the void. - assertNull(world.getLookingAtPos( - new Vector3f(0, 30, 0), - new Vector3f(0, 1, 0), - 10 - )); + // Looking down. + hit = world.getLookingAtPos(new Vector3f(7.5f, 10, 7.5f), new Vector3f(0, -1, 0), 10); + assertEquals(blockPos, hit.pos()); + assertEquals(Directions.UP, hit.norm()); - // Looking straight down, but too far away. - assertNull(world.getLookingAtPos( - new Vector3f(0.5f, 20, 0.5f), - new Vector3f(0, -1, 0), - 15 - )); - assertEquals( // Standing in the corner, looking into center of block. - blockPos, - world.getLookingAtPos( - new Vector3f(20f, 21f, 20f), - new Vector3f(1, -2, 1).normalize(), - 15 - ) - ); + // Looking up. + hit = world.getLookingAtPos(new Vector3f(7.5f, 5, 7.5f), new Vector3f(0, 1, 0), 10); + assertEquals(blockPos, hit.pos()); + assertEquals(Directions.DOWN, hit.norm()); } } diff --git a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java index 18e1d93..b2caa60 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java +++ b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java @@ -101,7 +101,7 @@ public class ClientCommunicationHandler { new Thread(tcpReceiver).start(); } } catch (IOException e) { - e.printStackTrace(); + log.warn("An IOException occurred while attempting to establish a connection to a client.", e); } attempts++; } diff --git a/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java b/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java index 1e1e753..db51e1f 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java +++ b/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java @@ -33,9 +33,11 @@ public class ServerPlayer extends Player { public static final float JUMP_SPEED = 8f; public static final int BLOCK_REMOVE_COOLDOWN = 250; + public static final int BLOCK_PLACE_COOLDOWN = 100; private ClientInputState lastInputState; private long lastBlockRemovedAt = 0; + private long lastBlockPlacedAt = 0; private boolean updated = false; @@ -59,27 +61,27 @@ public class ServerPlayer extends Player { public void tick(float dt, World world, Server server) { long now = System.currentTimeMillis(); + // Check for breaking blocks. if (lastInputState.hitting() && now - lastBlockRemovedAt > BLOCK_REMOVE_COOLDOWN) { Vector3f eyePos = new Vector3f(position); eyePos.y += getEyeHeight(); var hit = world.getLookingAtPos(eyePos, viewVector, 10); - System.out.println(hit); if (hit != null) { world.setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0); lastBlockRemovedAt = now; server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), world)); } } - if (lastInputState.interacting() && now - lastBlockRemovedAt > BLOCK_REMOVE_COOLDOWN) { + // Check for placing blocks. + if (lastInputState.interacting() && now - lastBlockPlacedAt > BLOCK_PLACE_COOLDOWN) { Vector3f eyePos = new Vector3f(position); eyePos.y += getEyeHeight(); var hit = world.getLookingAtPos(eyePos, viewVector, 10); - System.out.println(hit); if (hit != null) { Vector3i placePos = new Vector3i(hit.pos()); placePos.add(hit.norm()); world.setBlockAt(placePos.x, placePos.y, placePos.z, (byte) 1); - lastBlockRemovedAt = now; + lastBlockPlacedAt = now; server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(placePos, world)); } }