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 73726c1..3d63464 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java @@ -13,12 +13,13 @@ import org.lwjgl.opengl.GLUtil; import org.lwjgl.system.MemoryUtil; import java.io.IOException; +import java.util.Random; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL46.*; public class Aos2Client { - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws IOException, InterruptedException { System.out.println("LWJGL Version: " + Version.getVersion()); GLFWErrorCallback.createPrint(System.err).set(); if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW"); @@ -34,7 +35,7 @@ public class Aos2Client { } }); - glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); +// glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetInputMode(windowHandle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); glfwSetWindowPos(windowHandle, 50, 50); @@ -51,10 +52,15 @@ public class Aos2Client { glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); - Chunk chunk = Chunk.of((byte) 64); + Chunk chunk = Chunk.random(new Random(1)); + Camera cam = new Camera(); + cam.setPosition(0, 3, 0); + float angle = 0; + 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); Matrix4f projectionTransform = new Matrix4f().perspective(70, 800 / 600.0f, 0.01f, 100.0f); - Matrix4f viewTransform = new Matrix4f() - .lookAt(new Vector3f(-5, 50, -10), new Vector3f(8, 0, 8), new Vector3f(0, 1, 0)); ChunkMesh mesh = new ChunkMesh(chunk); int shaderProgram = createShaderProgram(); @@ -66,12 +72,15 @@ public class Aos2Client { while (!glfwWindowShouldClose(windowHandle)) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glUniformMatrix4fv(viewTransformUniform, false, viewTransform.get(new float[16])); + glUniformMatrix4fv(viewTransformUniform, false, cam.getViewTransformData()); mesh.draw(); glfwSwapBuffers(windowHandle); glfwPollEvents(); + angle += Math.PI / 48; + Thread.sleep(40); + cam.setOrientation((float) (Math.PI), 0); } Callbacks.glfwFreeCallbacks(windowHandle); diff --git a/client/src/main/java/nl/andrewl/aos2_client/BlockVertexData.java b/client/src/main/java/nl/andrewl/aos2_client/BlockVertexData.java new file mode 100644 index 0000000..7fa974c --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/BlockVertexData.java @@ -0,0 +1,9 @@ +package nl.andrewl.aos2_client; + +import org.joml.Vector3f; + +public record BlockVertexData( + Vector3f position, + Vector3f color, + Vector3f normal +) {} diff --git a/client/src/main/java/nl/andrewl/aos2_client/Camera.java b/client/src/main/java/nl/andrewl/aos2_client/Camera.java new file mode 100644 index 0000000..a52a5eb --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/Camera.java @@ -0,0 +1,41 @@ +package nl.andrewl.aos2_client; + +import nl.andrewl.aos_core.MathUtils; +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; + +public class Camera { + private final Vector3f position; + private final Vector2f orientation; + private final Matrix4f viewTransform; + private final float[] viewTransformData = new float[16]; + + public Camera() { + this.position = new Vector3f(); + this.orientation = new Vector2f(); + this.viewTransform = new Matrix4f(); + } + + public float[] getViewTransformData() { + return viewTransformData; + } + + public void setPosition(float x, float y, float z) { + position.set(x, y, z); + updateViewTransform(); + } + + public void setOrientation(float x, float y) { + orientation.set(MathUtils.normalize(x, 0, Math.PI * 2), MathUtils.normalize(y, 0, Math.PI * 2)); + updateViewTransform(); + } + + private void updateViewTransform() { + viewTransform.identity(); + viewTransform.rotate(-orientation.x, new Vector3f(1, 0, 0)); + viewTransform.rotate(-orientation.y, new Vector3f(0, 1, 0)); + 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 index 3b089df..73cddab 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java +++ b/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java @@ -1,10 +1,12 @@ package nl.andrewl.aos2_client; +import nl.andrewl.aos_core.Pair; import nl.andrewl.aos_core.model.Chunk; -import org.lwjgl.BufferUtils; +import org.joml.Vector3f; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; import static org.lwjgl.opengl.GL46.*; @@ -20,31 +22,130 @@ public class ChunkMesh { this.vaoId = glGenVertexArrays(); this.eboId = glGenBuffers(); - var meshData = chunk.generateMesh(); - System.out.println(meshData.first().size()); - FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(3 * meshData.first().size()); - for (var vertex : meshData.first()) { - vertexBuffer.put(vertex.x); - vertexBuffer.put(vertex.y); - vertexBuffer.put(vertex.z); - } - vertexBuffer.flip(); - IntBuffer indexBuffer = BufferUtils.createIntBuffer(meshData.second().size()); - for (var index : meshData.second()) { - indexBuffer.put(index); - } - indexBuffer.flip(); - this.indiciesCount = meshData.second().size(); + 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, vertexBuffer, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, meshData.first(), GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBuffer, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, meshData.second(), GL_STATIC_DRAW); glBindVertexArray(vaoId); + // Vertex position floats. glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * Float.BYTES, 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 void draw() { diff --git a/client/src/main/resources/shader/fragment.glsl b/client/src/main/resources/shader/fragment.glsl index c4577df..8341631 100644 --- a/client/src/main/resources/shader/fragment.glsl +++ b/client/src/main/resources/shader/fragment.glsl @@ -2,9 +2,18 @@ in vec3 vertexPosition; in vec3 vertexColor; +in vec3 vertexNormal; out vec4 fragmentColor; void main() { - fragmentColor = vec4(vertexColor, 1.0); + vec3 lightDirection = vec3(0.0, -1.0, -0.5);// TODO: Add this via a uniform. + vec3 lightColor = vec3(1.0, 1.0, 0.9); // TODO: Add this via a uniform. + + vec3 ambientComponent = vec3(0.1, 0.1, 0.1); + vec3 diffuseComponent = max(dot(vertexNormal, lightDirection), 0.0) * lightColor; + // 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/vertex.glsl b/client/src/main/resources/shader/vertex.glsl index 735f874..fbbfefe 100644 --- a/client/src/main/resources/shader/vertex.glsl +++ b/client/src/main/resources/shader/vertex.glsl @@ -1,15 +1,20 @@ #version 460 core layout (location = 0) in vec3 vertexPositionIn; +layout (location = 1) in vec3 vertexColorIn; +layout (location = 2) in vec3 vertexNormalIn; uniform mat4 projectionTransform; uniform mat4 viewTransform; out vec3 vertexPosition; out vec3 vertexColor; +out vec3 vertexNormal; void main() { + // TODO: Add model transform uniform. gl_Position = projectionTransform * viewTransform * vec4(vertexPositionIn, 1.0); vertexPosition = vertexPositionIn; - vertexColor = vec3(1.0, 0.5, 0.5); + vertexColor = vertexColorIn; + vertexNormal = vertexNormalIn; } diff --git a/core/src/main/java/nl/andrewl/aos_core/MathUtils.java b/core/src/main/java/nl/andrewl/aos_core/MathUtils.java new file mode 100644 index 0000000..1814053 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/MathUtils.java @@ -0,0 +1,9 @@ +package nl.andrewl.aos_core; + +public class MathUtils { + public static double normalize(double value, double start, double end) { + final double width = end - start; + final double offsetValue = value - start; + return offsetValue - (Math.floor(offsetValue / width) * width) + start; + } +} 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 b670fb4..77e8fec 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 @@ -1,87 +1,53 @@ package nl.andrewl.aos_core.model; -import nl.andrewl.aos_core.Pair; import org.joml.Vector3f; import org.joml.Vector3i; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; +import java.util.Random; public class Chunk { /** * The size of a chunk, in terms of the number of blocks on one axis of the cube. */ - public static final byte SIZE = 16; + public static final int SIZE = 16; + public static final int TOTAL_SIZE = SIZE * SIZE * SIZE; - private final byte[] blocks = new byte[SIZE * SIZE * SIZE]; + private final byte[] blocks = new byte[TOTAL_SIZE]; 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; - if (idx < 0 || idx >= SIZE * SIZE * SIZE) return 0; - return blocks[x * SIZE * SIZE + y * SIZE + z]; + return blocks[idx]; } public byte getBlockAt(Vector3i localPosition) { return getBlockAt(localPosition.x, localPosition.y, localPosition.z); } + 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; + blocks[idx] = value; + } + public byte[] getBlocks() { return blocks; } - public Pair, List> generateMesh() { - List vertexList = new ArrayList<>(); - List indexList = new ArrayList<>(); - int elementIdx = 0; - for (int x = 0; x < SIZE; x++) { - for (int y = 0; y < SIZE; y++) { - for (int z = 0; z < SIZE; z++) { - byte block = getBlockAt(x, y, z); - if (block == 0) continue; - // Top - if (getBlockAt(x, y + 1, z) == 0) { - vertexList.add(new Vector3f(x + 1, y, z)); // 0 - vertexList.add(new Vector3f(x, y, z)); // 1 - vertexList.add(new Vector3f(x, y, z + 1)); // 2 - vertexList.add(new Vector3f(x + 1, y, z + 1));// 3 - - indexList.add(elementIdx); - indexList.add(elementIdx + 1); - indexList.add(elementIdx + 2); - - indexList.add(elementIdx + 2); - indexList.add(elementIdx + 3); - indexList.add(elementIdx); - - elementIdx += 4; - } - // Bottom - if (getBlockAt(x, y - 1, z) == 0) { - vertexList.add(new Vector3f(x + 1, y - 1, z)); // 0 - vertexList.add(new Vector3f(x, y - 1, z)); // 1 - vertexList.add(new Vector3f(x, y - 1, z + 1)); // 2 - vertexList.add(new Vector3f(x + 1, y - 1, z + 1));// 3 - - indexList.add(elementIdx); - indexList.add(elementIdx + 2); - indexList.add(elementIdx + 1); - - indexList.add(elementIdx); - indexList.add(elementIdx + 3); - indexList.add(elementIdx + 2); - - elementIdx += 4; - } - // Positive z - // Negative z - // Positive x - // Negative x + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int y = 0; y < SIZE; y++) { + sb.append("y=").append(y).append('\n'); + for (int z = 0; z < SIZE; z++) { + for (int x = 0; x < SIZE; x++) { + sb.append(String.format("%02X ", getBlockAt(x, y, z))); } + sb.append('\n'); } } - - return new Pair<>(vertexList, indexList); + return sb.toString(); } public static Chunk of(byte value) { @@ -90,8 +56,16 @@ public class Chunk { return c; } + public static Chunk random(Random rand) { + Chunk c = new Chunk(); + for (int i = 0; i < TOTAL_SIZE; i++) { + c.blocks[i] = (byte) rand.nextInt(1, 128); + } + return c; + } + public static Vector3f getColor(byte blockValue) { - float v = blockValue / 128.0f; + float v = blockValue / 127.0f; return new Vector3f(v); } }