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 b805193..53d2d27 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java @@ -1,11 +1,7 @@ package nl.andrewl.aos2_client; -import nl.andrewl.aos_core.FileUtils; import nl.andrewl.aos_core.model.Chunk; -import org.joml.Matrix3f; -import org.joml.Matrix4f; -import org.joml.Vector3f; -import org.lwjgl.BufferUtils; +import org.joml.Vector3i; import org.lwjgl.Version; import org.lwjgl.glfw.Callbacks; import org.lwjgl.glfw.GLFWErrorCallback; @@ -13,14 +9,59 @@ import org.lwjgl.opengl.GL; 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, InterruptedException { + public static void main(String[] args) { + long windowHandle = initUI(); + + Chunk chunk = Chunk.random(new Vector3i(0, 0, 0), new Random(1)); + Camera cam = new Camera(); + glfwSetCursorPosCallback(windowHandle, cam); + + 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); + + ChunkRenderer chunkRenderer = new ChunkRenderer(); + ChunkMesh mesh = new ChunkMesh(chunk); + chunkRenderer.addChunkMesh(mesh); + + while (!glfwWindowShouldClose(windowHandle)) { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + chunkRenderer.draw(cam); + + glfwSwapBuffers(windowHandle); + glfwPollEvents(); + + if (glfwGetKey(windowHandle, GLFW_KEY_W) == GLFW_PRESS) cam.move(Camera.FORWARD); + if (glfwGetKey(windowHandle, GLFW_KEY_S) == GLFW_PRESS) cam.move(Camera.BACKWARD); + if (glfwGetKey(windowHandle, GLFW_KEY_A) == GLFW_PRESS) cam.move(Camera.LEFT); + if (glfwGetKey(windowHandle, GLFW_KEY_D) == GLFW_PRESS) cam.move(Camera.RIGHT); + if (glfwGetKey(windowHandle, GLFW_KEY_SPACE) == GLFW_PRESS) cam.move(Camera.UP); + if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) cam.move(Camera.DOWN); + } + + chunkRenderer.free(); + + Callbacks.glfwFreeCallbacks(windowHandle); + glfwDestroyWindow(windowHandle); + glfwTerminate(); + glfwSetErrorCallback(null).free(); + } + + private static long initUI() { System.out.println("LWJGL Version: " + Version.getVersion()); GLFWErrorCallback.createPrint(System.err).set(); if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW"); @@ -53,77 +94,6 @@ public class Aos2Client { glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); - Chunk chunk = Chunk.random(new Random(1)); - Camera cam = new Camera(); - glfwSetCursorPosCallback(windowHandle, cam); - - 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); - Matrix4f projectionTransform = new Matrix4f().perspective(70, 800 / 600.0f, 0.01f, 100.0f); - ChunkMesh mesh = new ChunkMesh(chunk); - - int shaderProgram = createShaderProgram(); - int projectionTransformUniform = glGetUniformLocation(shaderProgram, "projectionTransform"); - int viewTransformUniform = glGetUniformLocation(shaderProgram, "viewTransform"); - int normalTransformUniform = glGetUniformLocation(shaderProgram, "normalTransform"); - - glUniformMatrix4fv(projectionTransformUniform, false, projectionTransform.get(new float[16])); - - long t = 0; - while (!glfwWindowShouldClose(windowHandle)) { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glUniformMatrix4fv(viewTransformUniform, false, cam.getViewTransformData()); - Matrix3f normalTransform = new Matrix3f(); - glUniformMatrix3fv(normalTransformUniform, false, normalTransform.get(new float[9])); - - mesh.draw(); - - glfwSwapBuffers(windowHandle); - glfwPollEvents(); -// float n = (float) (8 * Math.sin(Math.toRadians(t)) + 8); -// cam.setPosition(n, -1, n); -// cam.setOrientationDegrees(0, 0); -// Thread.sleep(20); -// t++; -// if (t >= 360) t = 0; - - if (glfwGetKey(windowHandle, GLFW_KEY_W) == GLFW_PRESS) cam.move(Camera.FORWARD); - if (glfwGetKey(windowHandle, GLFW_KEY_S) == GLFW_PRESS) cam.move(Camera.BACKWARD); - if (glfwGetKey(windowHandle, GLFW_KEY_A) == GLFW_PRESS) cam.move(Camera.LEFT); - if (glfwGetKey(windowHandle, GLFW_KEY_D) == GLFW_PRESS) cam.move(Camera.RIGHT); - if (glfwGetKey(windowHandle, GLFW_KEY_SPACE) == GLFW_PRESS) cam.move(Camera.UP); - if (glfwGetKey(windowHandle, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) cam.move(Camera.DOWN); - } - - Callbacks.glfwFreeCallbacks(windowHandle); - glfwDestroyWindow(windowHandle); - glfwTerminate(); - glfwSetErrorCallback(null).free(); - } - - private static int createShaderProgram() throws IOException { - int prog = glCreateProgram(); - int fragShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragShader, FileUtils.readClasspathFile("shader/fragment.glsl")); - glCompileShader(fragShader); - glAttachShader(prog, fragShader); - int vertShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertShader, FileUtils.readClasspathFile("shader/vertex.glsl")); - glCompileShader(vertShader); - glAttachShader(prog, vertShader); - - glValidateProgram(prog); - glLinkProgram(prog); - glUseProgram(prog); - return prog; + return windowHandle; } } 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 73cddab..73ef676 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java +++ b/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java @@ -156,4 +156,10 @@ public class ChunkMesh { 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/ChunkRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/ChunkRenderer.java new file mode 100644 index 0000000..c7eb9a4 --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/ChunkRenderer.java @@ -0,0 +1,52 @@ +package nl.andrewl.aos2_client; + +import org.joml.Matrix3f; +import org.joml.Matrix4f; + +import java.util.ArrayList; +import java.util.List; + +import static org.lwjgl.opengl.GL46.*; + +public class ChunkRenderer { + private final ShaderProgram shaderProgram; + private final int projectionTransformUniform; + private final int viewTransformUniform; + private final int normalTransformUniform; + + private final Matrix4f projectionTransform = new Matrix4f().perspective(70, 800 / 600.0f, 0.01f, 100.0f); + + private final List chunkMeshes = new ArrayList<>(); + + public ChunkRenderer() { + this.shaderProgram = new ShaderProgram.Builder() + .withShader("shader/chunk/vertex.glsl", GL_VERTEX_SHADER) + .withShader("shader/chunk/fragment.glsl", GL_FRAGMENT_SHADER) + .build(); + shaderProgram.use(); + this.projectionTransformUniform = shaderProgram.getUniform("projectionTransform"); + this.viewTransformUniform = shaderProgram.getUniform("viewTransform"); + this.normalTransformUniform = shaderProgram.getUniform("normalTransform"); + + // Preemptively load projection transform, which doesn't change much. + glUniformMatrix4fv(projectionTransformUniform, false, projectionTransform.get(new float[16])); + } + + public void addChunkMesh(ChunkMesh mesh) { + this.chunkMeshes.add(mesh); + } + + public void draw(Camera cam) { + glUniformMatrix4fv(viewTransformUniform, false, cam.getViewTransformData()); + Matrix3f normalTransform = new Matrix3f(); + glUniformMatrix3fv(normalTransformUniform, false, normalTransform.get(new float[9])); + shaderProgram.use(); + + for (var mesh : chunkMeshes) mesh.draw(); + } + + public void free() { + for (var mesh : chunkMeshes) mesh.free(); + shaderProgram.free(); + } +} diff --git a/client/src/main/java/nl/andrewl/aos2_client/ShaderProgram.java b/client/src/main/java/nl/andrewl/aos2_client/ShaderProgram.java new file mode 100644 index 0000000..2e02084 --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/ShaderProgram.java @@ -0,0 +1,57 @@ +package nl.andrewl.aos2_client; + +import nl.andrewl.aos_core.FileUtils; + +import java.io.IOException; + +import static org.lwjgl.opengl.GL46.*; + +/** + * Represents a shader program with one or more individual shaders. + */ +public class ShaderProgram { + private final int id; + + public ShaderProgram(int id) { + this.id = id; + } + + public void use() { + glUseProgram(id); + } + + public int getUniform(String name) { + return glGetUniformLocation(id, name); + } + + public void free() { + glDeleteProgram(id); + } + + public static class Builder { + private final int id; + + public Builder() { + this.id = glCreateProgram(); + } + + public Builder withShader(String resource, int type) { + int shaderId = glCreateShader(type); + try { + glShaderSource(shaderId, FileUtils.readClasspathFile(resource)); + } catch (IOException e) { + throw new RuntimeException(e); + } + glCompileShader(shaderId); + glAttachShader(id, shaderId); + glDeleteShader(shaderId); + return this; + } + + public ShaderProgram build() { + glValidateProgram(id); + glLinkProgram(id); + return new ShaderProgram(id); + } + } +} diff --git a/client/src/main/resources/shader/fragment.glsl b/client/src/main/resources/shader/chunk/fragment.glsl similarity index 84% rename from client/src/main/resources/shader/fragment.glsl rename to client/src/main/resources/shader/chunk/fragment.glsl index 71ed3f8..4886647 100644 --- a/client/src/main/resources/shader/fragment.glsl +++ b/client/src/main/resources/shader/chunk/fragment.glsl @@ -11,7 +11,7 @@ void main() { 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; + vec3 diffuseComponent = max(dot(vertexNormal * -1, lightDirection), 0.0) * lightColor; // No specular component. fragmentColor = vec4((ambientComponent + diffuseComponent) * vertexColor, 1.0); diff --git a/client/src/main/resources/shader/vertex.glsl b/client/src/main/resources/shader/chunk/vertex.glsl similarity index 100% rename from client/src/main/resources/shader/vertex.glsl rename to client/src/main/resources/shader/chunk/vertex.glsl 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 77e8fec..024ab46 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 @@ -6,6 +6,9 @@ import org.joml.Vector3i; import java.util.Arrays; import java.util.Random; +/** + * Holds information about a uniform "chunk" of the voxel world. + */ public class Chunk { /** * The size of a chunk, in terms of the number of blocks on one axis of the cube. @@ -14,6 +17,20 @@ public class Chunk { public static final int TOTAL_SIZE = SIZE * SIZE * SIZE; private final byte[] blocks = new byte[TOTAL_SIZE]; + private final Vector3i position; + + public Chunk(int cx, int cy, int cz) { + this.position = new Vector3i(cx, cy, cz); + } + + public Chunk(Vector3i position) { + this.position = new Vector3i(position); + } + + public Chunk(Chunk other) { + this(other.position); + System.arraycopy(other.blocks, 0, this.blocks, 0, 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; @@ -50,14 +67,8 @@ public class Chunk { return sb.toString(); } - public static Chunk of(byte value) { - Chunk c = new Chunk(); - Arrays.fill(c.blocks, value); - return c; - } - - public static Chunk random(Random rand) { - Chunk c = new Chunk(); + public static Chunk random(Vector3i position, Random rand) { + Chunk c = new Chunk(position); for (int i = 0; i < TOTAL_SIZE; i++) { c.blocks[i] = (byte) rand.nextInt(1, 128); }