diff --git a/client/pom.xml b/client/pom.xml index 9201018..f76ad7a 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -212,4 +212,31 @@ + + + + + maven-assembly-plugin + + + + nl.andrewl.aos2_client.Aos2Client + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + \ No newline at end of file diff --git a/client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java b/client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java index d8e0869..88f11d9 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java @@ -1,5 +1,7 @@ package nl.andrewl.aos2_client; +import nl.andrewl.aos2_client.render.ChunkMesh; +import nl.andrewl.aos2_client.render.ChunkRenderer; import nl.andrewl.aos_core.model.Chunk; import org.joml.Vector3i; import org.lwjgl.Version; @@ -19,29 +21,28 @@ public class Aos2Client { long windowHandle = initUI(); Camera cam = new Camera(); + cam.setOrientationDegrees(90, 90); + cam.setPosition(-3, 3, 0); glfwSetCursorPosCallback(windowHandle, cam); Chunk chunk = Chunk.random(new Vector3i(0, 0, 0), new Random(1)); - for (int i = 0; i < 16; i++) { - chunk.setBlockAt(i, 0, 0, (byte) 8); - chunk.setBlockAt(0, i, 0, (byte) 40); - chunk.setBlockAt(0, 0, i, (byte) 120); - } -// chunk.setBlockAt(0, 15, 0, (byte) 0); -// chunk.setBlockAt(1, 15, 0, (byte) 0); -// chunk.setBlockAt(2, 15, 0, (byte) 0); -// chunk.setBlockAt(2, 15, 1, (byte) 0); -// chunk.setBlockAt(0, 0, 0, (byte) 0); Chunk chunk2 = Chunk.random(new Vector3i(1, 0, 0), new Random(1)); Chunk chunk3 = Chunk.random(new Vector3i(1, 0, 1), new Random(1)); Chunk chunk4 = Chunk.random(new Vector3i(0, 0, 1), new Random(1)); - ChunkRenderer chunkRenderer = new ChunkRenderer(); +// chunk.setBlockAt(0, 0, 0, (byte) 0); + for (int x = 0; x < Chunk.SIZE; x++) { + for (int z = 0; z < Chunk.SIZE; z++) { + chunk.setBlockAt(x, Chunk.SIZE - 1, z, (byte) 0); + } + } + + ChunkRenderer chunkRenderer = new ChunkRenderer(); + chunkRenderer.addChunkMesh(new ChunkMesh(chunk)); chunkRenderer.addChunkMesh(new ChunkMesh(chunk2)); chunkRenderer.addChunkMesh(new ChunkMesh(chunk3)); chunkRenderer.addChunkMesh(new ChunkMesh(chunk4)); - chunkRenderer.addChunkMesh(new ChunkMesh(chunk)); while (!glfwWindowShouldClose(windowHandle)) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -94,7 +95,7 @@ public class Aos2Client { glfwShowWindow(windowHandle); GL.createCapabilities(); - GLUtil.setupDebugMessageCallback(System.out); +// GLUtil.setupDebugMessageCallback(System.out); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); diff --git a/client/src/main/java/nl/andrewl/aos2_client/Camera.java b/client/src/main/java/nl/andrewl/aos2_client/Camera.java index 4a6c90d..fb936d0 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Camera.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Camera.java @@ -8,6 +8,9 @@ import org.lwjgl.glfw.GLFWCursorPosCallbackI; import static org.lwjgl.glfw.GLFW.glfwGetCursorPos; +/** + * Represents the player camera in the game world. + */ public class Camera implements GLFWCursorPosCallbackI { public static final Vector3f UP = new Vector3f(0, 1, 0); public static final Vector3f DOWN = new Vector3f(0, -1, 0); @@ -16,7 +19,23 @@ public class Camera implements GLFWCursorPosCallbackI { public static final Vector3f FORWARD = new Vector3f(0, 0, -1); public static final Vector3f BACKWARD = new Vector3f(0, 0, 1); + /** + * The x, y, and z position of the camera in the world. + */ private final Vector3f position; + + /** + * The camera's angular orientation. X refers to the rotation about the + * vertical axis, while Y refers to the rotation about the horizontal axis. + *

+ * The Y axis orientation is limited to between 0 and PI, with 0 + * being looking straight down, and PI looking straight up. + *

+ *

+ * The X axis orientation is limited to between 0 and 2 PI, with 0 + * being looking at the - Z axis. + *

+ */ private final Vector2f orientation; private final Matrix4f viewTransform; private final float[] viewTransformData = new float[16]; @@ -50,7 +69,10 @@ public class Camera implements GLFWCursorPosCallbackI { } public void setOrientation(float x, float y) { - orientation.set(MathUtils.normalize(x, 0, Math.PI * 2), MathUtils.normalize(y, 0, Math.PI * 2)); + orientation.set( + MathUtils.normalize(x, 0, Math.PI * 2), + MathUtils.clamp(y, 0, (float) (Math.PI)) + ); updateViewTransform(); } @@ -60,7 +82,7 @@ public class Camera implements GLFWCursorPosCallbackI { private void updateViewTransform() { viewTransform.identity(); - viewTransform.rotate(-orientation.y, RIGHT); + viewTransform.rotate(-orientation.y + ((float) Math.PI / 2), RIGHT); viewTransform.rotate(-orientation.x, UP); viewTransform.translate(-position.x, -position.y, -position.z); viewTransform.get(viewTransformData); diff --git a/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java b/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java deleted file mode 100644 index e14f44f..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java +++ /dev/null @@ -1,176 +0,0 @@ -package nl.andrewl.aos2_client; - -import nl.andrewl.aos_core.Pair; -import nl.andrewl.aos_core.model.Chunk; -import org.joml.Vector3f; -import org.joml.Vector3i; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import static org.lwjgl.opengl.GL46.*; - -/** - * Represents a 3d mesh for a chunk. - */ -public class ChunkMesh { - private final int vboId; - private final int vaoId; - private final int eboId; - - private int indiciesCount; - - private final int[] positionData; - - public ChunkMesh(Chunk chunk) { - this.positionData = new int[]{chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z}; - this.vboId = glGenBuffers(); - this.vaoId = glGenVertexArrays(); - this.eboId = glGenBuffers(); - - long start = System.currentTimeMillis(); - var meshData = generateMesh(chunk); - long dur = System.currentTimeMillis() - start; - System.out.println("Generated chunk mesh in " + dur + " ms"); - this.indiciesCount = meshData.second().length; - - glBindBuffer(GL_ARRAY_BUFFER, vboId); - glBufferData(GL_ARRAY_BUFFER, meshData.first(), GL_STATIC_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, meshData.second(), GL_STATIC_DRAW); - - glBindVertexArray(vaoId); - // Vertex position floats. - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, false, 9 * Float.BYTES, 0); - // Vertex color floats. - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, false, 9 * Float.BYTES, 3 * Float.BYTES); - // Vertex normal floats. - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 3, GL_FLOAT, false, 9 * Float.BYTES, 6 * Float.BYTES); - } - - private Pair generateMesh(Chunk c) { - List vertexList = new ArrayList<>(); - List indexList = new ArrayList<>(); - int idx = 0; - for (int x = 0; x < Chunk.SIZE; x++) { - for (int y = 0; y < Chunk.SIZE; y++) { - for (int z = 0; z < Chunk.SIZE; z++) { - byte block = c.getBlockAt(x, y, z); - if (block == 0) continue; - Vector3f color = Chunk.getColor(block); - var A = new Vector3f(x, y + 1, z); - var B = new Vector3f(x, y + 1, z + 1); - var C = new Vector3f(x + 1, y + 1, z + 1); - var D = new Vector3f(x + 1, y + 1, z); - var E = new Vector3f(x, y, z); - var F = new Vector3f(x, y, z + 1); - var G = new Vector3f(x + 1, y, z + 1); - var H = new Vector3f(x + 1, y, z); - - // Top - if (c.getBlockAt(x, y + 1, z) == 0) { - var norm = new Vector3f(0, 1, 0); - vertexList.addAll(Stream.of(A, B, C, D).map(v -> new BlockVertexData(v, color, norm)).toList()); - indexList.addAll(List.of( - idx, idx + 1, idx + 3, - idx + 3, idx + 1, idx + 2 - )); - idx += 4; - } - // Bottom - if (c.getBlockAt(x, y - 1, z) == 0) { - var norm = new Vector3f(0, -1, 0); - vertexList.addAll(Stream.of(E, F, G, H).map(v -> new BlockVertexData(v, color, norm)).toList()); - indexList.addAll(List.of( - idx + 3, idx + 1, idx, - idx + 1, idx + 3, idx + 2 - )); - idx += 4; - } - // Positive z - if (c.getBlockAt(x, y, z + 1) == 0) { - var norm = new Vector3f(0, 0, 1); - vertexList.addAll(Stream.of(B, F, G, C).map(v -> new BlockVertexData(v, color, norm)).toList()); - indexList.addAll(List.of( - idx + 3, idx, idx + 1, - idx + 3, idx + 1, idx + 2 - )); - idx += 4; - } - // Negative z - if (c.getBlockAt(x, y, z - 1) == 0) { - var norm = new Vector3f(0, 0, -1); - vertexList.addAll(Stream.of(A, E, H, D).map(v -> new BlockVertexData(v, color, norm)).toList()); - indexList.addAll(List.of( - idx, idx + 3, idx + 2, - idx + 2, idx + 1, idx - )); - idx += 4; - } - // Positive x - if (c.getBlockAt(x + 1, y, z) == 0) { - var norm = new Vector3f(1, 0, 0); - vertexList.addAll(Stream.of(C, G, H, D).map(v -> new BlockVertexData(v, color, norm)).toList()); - indexList.addAll(List.of( - idx + 3, idx, idx + 1, - idx + 3, idx + 1, idx + 2 - )); - idx += 4; - } - // Negative x - if (c.getBlockAt(x - 1, y, z) == 0) { - var norm = new Vector3f(-1, 0, 0); - vertexList.addAll(Stream.of(A, E, F, B).map(v -> new BlockVertexData(v, color, norm)).toList()); - indexList.addAll(List.of( - idx + 3, idx, idx + 1, - idx + 3, idx + 1, idx + 2 - )); - idx += 4; - } - } - } - } - - float[] vertexData = new float[9 * vertexList.size()]; - int vertexDataIdx = 0; - for (var vertex : vertexList) { - vertexData[vertexDataIdx++] = vertex.position().x; - vertexData[vertexDataIdx++] = vertex.position().y; - vertexData[vertexDataIdx++] = vertex.position().z; - vertexData[vertexDataIdx++] = vertex.color().x; - vertexData[vertexDataIdx++] = vertex.color().y; - vertexData[vertexDataIdx++] = vertex.color().z; - vertexData[vertexDataIdx++] = vertex.normal().x; - vertexData[vertexDataIdx++] = vertex.normal().y; - vertexData[vertexDataIdx++] = vertex.normal().z; - } - int[] indexData = indexList.stream().mapToInt(v -> v).toArray(); - System.out.printf("Generated chunk mesh: %d vertices, %d indexes%n", vertexList.size(), indexData.length); - - return new Pair<>(vertexData, indexData); - } - - public int[] getPositionData() { - return positionData; - } - - public void draw() { - // Bind elements. - glBindBuffer(GL_ARRAY_BUFFER, vboId); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); - glBindVertexArray(vaoId); - - glDrawElements(GL_TRIANGLES, indiciesCount, GL_UNSIGNED_INT, 0); - } - - public void free() { - glDeleteBuffers(vboId); - glDeleteBuffers(eboId); - glDeleteVertexArrays(vaoId); - } -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/BlockVertexData.java b/client/src/main/java/nl/andrewl/aos2_client/render/BlockVertexData.java similarity index 75% rename from client/src/main/java/nl/andrewl/aos2_client/BlockVertexData.java rename to client/src/main/java/nl/andrewl/aos2_client/render/BlockVertexData.java index 7fa974c..a7fcb0e 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/BlockVertexData.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/BlockVertexData.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos2_client; +package nl.andrewl.aos2_client.render; import org.joml.Vector3f; diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/ChunkMesh.java b/client/src/main/java/nl/andrewl/aos2_client/render/ChunkMesh.java new file mode 100644 index 0000000..9c1413a --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/render/ChunkMesh.java @@ -0,0 +1,92 @@ +package nl.andrewl.aos2_client.render; + +import nl.andrewl.aos_core.model.Chunk; + +import java.util.Arrays; + +import static org.lwjgl.opengl.GL46.*; + +/** + * Represents a 3d mesh for a chunk. + */ +public class ChunkMesh { + private final int vboId; + private final int vaoId; + private final int eboId; + + private int indiciesCount; + + private final int[] positionData; + private final Chunk chunk; + + public ChunkMesh(Chunk chunk) { + this.chunk = chunk; + this.positionData = new int[]{chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z}; + + this.vboId = glGenBuffers(); + this.eboId = glGenBuffers(); + this.vaoId = glGenVertexArrays(); + + loadMesh(); + + initVertexArrayAttributes(); + } + + public int[] getPositionData() { + return positionData; + } + + private void loadMesh() { + long start = System.currentTimeMillis(); + var meshData = ChunkMeshGenerator.generateMesh(chunk); + long dur = System.currentTimeMillis() - start; + System.out.printf( + "Generated chunk mesh in %d ms with %d vertices and %d indices, and %d faces. Vertex data size: %d%n", + dur, + meshData.vertexData().limit() / 9, + meshData.indices().limit(), + meshData.indices().limit() / 4, + meshData.vertexData().limit() + ); + this.indiciesCount = meshData.indices().limit(); + int[] data = new int[indiciesCount]; + meshData.indices().get(data); + meshData.indices().flip(); + System.out.println(Arrays.toString(data)); + + glBindBuffer(GL_ARRAY_BUFFER, vboId); + glBufferData(GL_ARRAY_BUFFER, meshData.vertexData(), GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, meshData.indices(), GL_STATIC_DRAW); + + int size = glGetBufferParameteri(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE); + System.out.println(size); + } + + private void initVertexArrayAttributes() { + glBindVertexArray(vaoId); + // Vertex position floats. + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 9 * Float.BYTES, 0); + // Vertex color floats. + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, false, 9 * Float.BYTES, 3 * Float.BYTES); + // Vertex normal floats. + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 3, GL_FLOAT, false, 9 * Float.BYTES, 6 * Float.BYTES); + } + + public void draw() { + glBindBuffer(GL_ARRAY_BUFFER, vboId); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); + glBindVertexArray(vaoId); + glDrawElements(GL_TRIANGLES, indiciesCount, GL_UNSIGNED_INT, 0); + } + + public void free() { + glDeleteBuffers(vboId); + glDeleteBuffers(eboId); + glDeleteVertexArrays(vaoId); + } +} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/ChunkMeshData.java b/client/src/main/java/nl/andrewl/aos2_client/render/ChunkMeshData.java new file mode 100644 index 0000000..13f5510 --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/render/ChunkMeshData.java @@ -0,0 +1,9 @@ +package nl.andrewl.aos2_client.render; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +public record ChunkMeshData( + FloatBuffer vertexData, + IntBuffer indices +) {} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/ChunkMeshGenerator.java b/client/src/main/java/nl/andrewl/aos2_client/render/ChunkMeshGenerator.java new file mode 100644 index 0000000..67e750d --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/render/ChunkMeshGenerator.java @@ -0,0 +1,103 @@ +package nl.andrewl.aos2_client.render; + +import nl.andrewl.aos_core.model.Chunk; +import org.joml.Vector3f; +import org.lwjgl.BufferUtils; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.List; + + +public final class ChunkMeshGenerator { + private ChunkMeshGenerator() {} + + public static ChunkMeshData generateMesh(Chunk chunk) { + FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(20000); + IntBuffer indexBuffer = BufferUtils.createIntBuffer(5000); + int idx = 0; + for (int i = 0; i < Chunk.TOTAL_SIZE; i++) { + var pos = Chunk.idxToXyz(i); + int x = pos.x; + int y = pos.y; + int z = pos.z; + byte block = chunk.getBlocks()[i]; + if (block <= 0) { + continue; // Don't render empty blocks. + } + + Vector3f color = Chunk.getColor(block); + + // See /design/block_rendering.svg for a diagram of how these vertices are defined. + var a = new Vector3f(x, y + 1, z + 1); + var b = new Vector3f(x, y + 1, z); + var c = new Vector3f(x, y, z); + var d = new Vector3f(x, y, z + 1); + var e = new Vector3f(x + 1, y + 1, z); + var f = new Vector3f(x + 1, y + 1, z + 1); + var g = new Vector3f(x + 1, y, z + 1); + var h = new Vector3f(x + 1, y, z); + + // Top + if (chunk.getBlockAt(x, y + 1, z) == 0) { + var norm = new Vector3f(0, 1, 0); + genFace(vertexBuffer, indexBuffer, idx, color, norm, List.of(a, f, e, b)); + idx += 4; + } + // Bottom + if (chunk.getBlockAt(x, y - 1, z) == 0) { + var norm = new Vector3f(0, -1, 0); + genFace(vertexBuffer, indexBuffer, idx, color, norm, List.of(c, h, g, d)); + idx += 4; + } + // Positive z + if (chunk.getBlockAt(x, y, z + 1) == 0) { + var norm = new Vector3f(0, 0, 1); + genFace(vertexBuffer, indexBuffer, idx, color, norm, List.of(f, a, d, g)); + idx += 4; + } + // Negative z + if (chunk.getBlockAt(x, y, z - 1) == 0) { + var norm = new Vector3f(0, 0, -1); + genFace(vertexBuffer, indexBuffer, idx, color, norm, List.of(b, e, h, c)); + idx += 4; + } + // Positive x + if (chunk.getBlockAt(x + 1, y, z) == 0) { + var norm = new Vector3f(1, 0, 0); + genFace(vertexBuffer, indexBuffer, idx, color, norm, List.of(e, f, g, h)); + idx += 4; + } + // Negative x + if (chunk.getBlockAt(x - 1, y, z) == 0) { + var norm = new Vector3f(-1, 0, 0); + genFace(vertexBuffer, indexBuffer, idx, color, norm, List.of(a, b, c, d)); + idx += 4; + } + } + + return new ChunkMeshData(vertexBuffer.flip(), indexBuffer.flip()); + } + + private static void genFace(FloatBuffer vertexBuffer, IntBuffer indexBuffer, int currentIndex, Vector3f color, Vector3f norm, List vertices) { + for (var vertex : vertices) { + vertexBuffer.put(vertex.x); + vertexBuffer.put(vertex.y); + vertexBuffer.put(vertex.z); + vertexBuffer.put(color.x); + vertexBuffer.put(color.y); + vertexBuffer.put(color.z); + vertexBuffer.put(norm.x); + vertexBuffer.put(norm.y); + vertexBuffer.put(norm.z); + } + // Top-left triangle. + indexBuffer.put(currentIndex); + indexBuffer.put(currentIndex + 1); + indexBuffer.put(currentIndex + 2); + // Bottom-right triangle. + indexBuffer.put(currentIndex + 2); + indexBuffer.put(currentIndex + 3); + indexBuffer.put(currentIndex); + } +} diff --git a/client/src/main/java/nl/andrewl/aos2_client/ChunkRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/ChunkRenderer.java similarity index 83% rename from client/src/main/java/nl/andrewl/aos2_client/ChunkRenderer.java rename to client/src/main/java/nl/andrewl/aos2_client/render/ChunkRenderer.java index 5c96f41..db89aaa 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/ChunkRenderer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/ChunkRenderer.java @@ -1,5 +1,7 @@ -package nl.andrewl.aos2_client; +package nl.andrewl.aos2_client.render; +import nl.andrewl.aos2_client.Camera; +import nl.andrewl.aos_core.model.Chunk; import org.joml.Matrix3f; import org.joml.Matrix4f; @@ -14,6 +16,7 @@ public class ChunkRenderer { private final int viewTransformUniform; private final int normalTransformUniform; private final int chunkPositionUniform; + private final int chunkSizeUniform; private final Matrix4f projectionTransform = new Matrix4f().perspective(70, 800 / 600.0f, 0.01f, 100.0f); @@ -22,16 +25,18 @@ public class ChunkRenderer { public ChunkRenderer() { this.shaderProgram = new ShaderProgram.Builder() .withShader("shader/chunk/vertex.glsl", GL_VERTEX_SHADER) - .withShader("shader/chunk/fragment.glsl", GL_FRAGMENT_SHADER) + .withShader("shader/chunk/normal_fragment.glsl", GL_FRAGMENT_SHADER) .build(); shaderProgram.use(); this.projectionTransformUniform = shaderProgram.getUniform("projectionTransform"); this.viewTransformUniform = shaderProgram.getUniform("viewTransform"); this.normalTransformUniform = shaderProgram.getUniform("normalTransform"); this.chunkPositionUniform = shaderProgram.getUniform("chunkPosition"); + this.chunkSizeUniform = shaderProgram.getUniform("chunkSize"); // Preemptively load projection transform, which doesn't change much. glUniformMatrix4fv(projectionTransformUniform, false, projectionTransform.get(new float[16])); + glUniform1i(chunkSizeUniform, Chunk.SIZE); } public void addChunkMesh(ChunkMesh mesh) { @@ -39,13 +44,9 @@ public class ChunkRenderer { } public void draw(Camera cam) { - glUniformMatrix4fv(viewTransformUniform, false, cam.getViewTransformData()); - Matrix3f normalTransform = new Matrix3f(); - glUniformMatrix3fv(normalTransformUniform, false, normalTransform.get(new float[9])); shaderProgram.use(); - + glUniformMatrix4fv(viewTransformUniform, false, cam.getViewTransformData()); for (var mesh : chunkMeshes) { - // For each chunk, specify its position so that the shaders can draw it offset. glUniform3iv(chunkPositionUniform, mesh.getPositionData()); mesh.draw(); } diff --git a/client/src/main/java/nl/andrewl/aos2_client/ShaderProgram.java b/client/src/main/java/nl/andrewl/aos2_client/render/ShaderProgram.java similarity index 74% rename from client/src/main/java/nl/andrewl/aos2_client/ShaderProgram.java rename to client/src/main/java/nl/andrewl/aos2_client/render/ShaderProgram.java index 2e02084..3eb1e63 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/ShaderProgram.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/ShaderProgram.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos2_client; +package nl.andrewl.aos2_client.render; import nl.andrewl.aos_core.FileUtils; @@ -10,12 +10,20 @@ import static org.lwjgl.opengl.GL46.*; * Represents a shader program with one or more individual shaders. */ public class ShaderProgram { + /** + * The id of the generated shader program. + */ private final int id; public ShaderProgram(int id) { this.id = id; } + /** + * Called to set this program as the one currently used by the OpenGL + * context. Call this before any other operation which uses the program, + * like {@link ShaderProgram#getUniform(String)} or drawing. + */ public void use() { glUseProgram(id); } @@ -53,5 +61,9 @@ public class ShaderProgram { glLinkProgram(id); return new ShaderProgram(id); } + + public void discard() { + glDeleteProgram(id); + } } } diff --git a/client/src/main/java/nl/andrewl/aos2_client/TextMesh.java b/client/src/main/java/nl/andrewl/aos2_client/render/TextMesh.java similarity index 97% rename from client/src/main/java/nl/andrewl/aos2_client/TextMesh.java rename to client/src/main/java/nl/andrewl/aos2_client/render/TextMesh.java index d61d870..9b60716 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/TextMesh.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/TextMesh.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos2_client; +package nl.andrewl.aos2_client.render; import org.joml.Vector2f; import org.lwjgl.BufferUtils; diff --git a/client/src/main/resources/shader/chunk/fragment.glsl b/client/src/main/resources/shader/chunk/fragment.glsl index 4886647..497c2a2 100644 --- a/client/src/main/resources/shader/chunk/fragment.glsl +++ b/client/src/main/resources/shader/chunk/fragment.glsl @@ -15,5 +15,4 @@ void main() { // No specular component. fragmentColor = vec4((ambientComponent + diffuseComponent) * vertexColor, 1.0); - //fragmentColor = vec4((vertexNormal + 1) / 2.0, 1.0); } \ No newline at end of file diff --git a/client/src/main/resources/shader/chunk/normal_fragment.glsl b/client/src/main/resources/shader/chunk/normal_fragment.glsl new file mode 100644 index 0000000..d637fdf --- /dev/null +++ b/client/src/main/resources/shader/chunk/normal_fragment.glsl @@ -0,0 +1,11 @@ +#version 460 core + +in vec3 vertexPosition; +in vec3 vertexColor; +in vec3 vertexNormal; + +out vec4 fragmentColor; + +void main() { + fragmentColor = vec4((vertexNormal + 1) / 2.0, 1.0); +} \ No newline at end of file diff --git a/client/src/main/resources/shader/chunk/vertex.glsl b/client/src/main/resources/shader/chunk/vertex.glsl index 0aa5b5d..1618ab4 100644 --- a/client/src/main/resources/shader/chunk/vertex.glsl +++ b/client/src/main/resources/shader/chunk/vertex.glsl @@ -6,18 +6,17 @@ layout (location = 2) in vec3 vertexNormalIn; uniform mat4 projectionTransform; uniform mat4 viewTransform; -uniform mat3 normalTransform; uniform ivec3 chunkPosition; +uniform int chunkSize; out vec3 vertexPosition; out vec3 vertexColor; out vec3 vertexNormal; void main() { - vec3 realVertexPosition = vertexPositionIn + (chunkPosition * 16); + vertexPosition = vertexPositionIn + (chunkPosition * chunkSize); - gl_Position = projectionTransform * viewTransform * vec4(realVertexPosition, 1.0); - vertexPosition = realVertexPosition; + gl_Position = projectionTransform * viewTransform * vec4(vertexPosition, 1.0); vertexColor = vertexColorIn; - vertexNormal = normalize(normalTransform * vertexNormalIn); + vertexNormal = vertexNormalIn; } diff --git a/core/pom.xml b/core/pom.xml index 9d8f294..6df9073 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -23,5 +23,20 @@ joml 1.10.4 + + + + org.junit.jupiter + junit-jupiter-api + 5.8.2 + test + + + + org.junit.jupiter + junit-jupiter-engine + 5.8.2 + test + \ No newline at end of file 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 1814053..3d86851 100644 --- a/core/src/main/java/nl/andrewl/aos_core/MathUtils.java +++ b/core/src/main/java/nl/andrewl/aos_core/MathUtils.java @@ -6,4 +6,15 @@ public class MathUtils { final double offsetValue = value - start; return offsetValue - (Math.floor(offsetValue / width) * width) + start; } + + public static float normalize(float value, float start, float end) { + final float width = end - start; + final float offsetValue = value - start; + return offsetValue - ((float) Math.floor(offsetValue / width) * width) + start; + } + + public static float clamp(float value, float min, float max) { + if (value < min) return min; + return Math.min(value, max); + } } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/Chunk.java b/core/src/main/java/nl/andrewl/aos_core/model/Chunk.java index 3773fe4..cbdee1a 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/Chunk.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/Chunk.java @@ -12,7 +12,7 @@ public class Chunk { /** * The size of a chunk, in terms of the number of blocks on one axis of the cube. */ - public static final int SIZE = 16; + public static final int SIZE = 4; public static final int TOTAL_SIZE = SIZE * SIZE * SIZE; private final byte[] blocks = new byte[TOTAL_SIZE]; @@ -35,9 +35,37 @@ public class Chunk { return position; } + /** + * Converts the given 3D coordinate to a 1D index which points to the block + * with that coordinate within the chunk. + * @param x The x coordinate. + * @param y The y coordinate. + * @param z The z coordinate. + * @return The 1D index, or -1 if out of bounds. + */ + public static int xyzToIdx(int x, int y, int z) { + if (x < 0 || x >= SIZE || y < 0 || y >= SIZE || z < 0 || z >= SIZE) return -1; + return x * SIZE * SIZE + y * SIZE + z; + } + + /** + * Converts the given 1D index to a 3D coordinate that points to the block + * with that index. + * @param idx The index. + * @return The 3D coordinate, or -1, -1, -1 if the index is out of bounds. + */ + public static Vector3i idxToXyz(int idx) { + if (idx < 0 || idx >= TOTAL_SIZE) return new Vector3i(-1, -1, -1); + int x = idx / (SIZE * SIZE); + int remainder = idx % (SIZE * SIZE); + int y = remainder / SIZE; + int z = remainder % SIZE; + return new Vector3i(x, y, z); + } + public byte getBlockAt(int x, int y, int z) { - if (x < 0 || x >= SIZE || y < 0 || y >= SIZE || z < 0 || z >= SIZE) return 0; - int idx = x * SIZE * SIZE + y * SIZE + z; + int idx = xyzToIdx(x, y, z); + if (idx < 0) return 0; return blocks[idx]; } @@ -46,8 +74,8 @@ public class Chunk { } public void setBlockAt(int x, int y, int z, byte value) { - if (x < 0 || x >= SIZE || y < 0 || y >= SIZE || z < 0 || z >= SIZE) return; - int idx = x * SIZE * SIZE + y * SIZE + z; + int idx = xyzToIdx(x, y, z); + if (idx < 0) return; blocks[idx] = value; } diff --git a/core/src/test/java/nl/andrewl/aos_core/model/ChunkTest.java b/core/src/test/java/nl/andrewl/aos_core/model/ChunkTest.java new file mode 100644 index 0000000..48fb21f --- /dev/null +++ b/core/src/test/java/nl/andrewl/aos_core/model/ChunkTest.java @@ -0,0 +1,55 @@ +package nl.andrewl.aos_core.model; + +import org.joml.Vector3i; +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +public class ChunkTest { + @Test + public void testCoordinateIndexConversion() { + assertEquals(0, Chunk.xyzToIdx(0, 0, 0)); + assertEquals(-1, Chunk.xyzToIdx(-1, 0, 0)); + assertEquals(-1, Chunk.xyzToIdx(Chunk.SIZE, 0, 0)); + assertEquals(1, Chunk.xyzToIdx(0, 0, 1)); + assertEquals(Chunk.SIZE, Chunk.xyzToIdx(0, 1, 0)); + assertEquals(Chunk.SIZE * Chunk.SIZE, Chunk.xyzToIdx(1, 0, 0)); + assertEquals(Chunk.SIZE * Chunk.SIZE + 1, Chunk.xyzToIdx(1, 0, 1)); + assertEquals(Chunk.TOTAL_SIZE - 1, Chunk.xyzToIdx(Chunk.SIZE - 1, Chunk.SIZE - 1, Chunk.SIZE - 1)); + 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 idx = x * Chunk.SIZE * Chunk.SIZE + y * Chunk.SIZE + z; + assertEquals(idx, Chunk.xyzToIdx(x, y, z)); + } + } + } + + assertEquals(new Vector3i(0, 0, 0), Chunk.idxToXyz(0)); + assertEquals(new Vector3i(0, 0, 2), Chunk.idxToXyz(2)); + assertEquals(new Vector3i(1, 1, 1), Chunk.idxToXyz(Chunk.SIZE * Chunk.SIZE + Chunk.SIZE + 1)); + assertEquals(new Vector3i(Chunk.SIZE - 1, Chunk.SIZE - 1, Chunk.SIZE - 1), Chunk.idxToXyz(Chunk.TOTAL_SIZE - 1)); + assertEquals(new Vector3i(-1, -1, -1), Chunk.idxToXyz(Chunk.TOTAL_SIZE)); + + 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 idx = x * Chunk.SIZE * Chunk.SIZE + y * Chunk.SIZE + z; + assertEquals(new Vector3i(x, y, z), Chunk.idxToXyz(idx)); + } + } + } + } + + @Test + public void testGetBlockAt() { + Chunk chunk = Chunk.random(new Vector3i(0, 0, 0), new Random(1)); + for (int i = 0; i < Chunk.TOTAL_SIZE; i++) { + assertTrue(chunk.getBlockAt(Chunk.idxToXyz(i)) > 0); + } + assertEquals(0, chunk.getBlockAt(-1, 0, 0)); + assertEquals(0, chunk.getBlockAt(16, 0, 5)); + } +} diff --git a/design/block_rendering.svg b/design/block_rendering.svg new file mode 100644 index 0000000..2a498a0 --- /dev/null +++ b/design/block_rendering.svg @@ -0,0 +1,267 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + +Y + -Y + + +Z + -Z + + -X + +X + a + b + c + d + e + f + g + h + Block rendering schema +