diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/Freeworld.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/Freeworld.java index ecf62b4..2305ab1 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/Freeworld.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/Freeworld.java @@ -15,9 +15,11 @@ import io.github.xenfork.freeworld.client.render.gl.GLStateMgr; import io.github.xenfork.freeworld.client.render.world.HitResult; import io.github.xenfork.freeworld.core.registry.BuiltinRegistries; +import io.github.xenfork.freeworld.util.Direction; import io.github.xenfork.freeworld.util.Logging; import io.github.xenfork.freeworld.util.Timer; import io.github.xenfork.freeworld.world.World; +import io.github.xenfork.freeworld.world.block.BlockType; import io.github.xenfork.freeworld.world.block.BlockTypes; import io.github.xenfork.freeworld.world.entity.Entity; import io.github.xenfork.freeworld.world.entity.EntityTypes; @@ -64,6 +66,8 @@ public final class Freeworld implements AutoCloseable { private World world; private Entity player; private int blockDestroyTimer = 0; + private int blockPlaceTimer = 0; + private int selectBlock = 0; private Freeworld() { this.glfw = GLFW.INSTANCE; @@ -132,6 +136,13 @@ private void onKey(int key, int scancode, int action, int mods) { } } } + case GLFW.PRESS -> { + switch (key) { + case GLFW.KEY_1 -> selectBlock = 0; + case GLFW.KEY_2 -> selectBlock = 1; + case GLFW.KEY_3 -> selectBlock = 2; + } + } } } @@ -187,7 +198,30 @@ private void tick() { blockDestroyTimer = 0; } } + if (blockPlaceTimer >= 3) { + final HitResult hitResult = gameRenderer.hitResult(); + if (!hitResult.missed() && + glfw.getMouseButton(window, GLFW.MOUSE_BUTTON_RIGHT) == GLFW.PRESS) { + final Direction face = hitResult.face(); + final BlockType type = switch (selectBlock) { + case 0 -> BlockTypes.STONE; + case 1 -> BlockTypes.DIRT; + case 2 -> BlockTypes.GRASS_BLOCK; + default -> BlockTypes.AIR; + }; + if (!type.air()) { + world.setBlockType( + hitResult.x() + face.axisX(), + hitResult.y() + face.axisY(), + hitResult.z() + face.axisZ(), + type + ); + } + blockPlaceTimer = 0; + } + } blockDestroyTimer++; + blockPlaceTimer++; } private void initGL() { diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java index 26d28c4..b2e5448 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java @@ -24,6 +24,7 @@ import io.github.xenfork.freeworld.client.render.world.WorldRenderer; import io.github.xenfork.freeworld.core.Identifier; import io.github.xenfork.freeworld.core.math.AABBox; +import io.github.xenfork.freeworld.util.Direction; import io.github.xenfork.freeworld.util.Logging; import org.joml.Matrix4f; import org.slf4j.Logger; @@ -52,7 +53,7 @@ public final class GameRenderer implements GLResource { private BlockRenderer blockRenderer; private WorldRenderer worldRenderer; private Tessellator tessellator; - private HitResult hitResult = new HitResult(null, 0, 0, 0, true); + private HitResult hitResult = new HitResult(true, null, 0, 0, 0, Direction.SOUTH); public GameRenderer(Freeworld client) { this.client = client; diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/HitResult.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/HitResult.java index fd8c3af..18f4b35 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/HitResult.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/HitResult.java @@ -10,6 +10,7 @@ package io.github.xenfork.freeworld.client.render.world; +import io.github.xenfork.freeworld.util.Direction; import io.github.xenfork.freeworld.world.block.BlockType; /** @@ -17,10 +18,11 @@ * @since 0.1.0 */ public record HitResult( + boolean missed, BlockType blockType, int x, int y, int z, - boolean missed + Direction face ) { } diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java index 3b21824..aedb831 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java @@ -145,6 +145,7 @@ public HitResult selectBlock() { int nearestX = 0; int nearestY = 0; int nearestZ = 0; + Direction face = Direction.SOUTH; final float radius = 5.0f; final float radiusSquared = radius * radius; @@ -187,13 +188,173 @@ public HitResult selectBlock() { nearestX = x; nearestY = y; nearestZ = z; + face = detectFace( + ox, + oy, + oz, + frustumRayDir.x(), + frustumRayDir.y(), + frustumRayDir.z(), + box + ); } } } } } - return new HitResult(nearestBlock, nearestX, nearestY, nearestZ, nearestBlock == null); + // TODO: 2024/4/12 squid233: detect face + return new HitResult(nearestBlock == null, nearestBlock, nearestX, nearestY, nearestZ, face); + } + + private Direction detectFace( + double originX, + double originY, + double originZ, + double dirX, + double dirY, + double dirZ, + AABBox box + ) { + double t = -1.0; + Direction direction = Direction.SOUTH; + for (Direction dir : Direction.LIST) { + final double v = rayFace(dir, originX, originY, originZ, dirX, dirY, dirZ, box); + if (v > t) { + t = v; + direction = dir; + } + } + return direction; + } + + private double rayFace( + Direction direction, + double originX, + double originY, + double originZ, + double dirX, + double dirY, + double dirZ, + AABBox box + ) { + final double epsilon = 0.001; + final double minX = box.minX(); + final double minY = box.minY(); + final double minZ = box.minZ(); + final double maxX = box.maxX(); + final double maxY = box.maxY(); + final double maxZ = box.maxZ(); + return switch (direction) { + case WEST -> Math.max( + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + minX, maxY, minZ, + minX, minY, minZ, + minX, minY, maxZ, + epsilon + ), + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + minX, minY, maxZ, + minX, maxY, maxZ, + minX, maxY, minZ, + epsilon + ) + ); + case EAST -> Math.max( + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + maxX, maxY, maxZ, + maxX, minY, maxZ, + maxX, minY, minZ, + epsilon + ), + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + maxX, minY, minZ, + maxX, maxY, minZ, + maxX, maxY, maxZ, + epsilon + ) + ); + case DOWN -> Math.max( + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + minX, minY, maxZ, + minX, minY, minZ, + maxX, minY, minZ, + epsilon + ), + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + maxX, minY, minZ, + maxX, minY, maxZ, + minX, minY, maxZ, + epsilon + ) + ); + case UP -> Math.max( + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + minX, maxY, minZ, + minX, maxY, maxZ, + maxX, maxY, maxZ, + epsilon + ), + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + maxX, maxY, maxZ, + maxX, maxY, minZ, + minX, maxY, minZ, + epsilon + ) + ); + case NORTH -> Math.max( + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + maxX, maxY, minZ, + maxX, minY, minZ, + minX, minY, minZ, + epsilon + ), + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + minX, minY, minZ, + minX, maxY, minZ, + maxX, maxY, minZ, + epsilon + ) + ); + case SOUTH -> Math.max( + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + minX, maxY, maxZ, + minX, minY, maxZ, + maxX, minY, maxZ, + epsilon + ), + Intersectiond.intersectRayTriangleFront( + originX, originY, originZ, + dirX, dirY, dirZ, + maxX, minY, maxZ, + maxX, maxY, maxZ, + minX, maxY, maxZ, + epsilon + ) + ); + }; } @Override