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 @@
+
+
+
+