diff --git a/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java b/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java index 8c2392c..884ec1b 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java @@ -18,11 +18,12 @@ import freeworld.client.render.screen.ingame.CreativeTabScreen; import freeworld.client.render.screen.ingame.PauseScreen; import freeworld.client.render.screen.Screen; -import freeworld.client.render.world.HitResult; +import freeworld.client.render.world.BlockHitResult; import freeworld.client.render.world.WorldRenderer; import freeworld.core.registry.Registries; import freeworld.math.Vector2d; import freeworld.math.Vector3d; +import freeworld.math.Vector3i; import freeworld.util.Direction; import freeworld.util.Logging; import freeworld.util.math.MathUtil; @@ -296,7 +297,7 @@ private void tick() { MathUtil.moveRelative(xo, 0.0, zo, player.getComponent(EntityComponents.ROTATION).y(), speed)); if (blockDestroyTimer >= 2) { - final HitResult hitResult = gameRenderer.hitResult(); + final BlockHitResult hitResult = gameRenderer.hitResult(); if (!hitResult.missed() && glfw.getMouseButton(window, GLFW.MOUSE_BUTTON_LEFT) == GLFW.PRESS) { world.setBlockType(hitResult.x(), hitResult.y(), hitResult.z(), BlockTypes.AIR); @@ -304,16 +305,17 @@ private void tick() { } } if (blockPlaceTimer >= 2) { - final HitResult hitResult = gameRenderer.hitResult(); + final BlockHitResult hitResult = gameRenderer.hitResult(); if (!hitResult.missed() && glfw.getMouseButton(window, GLFW.MOUSE_BUTTON_RIGHT) == GLFW.PRESS) { final Direction face = hitResult.face(); final BlockType type = hotBar[hotBarSelection]; if (!type.air()) { + Vector3i axis = face.axis(); world.setBlockType( - hitResult.x() + face.axisX(), - hitResult.y() + face.axisY(), - hitResult.z() + face.axisZ(), + hitResult.x() + axis.x(), + hitResult.y() + axis.y(), + hitResult.z() + axis.z(), type ); } diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/GameRenderer.java b/modules/freeworld.client/src/main/java/freeworld/client/render/GameRenderer.java index 036150d..b60ce6f 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/GameRenderer.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/GameRenderer.java @@ -26,15 +26,15 @@ import freeworld.client.render.texture.TextureAtlas; import freeworld.client.render.texture.TextureManager; import freeworld.client.render.world.BlockRenderer; -import freeworld.client.render.world.HitResult; +import freeworld.client.render.world.BlockHitResult; import freeworld.client.render.world.WorldRenderer; import freeworld.client.world.chunk.ClientChunk; import freeworld.core.Identifier; import freeworld.core.ModelResourcePath; -import freeworld.core.math.AABBox; import freeworld.math.Matrix4f; import freeworld.util.Direction; import freeworld.util.Logging; +import freeworld.util.math.Lined; import freeworld.world.entity.Entity; import org.slf4j.Logger; import overrungl.opengl.GL10C; @@ -58,7 +58,7 @@ public final class GameRenderer implements GLResource { private HudRenderer hudRenderer; private BlockRenderer blockRenderer; private WorldRenderer worldRenderer; - private HitResult hitResult = new HitResult(true, null, 0, 0, 0, Direction.SOUTH); + private BlockHitResult hitResult = new BlockHitResult(true, null, 0, 0, 0, Direction.SOUTH); public GameRenderer(Freeworld client) { this.client = client; @@ -157,40 +157,33 @@ public void render(GLStateMgr gl, double partialTick) { final List chunks = worldRenderer.renderingChunks(player); worldRenderer.compileChunks(chunks); + hitResult = worldRenderer.selectBlock(player); + RenderSystem.bindTexture2D(textureManager.getTexture(TextureManager.BLOCK_ATLAS)); + if (!hitResult.missed()) { + gl.enablePolygonOffsetFill(); + gl.setPolygonOffset(1.0f, 1.0f); + gl.setLineWidth(2.0f); + } worldRenderer.renderChunks(gl, chunks); + if (!hitResult.missed()) { + gl.disablePolygonOffsetFill(); + gl.setLineWidth(1.0f); + } - hitResult = worldRenderer.selectBlock(player); if (!hitResult.missed()) { - final AABBox box = hitResult.blockType().outlineShape().move(hitResult.x(), hitResult.y(), hitResult.z()); - final float minX = (float) box.minX(); - final float minY = (float) box.minY(); - final float minZ = (float) box.minZ(); - final float maxX = (float) box.maxX(); - final float maxY = (float) box.maxY(); - final float maxZ = (float) box.maxZ(); - final float offset = 0.005f; + var lines = hitResult.blockType().outlineShape().toLines(Direction.LIST); + Matrix4f mat = Matrix4f.translation(hitResult.x(), hitResult.y(), hitResult.z()); RenderSystem.bindTexture2D(null); RenderSystem.useProgram(positionColorProgram); RenderSystem.updateMatrices(); final Tessellator tessellator = Tessellator.getInstance(); tessellator.begin(GLDrawMode.LINES); - // -x - tessellator.indices(0, 1, 0, 2, 1, 3, 2, 3); - // +x - tessellator.indices(4, 5, 4, 6, 5, 7, 6, 7); - // -z - tessellator.indices(0, 4, 2, 6); - // +z - tessellator.indices(1, 5, 3, 7); - tessellator.position(minX - offset, minY - offset, minZ - offset).color(0, 0, 0).texCoord(0f, 0f).emit(); - tessellator.position(minX - offset, minY - offset, maxZ + offset).color(0, 0, 0).texCoord(0f, 0f).emit(); - tessellator.position(minX - offset, maxY + offset, minZ - offset).color(0, 0, 0).texCoord(0f, 0f).emit(); - tessellator.position(minX - offset, maxY + offset, maxZ + offset).color(0, 0, 0).texCoord(0f, 0f).emit(); - tessellator.position(maxX + offset, minY - offset, minZ - offset).color(0, 0, 0).texCoord(0f, 0f).emit(); - tessellator.position(maxX + offset, minY - offset, maxZ + offset).color(0, 0, 0).texCoord(0f, 0f).emit(); - tessellator.position(maxX + offset, maxY + offset, minZ - offset).color(0, 0, 0).texCoord(0f, 0f).emit(); - tessellator.position(maxX + offset, maxY + offset, maxZ + offset).color(0, 0, 0).texCoord(0f, 0f).emit(); + for (Lined line : lines) { + tessellator.indices(0, 1); + tessellator.position(mat, line.from().toVector3f()).color(0, 0, 0).texCoord(0f, 0f).emit(); + tessellator.position(mat, line.to().toVector3f()).color(0, 0, 0).texCoord(0f, 0f).emit(); + } tessellator.end(gl); } @@ -262,7 +255,7 @@ public BlockRenderer blockRenderer() { return blockRenderer; } - public HitResult hitResult() { + public BlockHitResult hitResult() { return hitResult; } } diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/Tessellator.java b/modules/freeworld.client/src/main/java/freeworld/client/render/Tessellator.java index afea0eb..389effc 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/Tessellator.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/Tessellator.java @@ -18,6 +18,7 @@ import freeworld.client.render.model.vertex.VertexLayout; import freeworld.client.render.model.vertex.VertexLayouts; import freeworld.math.Matrix4f; +import freeworld.math.Vector3f; import overrungl.opengl.GL10C; import overrungl.opengl.GL15C; @@ -62,6 +63,12 @@ public Tessellator position(Matrix4f positionMatrix, float x, float y, float z) return this; } + @Override + public Tessellator position(Matrix4f positionMatrix, Vector3f v) { + VertexBuilder.super.position(positionMatrix, v); + return this; + } + @Override public Tessellator color(int red, int green, int blue, int alpha) { vertexBuilder.color(red, green, blue, alpha); diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/builder/DefaultVertexBuilder.java b/modules/freeworld.client/src/main/java/freeworld/client/render/builder/DefaultVertexBuilder.java index af0f976..beb659c 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/builder/DefaultVertexBuilder.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/builder/DefaultVertexBuilder.java @@ -13,6 +13,7 @@ import freeworld.client.render.model.vertex.VertexFormat; import freeworld.client.render.model.vertex.VertexLayout; import freeworld.math.Matrix4f; +import freeworld.math.Vector3f; import freeworld.util.Logging; import org.slf4j.Logger; @@ -116,6 +117,12 @@ public DefaultVertexBuilder position(Matrix4f positionMatrix, float x, float y, return this; } + @Override + public DefaultVertexBuilder position(Matrix4f positionMatrix, Vector3f v) { + VertexBuilder.super.position(positionMatrix, v); + return this; + } + @Override public DefaultVertexBuilder color(int red, int green, int blue, int alpha) { VertexBuilder.super.color(red, green, blue, alpha); diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/builder/VertexBuilder.java b/modules/freeworld.client/src/main/java/freeworld/client/render/builder/VertexBuilder.java index 3af3f1e..e99e8d6 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/builder/VertexBuilder.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/builder/VertexBuilder.java @@ -12,6 +12,7 @@ import freeworld.client.render.model.vertex.VertexLayout; import freeworld.math.Matrix4f; +import freeworld.math.Vector3f; import freeworld.math.Vector4f; import java.lang.foreign.MemorySegment; @@ -42,6 +43,10 @@ default VertexBuilder position(Matrix4f positionMatrix, float x, float y, float return position(v.x(), v.y(), v.z()); } + default VertexBuilder position(Matrix4f positionMatrix, Vector3f v) { + return position(positionMatrix, v.x(), v.y(), v.z()); + } + default VertexBuilder color(int red, int green, int blue, int alpha) { nextElement((byte) red); nextElement((byte) green); diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/gl/GLStateMgr.java b/modules/freeworld.client/src/main/java/freeworld/client/render/gl/GLStateMgr.java index e2fe97a..7d49e06 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/gl/GLStateMgr.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/gl/GLStateMgr.java @@ -30,6 +30,10 @@ public abstract class GLStateMgr implements GL { private int currentProgram = 0; private int depthFunc = LESS; private boolean depthTest = false; + private float lineWidth = 1.0f; + private boolean polygonOffsetFill = false; + private float polygonOffsetFactor = 0.0f; + private float polygonOffsetUnits = 0.0f; private int textureBinding2D = 0; private int vertexArrayBinding = 0; @@ -174,6 +178,60 @@ public boolean depthTest() { return depthTest; } + @Skip + public void setLineWidth(float width) { + if (this.lineWidth != width) { + this.lineWidth = width; + lineWidth(width); + } + } + + @Skip + public float lineWidth() { + return lineWidth; + } + + @Skip + public void enablePolygonOffsetFill() { + if (!this.polygonOffsetFill) { + this.polygonOffsetFill = true; + enable(POLYGON_OFFSET_FILL); + } + } + + @Skip + public void disablePolygonOffsetFill() { + if (this.polygonOffsetFill) { + this.polygonOffsetFill = false; + disable(POLYGON_OFFSET_FILL); + } + } + + @Skip + public boolean polygonOffsetFill() { + return polygonOffsetFill; + } + + @Skip + public void setPolygonOffset(float factor, float units) { + if (this.polygonOffsetFactor != factor || + this.polygonOffsetUnits != units) { + this.polygonOffsetFactor = factor; + this.polygonOffsetUnits = units; + polygonOffset(factor, units); + } + } + + @Skip + public float polygonOffsetFactor() { + return polygonOffsetFactor; + } + + @Skip + public float polygonOffsetUnits() { + return polygonOffsetUnits; + } + @Skip public void setTextureBinding2D(int textureBinding2D) { if (this.textureBinding2D != textureBinding2D) { diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/HitResult.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockHitResult.java similarity index 74% rename from modules/freeworld.client/src/main/java/freeworld/client/render/world/HitResult.java rename to modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockHitResult.java index 1ca3217..a2564e6 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/world/HitResult.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockHitResult.java @@ -4,8 +4,8 @@ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. + * License as published by the Free Software Foundation; + * only version 2.1 of the License. */ package freeworld.client.render.world; @@ -17,7 +17,7 @@ * @author squid233 * @since 0.1.0 */ -public record HitResult( +public record BlockHitResult( boolean missed, BlockType blockType, int x, diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockRenderer.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockRenderer.java index 887d358..623d58f 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockRenderer.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/BlockRenderer.java @@ -21,7 +21,9 @@ import freeworld.math.Matrix4f; import freeworld.math.Vector2f; import freeworld.math.Vector3f; +import freeworld.math.Vector4i; import freeworld.util.Direction; +import freeworld.util.math.AABBox; import java.util.function.Predicate; @@ -38,51 +40,18 @@ public BlockRenderer(TextureManager textureManager) { private void emitVertices(VertexBuilder builder, Matrix4f matrix, Vector3f from, Vector3f to, Vector2f uvFrom, Vector2f uvTo, Direction direction) { // TODO: 2024/7/6 squid233: color - float color; - switch (direction) { - case WEST -> { - color = 0.7f; - builder.position(matrix, from.x(), to.y(), from.z()).color(color, color, color).texCoord(uvFrom.x(), uvFrom.y()).emit(); - builder.position(matrix, from.x(), from.y(), from.z()).color(color, color, color).texCoord(uvFrom.x(), uvTo.y()).emit(); - builder.position(matrix, from.x(), from.y(), to.z()).color(color, color, color).texCoord(uvTo.x(), uvTo.y()).emit(); - builder.position(matrix, from.x(), to.y(), to.z()).color(color, color, color).texCoord(uvTo.x(), uvFrom.y()).emit(); - } - case EAST -> { - color = 1.0f; - builder.position(matrix, to.x(), to.y(), to.z()).color(color, color, color).texCoord(uvFrom.x(), uvFrom.y()).emit(); - builder.position(matrix, to.x(), from.y(), to.z()).color(color, color, color).texCoord(uvFrom.x(), uvTo.y()).emit(); - builder.position(matrix, to.x(), from.y(), from.z()).color(color, color, color).texCoord(uvTo.x(), uvTo.y()).emit(); - builder.position(matrix, to.x(), to.y(), from.z()).color(color, color, color).texCoord(uvTo.x(), uvFrom.y()).emit(); - } - case DOWN -> { - color = 0.6f; - builder.position(matrix, from.x(), from.y(), to.z()).color(color, color, color).texCoord(uvFrom.x(), uvFrom.y()).emit(); - builder.position(matrix, from.x(), from.y(), from.z()).color(color, color, color).texCoord(uvFrom.x(), uvTo.y()).emit(); - builder.position(matrix, to.x(), from.y(), from.z()).color(color, color, color).texCoord(uvTo.x(), uvTo.y()).emit(); - builder.position(matrix, to.x(), from.y(), to.z()).color(color, color, color).texCoord(uvTo.x(), uvFrom.y()).emit(); - } - case UP -> { - color = 0.9f; - builder.position(matrix, from.x(), to.y(), from.z()).color(color, color, color).texCoord(uvFrom.x(), uvFrom.y()).emit(); - builder.position(matrix, from.x(), to.y(), to.z()).color(color, color, color).texCoord(uvFrom.x(), uvTo.y()).emit(); - builder.position(matrix, to.x(), to.y(), to.z()).color(color, color, color).texCoord(uvTo.x(), uvTo.y()).emit(); - builder.position(matrix, to.x(), to.y(), from.z()).color(color, color, color).texCoord(uvTo.x(), uvFrom.y()).emit(); - } - case NORTH -> { - color = 0.8f; - builder.position(matrix, to.x(), to.y(), from.z()).color(color, color, color).texCoord(uvFrom.x(), uvFrom.y()).emit(); - builder.position(matrix, to.x(), from.y(), from.z()).color(color, color, color).texCoord(uvFrom.x(), uvTo.y()).emit(); - builder.position(matrix, from.x(), from.y(), from.z()).color(color, color, color).texCoord(uvTo.x(), uvTo.y()).emit(); - builder.position(matrix, from.x(), to.y(), from.z()).color(color, color, color).texCoord(uvTo.x(), uvFrom.y()).emit(); - } - case SOUTH -> { - color = 0.8f; - builder.position(matrix, from.x(), to.y(), to.z()).color(color, color, color).texCoord(uvFrom.x(), uvFrom.y()).emit(); - builder.position(matrix, from.x(), from.y(), to.z()).color(color, color, color).texCoord(uvFrom.x(), uvTo.y()).emit(); - builder.position(matrix, to.x(), from.y(), to.z()).color(color, color, color).texCoord(uvTo.x(), uvTo.y()).emit(); - builder.position(matrix, to.x(), to.y(), to.z()).color(color, color, color).texCoord(uvTo.x(), uvFrom.y()).emit(); - } - } + float color = switch (direction) { + case WEST -> 0.7f; + case EAST -> 1.0f; + case DOWN -> 0.6f; + case UP -> 0.9f; + case NORTH, SOUTH -> 0.8f; + }; + Vector4i vertexIndices = direction.vertexIndices(); + builder.position(matrix, AABBox.getPoint(from, to, vertexIndices.x())).color(color, color, color).texCoord(uvFrom.x(), uvFrom.y()).emit(); + builder.position(matrix, AABBox.getPoint(from, to, vertexIndices.y())).color(color, color, color).texCoord(uvFrom.x(), uvTo.y()).emit(); + builder.position(matrix, AABBox.getPoint(from, to, vertexIndices.z())).color(color, color, color).texCoord(uvTo.x(), uvTo.y()).emit(); + builder.position(matrix, AABBox.getPoint(from, to, vertexIndices.w())).color(color, color, color).texCoord(uvTo.x(), uvFrom.y()).emit(); } public void renderBlockModel(VertexBuilder builder, BlockModel model, Matrix4f matrix, int x, int y, int z, Predicate shouldCullFace) { diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java index 874136b..5cbe94b 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java @@ -14,8 +14,9 @@ import freeworld.client.render.model.block.BlockModel; import freeworld.client.render.model.block.BlockModelManager; import freeworld.core.registry.Registries; +import freeworld.math.Vector3i; import freeworld.world.chunk.Chunk; -import freeworld.world.chunk.ChunkPos; +import freeworld.util.math.ChunkPos; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -38,13 +39,12 @@ public static ChunkVertexData compile( final int cx = chunk.x(); final int cy = chunk.y(); final int cz = chunk.z(); + Vector3i chunkPos = new Vector3i(cx, cy, cz); for (int x = 0; x < Chunk.SIZE; x++) { for (int y = 0; y < Chunk.SIZE; y++) { for (int z = 0; z < Chunk.SIZE; z++) { - int finalX = x; - int finalY = y; - int finalZ = z; + Vector3i finalPos = new Vector3i(x, y, z); final BlockModel model = blockModelManager.get(Registries.BLOCK_TYPE.getId(chunk.getBlockType(x, y, z))); blockRenderer.renderBlockModel( vertexBuilder, @@ -53,18 +53,14 @@ public static ChunkVertexData compile( ChunkPos.relativeToAbsolute(cy, y), ChunkPos.relativeToAbsolute(cz, z), direction -> { - final int nx = finalX + direction.axisX(); - final int ny = finalY + direction.axisY(); - final int nz = finalZ + direction.axisZ(); - final int absNx = ChunkPos.relativeToAbsolute(cx, nx); - final int absNy = ChunkPos.relativeToAbsolute(cy, ny); - final int absNz = ChunkPos.relativeToAbsolute(cz, nz); + Vector3i nPos = direction.axis().add(finalPos); + Vector3i abs = ChunkPos.relativeToAbsolute(chunkPos, nPos); final boolean shouldRender = - (chunk.isInBound(nx, ny, nz) && - chunk.getBlockType(nx, ny, nz).nonOpaque()) || - (chunk.world().isBlockLoaded(absNx, absNy, absNz) && - chunk.world().getBlockType(absNx, absNy, absNz).nonOpaque()) || - !chunk.world().isBlockLoaded(absNx, absNy, absNz) /* TODO: add method world::tryLoading() */; + (chunk.isInBound(nPos.x(), nPos.y(), nPos.z()) && + chunk.getBlockType(nPos.x(), nPos.y(), nPos.z()).nonOpaque()) || + (chunk.world().isBlockLoaded(abs.x(), abs.y(), abs.z()) && + chunk.world().getBlockType(abs.x(), abs.y(), abs.z()).nonOpaque()) || + !chunk.world().isBlockLoaded(abs.x(), abs.y(), abs.z()) /* TODO: add method world::tryLoading() */; return !shouldRender; } ); diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java index 8240aaa..2e8061a 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java @@ -17,14 +17,15 @@ import freeworld.client.render.gl.GLStateMgr; import freeworld.client.render.model.vertex.VertexLayouts; import freeworld.client.world.chunk.ClientChunk; -import freeworld.core.math.AABBox; +import freeworld.util.math.AABBox; import freeworld.math.*; import freeworld.util.Direction; import freeworld.util.Logging; +import freeworld.util.math.HitResult; import freeworld.world.World; import freeworld.world.WorldListener; import freeworld.world.block.BlockType; -import freeworld.world.chunk.ChunkPos; +import freeworld.util.math.ChunkPos; import freeworld.world.entity.Entity; import freeworld.world.entity.EntityComponents; import org.slf4j.Logger; @@ -57,7 +58,7 @@ public final class WorldRenderer implements GLResource, WorldListener { private final Pool vertexBuilderPool = PoolBuilder .from(Mono.fromSupplier(WorldRenderer::createVertexBuilder).subscribeOn(scheduler)) .buildPool(); - private final Map chunks = new ConcurrentHashMap<>(RENDER_CHUNK_COUNT); + private final Map chunks = new ConcurrentHashMap<>(RENDER_CHUNK_COUNT); private final Disposable chunkGC; private int playerChunkX = 0; private int playerChunkY = 0; @@ -76,8 +77,8 @@ private static DefaultVertexBuilder createVertexBuilder() { } private void uninstallChunks() { - final List list = new ArrayList<>(RENDER_CHUNK_COUNT); - World.forEachChunk(gameRenderer.client().player(), RENDER_RADIUS, (x, y, z) -> list.add(new ChunkPos(x, y, z))); + final List list = new ArrayList<>(RENDER_CHUNK_COUNT); + World.forEachChunk(gameRenderer.client().player(), RENDER_RADIUS, (x, y, z) -> list.add(new Vector3i(x, y, z))); final var it = chunks.entrySet().iterator(); while (it.hasNext()) { final var e = it.next(); @@ -134,7 +135,7 @@ public void renderChunks(GLStateMgr gl, List renderingChunks) { } } - public HitResult selectBlock(Entity player) { + public BlockHitResult selectBlock(Entity player) { final FrustumRayBuilder frustumRayBuilder = new FrustumRayBuilder(RenderSystem.projectionViewMatrix()); final Vector3f frustumRayOrigin = frustumRayBuilder.origin(); final Vector3f frustumRayDir = frustumRayBuilder.dir(0.5f, 0.5f); @@ -163,205 +164,35 @@ public HitResult selectBlock(Entity player) { final float vx = x + 0.5f - ox; final float xSquared = vx * vx; for (int y = y0; y <= y1; y++) { + final float vy = y + 0.5f - oy; + final float ySquared = vy * vy; for (int z = z0; z <= z1; z++) { if (!world.isBlockLoaded(x, y, z)) { continue; } final float vz = z + 0.5f - oz; final float zSquared = vz * vz; - if ((xSquared + zSquared) <= radiusSquared) { + if ((xSquared + ySquared + zSquared) <= radiusSquared) { final BlockType blockType = world.getBlockType(x, y, z); if (blockType.air()) { continue; } - final AABBox box = blockType.outlineShape().move(x, y, z); - final Intersectiond.RayAab blockIntersectionResult = Intersectiond.intersectRayAab( - ox, - oy, - oz, - frustumRayDir.x(), - frustumRayDir.y(), - frustumRayDir.z(), - box.minX(), - box.minY(), - box.minZ(), - box.maxX(), - box.maxY(), - box.maxZ() - ); - if (blockIntersectionResult.intersected() && - blockIntersectionResult.result().x() < nearestBlockDistance) { - nearestBlockDistance = blockIntersectionResult.result().x(); + HitResult hitResult = blockType.outlineShape().rayCast(frustumRayOrigin.sub(x, y, z).toVector3d(), frustumRayDir.toVector3d()); + if (!hitResult.missed() && + hitResult.distance() < nearestBlockDistance) { + nearestBlockDistance = hitResult.distance(); nearestBlock = blockType; nearestX = x; nearestY = y; nearestZ = z; - face = detectFace( - ox, - oy, - oz, - frustumRayDir.x(), - frustumRayDir.y(), - frustumRayDir.z(), - box - ); + face = hitResult.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 - ) - ); - }; + return new BlockHitResult(nearestBlock == null, nearestBlock, nearestX, nearestY, nearestZ, face); } @Override @@ -371,10 +202,11 @@ public void onBlockChanged(int x, int y, int z) { chunk.markDirty(); } for (Direction direction : Direction.LIST) { + Vector3i axis = direction.axis(); final ClientChunk chunk1 = getChunkByAbsolutePos( - x + direction.axisX(), - y + direction.axisY(), - z + direction.axisZ() + x + axis.x(), + y + axis.y(), + z + axis.z() ); if (chunk1 != null) { chunk1.markDirty(); @@ -383,11 +215,11 @@ public void onBlockChanged(int x, int y, int z) { } private ClientChunk getChunk(int x, int y, int z) { - return chunks.get(new ChunkPos(x, y, z)); + return chunks.get(new Vector3i(x, y, z)); } private ClientChunk getChunkOrCreate(int x, int y, int z) { - return chunks.computeIfAbsent(new ChunkPos(x, y, z), + return chunks.computeIfAbsent(new Vector3i(x, y, z), chunkPos -> new ClientChunk(world, this, chunkPos.x(), chunkPos.y(), chunkPos.z())); } diff --git a/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java b/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java index 67ea635..da3bdca 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java @@ -17,14 +17,8 @@ import freeworld.client.render.world.ChunkCompiler; import freeworld.client.render.world.ChunkVertexData; import freeworld.client.render.world.WorldRenderer; -import freeworld.math.Vector2d; -import freeworld.math.Vector3d; -import freeworld.util.Logging; import freeworld.world.World; import freeworld.world.chunk.Chunk; -import freeworld.world.entity.Entity; -import freeworld.world.entity.EntityComponents; -import org.slf4j.Logger; import overrungl.opengl.GL15C; import reactor.core.Disposable; import reactor.core.publisher.Flux; @@ -39,7 +33,6 @@ * @since 0.1.0 */ public final class ClientChunk extends Chunk implements AutoCloseable { - private static final Logger logger = Logging.caller(); private static final Cleaner CLEANER = Cleaner.create(); private final Cleaner.Cleanable cleanable; private final State state; @@ -150,22 +143,6 @@ private void buildBuffer(GLStateMgr gl, ChunkVertexData data) { } } - public double xzDistanceToPlayerSquared(Entity player) { - if (!player.hasComponent(EntityComponents.POSITION)) { - return 0.0; - } - final Vector3d value = player.getComponent(EntityComponents.POSITION); - return Vector2d.distanceSquared(x(), value.x(), z(), value.z()); - } - - public double yDistanceToPlayer(Entity player) { - if (!player.hasComponent(EntityComponents.POSITION)) { - return 0.0; - } - final Vector3d value = player.getComponent(EntityComponents.POSITION); - return Math.abs(value.y() - y()); - } - @Override public void markDirty() { super.markDirty(); diff --git a/modules/freeworld.core/src/main/java/freeworld/util/Direction.java b/modules/freeworld.core/src/main/java/freeworld/util/Direction.java index 77d0892..55f4ffc 100644 --- a/modules/freeworld.core/src/main/java/freeworld/util/Direction.java +++ b/modules/freeworld.core/src/main/java/freeworld/util/Direction.java @@ -4,39 +4,42 @@ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. + * License as published by the Free Software Foundation; + * only version 2.1 of the License. */ package freeworld.util; +import freeworld.math.Vector3i; +import freeworld.math.Vector4i; + import java.util.List; +import static freeworld.util.math.AABBox.*; + /** * @author squid233 * @since 0.1.0 */ public enum Direction { - WEST(0, 1, -1, 0, 0), - EAST(1, 0, 1, 0, 0), - DOWN(2, 3, 0, -1, 0), - UP(3, 2, 0, 1, 0), - NORTH(4, 5, 0, 0, -1), - SOUTH(5, 4, 0, 0, 1); + WEST(0, 1, new Vector3i(-1, 0, 0), new Vector4i(NX_PY_NZ, NX_NY_NZ, NX_NY_PZ, NX_PY_PZ)), + EAST(1, 0, new Vector3i(1, 0, 0), new Vector4i(PX_PY_PZ, PX_NY_PZ, PX_NY_NZ, PX_PY_NZ)), + DOWN(2, 3, new Vector3i(0, -1, 0), new Vector4i(NX_NY_PZ, NX_NY_NZ, PX_NY_NZ, PX_NY_PZ)), + UP(3, 2, new Vector3i(0, 1, 0), new Vector4i(NX_PY_NZ, NX_PY_PZ, PX_PY_PZ, PX_PY_NZ)), + NORTH(4, 5, new Vector3i(0, 0, -1), new Vector4i(PX_PY_NZ, PX_NY_NZ, NX_NY_NZ, NX_PY_NZ)), + SOUTH(5, 4, new Vector3i(0, 0, 1), new Vector4i(NX_PY_PZ, NX_NY_PZ, PX_NY_PZ, PX_PY_PZ)); public static final List LIST = List.of(values()); private final int id; private final int oppositeId; - private final int axisX; - private final int axisY; - private final int axisZ; + private final Vector3i axis; + private final Vector4i vertexIndices; - Direction(int id, int oppositeId, int axisX, int axisY, int axisZ) { + Direction(int id, int oppositeId, Vector3i axis, Vector4i vertexIndices) { this.id = id; this.oppositeId = oppositeId; - this.axisX = axisX; - this.axisY = axisY; - this.axisZ = axisZ; + this.axis = axis; + this.vertexIndices = vertexIndices; } public static Direction fromId(int id) { @@ -63,15 +66,11 @@ public int oppositeId() { return oppositeId; } - public int axisX() { - return axisX; - } - - public int axisY() { - return axisY; + public Vector3i axis() { + return axis; } - public int axisZ() { - return axisZ; + public Vector4i vertexIndices() { + return vertexIndices; } } diff --git a/modules/freeworld.core/src/main/java/freeworld/core/math/AABBox.java b/modules/freeworld.core/src/main/java/freeworld/util/math/AABBox.java similarity index 75% rename from modules/freeworld.core/src/main/java/freeworld/core/math/AABBox.java rename to modules/freeworld.core/src/main/java/freeworld/util/math/AABBox.java index 897a5bb..f92692a 100644 --- a/modules/freeworld.core/src/main/java/freeworld/core/math/AABBox.java +++ b/modules/freeworld.core/src/main/java/freeworld/util/math/AABBox.java @@ -8,7 +8,10 @@ * only version 2.1 of the License. */ -package freeworld.core.math; +package freeworld.util.math; + +import freeworld.math.Vector3d; +import freeworld.math.Vector3f; /** * @author squid233 @@ -22,10 +25,17 @@ public record AABBox( double maxY, double maxZ ) { - public static final AABBox EMPTY = new AABBox(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); - public static final AABBox FULL_CUBE = new AABBox(0.0, 0.0, 0.0, 1.0, 1.0, 1.0); + public static final int NX_NY_NZ = 0b000; + public static final int NX_NY_PZ = 0b001; + public static final int NX_PY_NZ = 0b010; + public static final int NX_PY_PZ = 0b011; + public static final int PX_NY_NZ = 0b100; + public static final int PX_NY_PZ = 0b101; + public static final int PX_PY_NZ = 0b110; + public static final int PX_PY_PZ = 0b111; public AABBox { + // autofix if (minX > maxX) { double _minX = minX; minX = maxX; @@ -43,6 +53,32 @@ public record AABBox( } } + public static Vector3f getPoint(Vector3f min, Vector3f max, int index) { + int ix = (index >> 2) & 1; + int iy = (index >> 1) & 1; + int iz = index & 1; + return new Vector3f( + ix == 0 ? min.x() : max.x(), + iy == 0 ? min.y() : max.y(), + iz == 0 ? min.z() : max.z() + ); + } + + public static Vector3d getPoint(Vector3d min, Vector3d max, int index) { + int ix = (index >> 2) & 1; + int iy = (index >> 1) & 1; + int iz = index & 1; + return new Vector3d( + ix == 0 ? min.x() : max.x(), + iy == 0 ? min.y() : max.y(), + iz == 0 ? min.z() : max.z() + ); + } + + public Vector3d getPoint(int index) { + return getPoint(new Vector3d(minX, minY, minZ), new Vector3d(maxX, maxY, maxZ), index); + } + public AABBox move(double x, double y, double z) { return new AABBox( minX() + x, diff --git a/modules/freeworld.core/src/main/java/freeworld/world/chunk/ChunkPos.java b/modules/freeworld.core/src/main/java/freeworld/util/math/ChunkPos.java similarity index 62% rename from modules/freeworld.core/src/main/java/freeworld/world/chunk/ChunkPos.java rename to modules/freeworld.core/src/main/java/freeworld/util/math/ChunkPos.java index 8663200..d0fe46f 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/chunk/ChunkPos.java +++ b/modules/freeworld.core/src/main/java/freeworld/util/math/ChunkPos.java @@ -4,21 +4,28 @@ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. + * License as published by the Free Software Foundation; + * only version 2.1 of the License. */ -package freeworld.world.chunk; +package freeworld.util.math; + +import freeworld.math.Vector3i; +import freeworld.world.chunk.Chunk; /** * @author squid233 * @since 0.1.0 */ -public record ChunkPos(int x, int y, int z) { +public final class ChunkPos { public static int relativeToAbsolute(int chunkPos, int relativePos) { return chunkPos * Chunk.SIZE + relativePos; } + public static Vector3i relativeToAbsolute(Vector3i chunkPos, Vector3i relativePos) { + return chunkPos.mul(Chunk.SIZE).add(relativePos); + } + public static int absoluteToRelative(int absolutePos) { return Math.floorMod(absolutePos, Chunk.SIZE); } diff --git a/modules/freeworld.core/src/main/java/freeworld/util/math/HitResult.java b/modules/freeworld.core/src/main/java/freeworld/util/math/HitResult.java new file mode 100644 index 0000000..3793c42 --- /dev/null +++ b/modules/freeworld.core/src/main/java/freeworld/util/math/HitResult.java @@ -0,0 +1,25 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * only version 2.1 of the License. + */ + +package freeworld.util.math; + +import freeworld.util.Direction; + +/** + * @author squid233 + * @since 0.1.0 + */ +public record HitResult( + boolean missed, + double distance, + Direction face +) { + public static final HitResult MISSED = new HitResult(true, 0.0, Direction.SOUTH); +} diff --git a/modules/freeworld.core/src/main/java/freeworld/util/math/Lined.java b/modules/freeworld.core/src/main/java/freeworld/util/math/Lined.java new file mode 100644 index 0000000..58ae6f7 --- /dev/null +++ b/modules/freeworld.core/src/main/java/freeworld/util/math/Lined.java @@ -0,0 +1,20 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * only version 2.1 of the License. + */ + +package freeworld.util.math; + +import freeworld.math.Vector3d; + +/** + * @author squid233 + * @since 0.1.0 + */ +public record Lined(Vector3d from, Vector3d to) { +} diff --git a/modules/freeworld.core/src/main/java/freeworld/util/shape/EmptyVoxelShape.java b/modules/freeworld.core/src/main/java/freeworld/util/shape/EmptyVoxelShape.java new file mode 100644 index 0000000..d7365f9 --- /dev/null +++ b/modules/freeworld.core/src/main/java/freeworld/util/shape/EmptyVoxelShape.java @@ -0,0 +1,45 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * only version 2.1 of the License. + */ + +package freeworld.util.shape; + +import freeworld.math.Vector3d; +import freeworld.util.Direction; +import freeworld.util.math.AABBox; +import freeworld.util.math.HitResult; +import freeworld.util.math.Lined; + +import java.util.List; + +/** + * @author squid233 + * @since 0.1.0 + */ +final class EmptyVoxelShape implements VoxelShape { + static final VoxelShape INSTANCE = new EmptyVoxelShape(); + + EmptyVoxelShape() { + } + + @Override + public HitResult rayCast(Vector3d origin, Vector3d dir) { + return HitResult.MISSED; + } + + @Override + public List toBoxes() { + return List.of(); + } + + @Override + public List toLines(List directions) { + return List.of(); + } +} diff --git a/modules/freeworld.core/src/main/java/freeworld/util/shape/SingleVoxelShape.java b/modules/freeworld.core/src/main/java/freeworld/util/shape/SingleVoxelShape.java new file mode 100644 index 0000000..dce302d --- /dev/null +++ b/modules/freeworld.core/src/main/java/freeworld/util/shape/SingleVoxelShape.java @@ -0,0 +1,121 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * only version 2.1 of the License. + */ + +package freeworld.util.shape; + +import freeworld.math.Intersectiond; +import freeworld.math.Vector3d; +import freeworld.math.Vector4i; +import freeworld.util.Direction; +import freeworld.util.math.AABBox; +import freeworld.util.math.HitResult; +import freeworld.util.math.Lined; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author squid233 + * @since 0.1.0 + */ +final class SingleVoxelShape implements VoxelShape { + private static final float epsilon = 0.00001f; + static final VoxelShape FULL_CUBE = new SingleVoxelShape(new AABBox(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)); + private final AABBox box; + private final List list; + + SingleVoxelShape(AABBox box) { + this.box = box; + this.list = List.of(box); + } + + private record RayCastFace(boolean missed, double distance) { + private static final RayCastFace MISSED = new RayCastFace(true, 0.0); + } + + private RayCastFace rayCastFace(Direction direction, Vector3d origin, Vector3d dir) { + Vector4i vertexIndices = direction.vertexIndices(); + Vector3d v0 = box.getPoint(vertexIndices.x()); + Vector3d v1 = box.getPoint(vertexIndices.y()); + Vector3d v2 = box.getPoint(vertexIndices.z()); + Vector3d v3 = box.getPoint(vertexIndices.w()); + double v = Math.max( + Intersectiond.intersectRayTriangleFront( + origin.x(), origin.y(), origin.z(), + dir.x(), dir.y(), dir.z(), + v0.x(), v0.y(), v0.z(), + v1.x(), v1.y(), v1.z(), + v2.x(), v2.y(), v2.z(), + epsilon + ), + Intersectiond.intersectRayTriangleFront( + origin.x(), origin.y(), origin.z(), + dir.x(), dir.y(), dir.z(), + v2.x(), v2.y(), v2.z(), + v3.x(), v3.y(), v3.z(), + v0.x(), v0.y(), v0.z(), + epsilon + ) + ); + if (v == -1.0) { + return RayCastFace.MISSED; + } + return new RayCastFace(false, v); + } + + @Override + public HitResult rayCast(Vector3d origin, Vector3d dir) { + Intersectiond.RayAab rayAab = Intersectiond.intersectRayAab( + origin.x(), origin.y(), origin.z(), + dir.x(), dir.y(), dir.z(), + box.minX(), box.minY(), box.minZ(), + box.maxX(), box.maxY(), box.maxZ() + ); + if (!rayAab.intersected()) { + return HitResult.MISSED; + } + RayCastFace rayCastFace = RayCastFace.MISSED; + Direction face = null; + for (Direction direction : Direction.LIST) { + RayCastFace face1 = rayCastFace(direction, origin, dir); + if (!face1.missed()) { + rayCastFace = face1; + face = direction; + } + } + if (rayCastFace.missed()) { + return HitResult.MISSED; + } + return new HitResult(false, rayCastFace.distance(), face); + } + + @Override + public List toBoxes() { + return list; + } + + @Override + public List toLines(List directions) { + List lines = new ArrayList<>(); + for (Direction direction : directions) { + Vector4i vertexIndices = direction.vertexIndices(); + Vector3d v0 = box.getPoint(vertexIndices.x()); + Vector3d v1 = box.getPoint(vertexIndices.y()); + Vector3d v2 = box.getPoint(vertexIndices.z()); + Vector3d v3 = box.getPoint(vertexIndices.w()); + lines.add(new Lined(v0, v1)); + lines.add(new Lined(v1, v2)); + lines.add(new Lined(v2, v3)); + lines.add(new Lined(v3, v0)); + } + return Collections.unmodifiableList(lines); + } +} diff --git a/modules/freeworld.core/src/main/java/freeworld/util/shape/VoxelShape.java b/modules/freeworld.core/src/main/java/freeworld/util/shape/VoxelShape.java new file mode 100644 index 0000000..b38db73 --- /dev/null +++ b/modules/freeworld.core/src/main/java/freeworld/util/shape/VoxelShape.java @@ -0,0 +1,39 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * only version 2.1 of the License. + */ + +package freeworld.util.shape; + +import freeworld.math.Vector3d; +import freeworld.util.Direction; +import freeworld.util.math.AABBox; +import freeworld.util.math.HitResult; +import freeworld.util.math.Lined; + +import java.util.List; + +/** + * @author squid233 + * @since 0.1.0 + */ +public interface VoxelShape { + static VoxelShape empty() { + return EmptyVoxelShape.INSTANCE; + } + + static VoxelShape fullCube() { + return SingleVoxelShape.FULL_CUBE; + } + + HitResult rayCast(Vector3d origin, Vector3d dir); + + List toBoxes(); + + List toLines(List directions); +} diff --git a/modules/freeworld.core/src/main/java/freeworld/world/World.java b/modules/freeworld.core/src/main/java/freeworld/world/World.java index ba301c7..b948d89 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/World.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/World.java @@ -10,13 +10,14 @@ package freeworld.world; -import freeworld.core.math.AABBox; +import freeworld.math.Vector3i; +import freeworld.util.math.AABBox; import freeworld.math.Vector3d; import freeworld.util.Int3Consumer; import freeworld.world.block.BlockType; import freeworld.world.block.BlockTypes; import freeworld.world.chunk.Chunk; -import freeworld.world.chunk.ChunkPos; +import freeworld.util.math.ChunkPos; import freeworld.world.entity.Entity; import freeworld.world.entity.EntityType; import freeworld.world.entity.EntityComponents; @@ -33,7 +34,7 @@ public final class World { public static final int TICKING_RADIUS = 5; public static final int TICKING_CHUNK_COUNT_CBRT = TICKING_RADIUS * 2 + 1; public static final int TICKING_CHUNK_COUNT = TICKING_CHUNK_COUNT_CBRT * TICKING_CHUNK_COUNT_CBRT * TICKING_CHUNK_COUNT_CBRT; - public final Map chunks = new ConcurrentHashMap<>(TICKING_CHUNK_COUNT); + public final Map chunks = new ConcurrentHashMap<>(TICKING_CHUNK_COUNT); private final List entities = new ArrayList<>(); private final MotionSystem motionSystem = new MotionSystem(); private final List listeners = new ArrayList<>(); @@ -76,7 +77,7 @@ public Entity createEntity(EntityType type, Vector3d position) { } public boolean isChunkLoaded(int x, int y, int z) { - return chunks.containsKey(new ChunkPos(x, y, z)); + return chunks.containsKey(new Vector3i(x, y, z)); } public boolean isBlockLoaded(int x, int y, int z) { @@ -89,7 +90,7 @@ public boolean isBlockLoaded(int x, int y, int z) { public Chunk getOrCreateChunk(int x, int y, int z) { return chunks.computeIfAbsent( - new ChunkPos(x, y, z), + new Vector3i(x, y, z), chunkPos -> { final Chunk chunk = new Chunk(this, chunkPos.x(), chunkPos.y(), chunkPos.z()); chunk.generateTerrain(); @@ -99,7 +100,7 @@ public Chunk getOrCreateChunk(int x, int y, int z) { } public Chunk getChunk(int x, int y, int z) { - return chunks.get(new ChunkPos(x, y, z)); + return chunks.get(new Vector3i(x, y, z)); } public Chunk getChunkByAbsolutePos(int x, int y, int z) { diff --git a/modules/freeworld.core/src/main/java/freeworld/world/block/AirBlockType.java b/modules/freeworld.core/src/main/java/freeworld/world/block/AirBlockType.java index 34150de..40e23ae 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/block/AirBlockType.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/block/AirBlockType.java @@ -10,7 +10,7 @@ package freeworld.world.block; -import freeworld.core.math.AABBox; +import freeworld.util.shape.VoxelShape; /** * @author squid233 @@ -22,12 +22,12 @@ public AirBlockType(Settings settings) { } @Override - public AABBox outlineShape() { - return AABBox.EMPTY; + public VoxelShape outlineShape() { + return VoxelShape.empty(); } @Override - public AABBox collisionShape() { - return AABBox.EMPTY; + public VoxelShape collisionShape() { + return VoxelShape.empty(); } } diff --git a/modules/freeworld.core/src/main/java/freeworld/world/block/BlockType.java b/modules/freeworld.core/src/main/java/freeworld/world/block/BlockType.java index 43c23cc..e22ff3e 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/block/BlockType.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/block/BlockType.java @@ -10,7 +10,7 @@ package freeworld.world.block; -import freeworld.core.math.AABBox; +import freeworld.util.shape.VoxelShape; /** * @author squid233 @@ -48,11 +48,11 @@ public boolean nonOpaque() { return nonOpaque; } - public AABBox outlineShape() { - return AABBox.FULL_CUBE; + public VoxelShape outlineShape() { + return VoxelShape.fullCube(); } - public AABBox collisionShape() { - return AABBox.FULL_CUBE; + public VoxelShape collisionShape() { + return VoxelShape.fullCube(); } } diff --git a/modules/freeworld.core/src/main/java/freeworld/world/chunk/Chunk.java b/modules/freeworld.core/src/main/java/freeworld/world/chunk/Chunk.java index 7012cac..dc23873 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/chunk/Chunk.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/chunk/Chunk.java @@ -10,6 +10,7 @@ package freeworld.world.chunk; +import freeworld.util.math.ChunkPos; import freeworld.util.math.SimplexNoiseUtil; import freeworld.world.World; import freeworld.world.block.BlockType; diff --git a/modules/freeworld.core/src/main/java/freeworld/world/component/ComponentKey.java b/modules/freeworld.core/src/main/java/freeworld/world/component/ComponentKey.java index 0779e13..549c7ce 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/component/ComponentKey.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/component/ComponentKey.java @@ -19,4 +19,11 @@ * @since 0.1.0 */ public record ComponentKey(Identifier identifier, Supplier defaultValue) { + public ComponentKey(Identifier identifier) { + this(identifier, (Supplier) null); + } + + public ComponentKey(Identifier identifier, T defaultValue) { + this(identifier, () -> defaultValue); + } } diff --git a/modules/freeworld.core/src/main/java/freeworld/world/entity/Entity.java b/modules/freeworld.core/src/main/java/freeworld/world/entity/Entity.java index 5319b8d..a21f074 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/entity/Entity.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/entity/Entity.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; +import java.util.function.Supplier; import java.util.function.UnaryOperator; /** @@ -46,7 +47,12 @@ public void addComponent(ComponentKey key, T component) { } public void addComponent(ComponentKey key) { - addComponent(key, key.defaultValue().get()); + Supplier defaultValue = key.defaultValue(); + if (defaultValue != null) { + addComponent(key, defaultValue.get()); + } else { + throw new IllegalStateException("No default value for component key " + key); + } } public void setComponent(ComponentKey key, T component) { diff --git a/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityComponents.java b/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityComponents.java index 5cd3b1b..62b2164 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityComponents.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityComponents.java @@ -11,7 +11,7 @@ package freeworld.world.entity; import freeworld.core.Identifier; -import freeworld.core.math.AABBox; +import freeworld.util.math.AABBox; import freeworld.math.Vector2d; import freeworld.math.Vector3d; import freeworld.world.component.ComponentKey; @@ -23,16 +23,14 @@ * @since 0.1.0 */ public final class EntityComponents { - private static final Supplier zeroVec3 = () -> Vector3d.ZERO; - private static final Supplier object = () -> Object.class; - public static final ComponentKey ACCELERATION = of("acceleration", zeroVec3); - public static final ComponentKey BOUNDING_BOX = of("bounding_box", () -> AABBox.EMPTY); - public static final ComponentKey EYE_POSITION = of("eye_position", () -> new Vector3d(0.0, 0.5, 0.0)); - public static final ComponentKey FLYING = of("flying", object); - public static final ComponentKey ON_GROUND = of("on_ground", object); - public static final ComponentKey POSITION = of("position", zeroVec3); - public static final ComponentKey ROTATION = of("rotation", () -> Vector2d.ZERO); - public static final ComponentKey VELOCITY = of("velocity", zeroVec3); + public static final ComponentKey ACCELERATION = of("acceleration", Vector3d.ZERO); + public static final ComponentKey BOUNDING_BOX = of("bounding_box"); + public static final ComponentKey EYE_POSITION = of("eye_position", new Vector3d(0.0, 0.5, 0.0)); + public static final ComponentKey FLYING = of("flying", Object.class); + public static final ComponentKey ON_GROUND = of("on_ground", Object.class); + public static final ComponentKey POSITION = of("position", Vector3d.ZERO); + public static final ComponentKey ROTATION = of("rotation", Vector2d.ZERO); + public static final ComponentKey VELOCITY = of("velocity", Vector3d.ZERO); private EntityComponents() { } @@ -40,4 +38,12 @@ private EntityComponents() { private static ComponentKey of(String name, Supplier defaultValue) { return new ComponentKey<>(Identifier.ofBuiltin(name), defaultValue); } + + private static ComponentKey of(String name) { + return new ComponentKey<>(Identifier.ofBuiltin(name)); + } + + private static ComponentKey of(String name, T defaultValue) { + return new ComponentKey<>(Identifier.ofBuiltin(name), defaultValue); + } } diff --git a/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityType.java b/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityType.java index 1e0bfbb..e1b8358 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityType.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/entity/EntityType.java @@ -4,13 +4,13 @@ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. + * License as published by the Free Software Foundation; + * only version 2.1 of the License. */ package freeworld.world.entity; -import freeworld.core.math.AABBox; +import freeworld.util.math.AABBox; import freeworld.math.Vector3d; import freeworld.world.World; diff --git a/modules/freeworld.core/src/main/java/freeworld/world/entity/system/MotionSystem.java b/modules/freeworld.core/src/main/java/freeworld/world/entity/system/MotionSystem.java index 01e3e9a..ad20a43 100644 --- a/modules/freeworld.core/src/main/java/freeworld/world/entity/system/MotionSystem.java +++ b/modules/freeworld.core/src/main/java/freeworld/world/entity/system/MotionSystem.java @@ -10,11 +10,11 @@ package freeworld.world.entity.system; -import freeworld.core.math.AABBox; +import freeworld.util.math.AABBox; import freeworld.math.Vector3d; import freeworld.world.World; import freeworld.world.block.BlockType; -import freeworld.world.chunk.ChunkPos; +import freeworld.util.math.ChunkPos; import freeworld.world.entity.Entity; import freeworld.world.entity.EntityComponents; @@ -74,8 +74,9 @@ public void process(World world, List entities) { if (blockType.air()) { continue; } - final AABBox box = blockType.collisionShape().move(x, y, z); - boxes.add(box); + for (AABBox box : blockType.collisionShape().toBoxes()) { + boxes.add(box.move(x, y, z)); + } } } } diff --git a/modules/freeworld.core/src/main/java/module-info.java b/modules/freeworld.core/src/main/java/module-info.java index f47d90d..ad7653c 100644 --- a/modules/freeworld.core/src/main/java/module-info.java +++ b/modules/freeworld.core/src/main/java/module-info.java @@ -16,11 +16,11 @@ */ module freeworld.core { exports freeworld.core; - exports freeworld.core.math; exports freeworld.core.registry; exports freeworld.util; exports freeworld.util.file; exports freeworld.util.math; + exports freeworld.util.shape; exports freeworld.world; exports freeworld.world.block; exports freeworld.world.chunk; diff --git a/modules/freeworld.math/src/main/java/freeworld/math/Vector3d.java b/modules/freeworld.math/src/main/java/freeworld/math/Vector3d.java index 8129418..502806a 100644 --- a/modules/freeworld.math/src/main/java/freeworld/math/Vector3d.java +++ b/modules/freeworld.math/src/main/java/freeworld/math/Vector3d.java @@ -52,4 +52,8 @@ public Vector3d withY(double y) { public Vector3d withZ(double z) { return new Vector3d(x(), y(), z); } + + public Vector3f toVector3f() { + return new Vector3f((float) x, (float) y, (float) z); + } } diff --git a/modules/freeworld.math/src/main/java/freeworld/math/Vector3f.java b/modules/freeworld.math/src/main/java/freeworld/math/Vector3f.java index 38f3183..2b3f204 100644 --- a/modules/freeworld.math/src/main/java/freeworld/math/Vector3f.java +++ b/modules/freeworld.math/src/main/java/freeworld/math/Vector3f.java @@ -24,4 +24,12 @@ public Vector3f(float d) { public Vector3f add(float x, float y, float z) { return new Vector3f(x() + x, y() + y, z() + z); } + + public Vector3f sub(float x, float y, float z) { + return new Vector3f(x() - x, y() - y, z() - z); + } + + public Vector3d toVector3d() { + return new Vector3d(x, y, z); + } } diff --git a/modules/freeworld.math/src/main/java/freeworld/math/Vector3i.java b/modules/freeworld.math/src/main/java/freeworld/math/Vector3i.java new file mode 100644 index 0000000..818118c --- /dev/null +++ b/modules/freeworld.math/src/main/java/freeworld/math/Vector3i.java @@ -0,0 +1,31 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * only version 2.1 of the License. + */ + +package freeworld.math; + +/** + * @author squid233 + * @since 0.1.0 + */ +public record Vector3i(int x, int y, int z) { + public static final Vector3i ZERO = new Vector3i(0); + + public Vector3i(int d) { + this(d, d, d); + } + + public Vector3i add(Vector3i v) { + return new Vector3i(x + v.x, y + v.y, z + v.z); + } + + public Vector3i mul(int i) { + return new Vector3i(x * i, y * i, z * i); + } +} diff --git a/modules/freeworld.math/src/main/java/freeworld/math/Vector4i.java b/modules/freeworld.math/src/main/java/freeworld/math/Vector4i.java new file mode 100644 index 0000000..056dbd2 --- /dev/null +++ b/modules/freeworld.math/src/main/java/freeworld/math/Vector4i.java @@ -0,0 +1,23 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * only version 2.1 of the License. + */ + +package freeworld.math; + +/** + * @author squid233 + * @since 0.1.0 + */ +public record Vector4i(int x, int y, int z, int w) { + public static final Vector4i ZERO = new Vector4i(0); + + public Vector4i(int d) { + this(d, d, d, d); + } +}