From 26881c15200e5b982e59caa998f231f921c2bb91 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Sun, 3 Jul 2022 22:12:40 +0200 Subject: [PATCH] Added the very basics of my own 3d graphics engine! --- .gitignore | 2 + client/pom.xml | 215 ++++++++++++++++++ .../nl/andrewl/aos2_client/Aos2Client.java | 99 ++++++++ .../nl/andrewl/aos2_client/ChunkMesh.java | 58 +++++ .../src/main/resources/shader/fragment.glsl | 10 + client/src/main/resources/shader/vertex.glsl | 15 ++ core/pom.xml | 27 +++ .../java/nl/andrewl/aos_core/FileUtils.java | 15 ++ .../main/java/nl/andrewl/aos_core/Pair.java | 3 + .../main/java/nl/andrewl/aos_core/Triple.java | 3 + .../java/nl/andrewl/aos_core/model/Chunk.java | 97 ++++++++ .../java/nl/andrewl/aos_core/model/World.java | 27 +++ pom.xml | 22 ++ server/pom.xml | 26 +++ 14 files changed, 619 insertions(+) create mode 100644 .gitignore create mode 100644 client/pom.xml create mode 100644 client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java create mode 100644 client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java create mode 100644 client/src/main/resources/shader/fragment.glsl create mode 100644 client/src/main/resources/shader/vertex.glsl create mode 100644 core/pom.xml create mode 100644 core/src/main/java/nl/andrewl/aos_core/FileUtils.java create mode 100644 core/src/main/java/nl/andrewl/aos_core/Pair.java create mode 100644 core/src/main/java/nl/andrewl/aos_core/Triple.java create mode 100644 core/src/main/java/nl/andrewl/aos_core/model/Chunk.java create mode 100644 core/src/main/java/nl/andrewl/aos_core/model/World.java create mode 100644 pom.xml create mode 100644 server/pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e673575 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +target/ \ No newline at end of file diff --git a/client/pom.xml b/client/pom.xml new file mode 100644 index 0000000..9201018 --- /dev/null +++ b/client/pom.xml @@ -0,0 +1,215 @@ + + + + ace-of-shades-2 + nl.andrewl + 1.0-SNAPSHOT + + 4.0.0 + + aos2-client + + + 17 + 17 + 3.3.1 + + + + + lwjgl-natives-linux-amd64 + + + unix + amd64 + + + + natives-linux + + + + lwjgl-natives-linux-aarch64 + + + unix + aarch64 + + + + natives-linux-arm64 + + + + lwjgl-natives-linux-arm + + + unix + arm + + + + natives-linux-arm32 + + + + lwjgl-natives-linux-arm32 + + + unix + arm32 + + + + natives-linux-arm32 + + + + lwjgl-natives-macos-x86_64 + + + mac + x86_64 + + + + natives-macos + + + + lwjgl-natives-macos-aarch64 + + + mac + aarch64 + + + + natives-macos-arm64 + + + + lwjgl-natives-windows-amd64 + + + windows + amd64 + + + + natives-windows + + + + lwjgl-natives-windows-x86 + + + windows + x86 + + + + natives-windows-x86 + + + + lwjgl-natives-windows-aarch64 + + + windows + aarch64 + + + + natives-windows-arm64 + + + + + + + + org.lwjgl + lwjgl-bom + ${lwjgl.version} + import + pom + + + + + + + nl.andrewl + aos2-core + ${parent.version} + + + + org.lwjgl + lwjgl + + + org.lwjgl + lwjgl-assimp + + + org.lwjgl + lwjgl-glfw + + + org.lwjgl + lwjgl-meshoptimizer + + + org.lwjgl + lwjgl-openal + + + org.lwjgl + lwjgl-opengl + + + org.lwjgl + lwjgl-stb + + + org.lwjgl + lwjgl + ${lwjgl.natives} + + + org.lwjgl + lwjgl-assimp + ${lwjgl.natives} + + + org.lwjgl + lwjgl-glfw + ${lwjgl.natives} + + + org.lwjgl + lwjgl-meshoptimizer + ${lwjgl.natives} + + + org.lwjgl + lwjgl-openal + ${lwjgl.natives} + + + org.lwjgl + lwjgl-opengl + ${lwjgl.natives} + + + org.lwjgl + lwjgl-stb + ${lwjgl.natives} + + + + \ 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 new file mode 100644 index 0000000..73726c1 --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/Aos2Client.java @@ -0,0 +1,99 @@ +package nl.andrewl.aos2_client; + +import nl.andrewl.aos_core.FileUtils; +import nl.andrewl.aos_core.model.Chunk; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.lwjgl.BufferUtils; +import org.lwjgl.Version; +import org.lwjgl.glfw.Callbacks; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GLUtil; +import org.lwjgl.system.MemoryUtil; + +import java.io.IOException; + +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.opengl.GL46.*; + +public class Aos2Client { + public static void main(String[] args) throws IOException { + System.out.println("LWJGL Version: " + Version.getVersion()); + GLFWErrorCallback.createPrint(System.err).set(); + if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW"); + glfwDefaultWindowHints(); + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + long windowHandle = glfwCreateWindow(800, 600, "Ace of Shades 2", MemoryUtil.NULL, MemoryUtil.NULL); + if (windowHandle == MemoryUtil.NULL) throw new RuntimeException("Failed to create GLFW window."); + glfwSetKeyCallback(windowHandle, (window, key, scancode, action, mods) -> { + if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) { + glfwSetWindowShouldClose(windowHandle, true); + } + }); + + glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + glfwSetInputMode(windowHandle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); + + glfwSetWindowPos(windowHandle, 50, 50); + glfwSetCursorPos(windowHandle, 0, 0); + + glfwMakeContextCurrent(windowHandle); + glfwSwapInterval(1); + glfwShowWindow(windowHandle); + + GL.createCapabilities(); + GLUtil.setupDebugMessageCallback(System.out); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + glCullFace(GL_BACK); + + Chunk chunk = Chunk.of((byte) 64); + 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(); + int projectionTransformUniform = glGetUniformLocation(shaderProgram, "projectionTransform"); + int viewTransformUniform = glGetUniformLocation(shaderProgram, "viewTransform"); + + glUniformMatrix4fv(projectionTransformUniform, false, projectionTransform.get(new float[16])); + + while (!glfwWindowShouldClose(windowHandle)) { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glUniformMatrix4fv(viewTransformUniform, false, viewTransform.get(new float[16])); + + mesh.draw(); + + glfwSwapBuffers(windowHandle); + glfwPollEvents(); + } + + 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; + } +} diff --git a/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java b/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java new file mode 100644 index 0000000..3b089df --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/ChunkMesh.java @@ -0,0 +1,58 @@ +package nl.andrewl.aos2_client; + +import nl.andrewl.aos_core.model.Chunk; +import org.lwjgl.BufferUtils; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import static org.lwjgl.opengl.GL46.*; + +public class ChunkMesh { + private final int vboId; + private final int vaoId; + private final int eboId; + + private final int indiciesCount; + + public ChunkMesh(Chunk chunk) { + this.vboId = glGenBuffers(); + 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(); + + glBindBuffer(GL_ARRAY_BUFFER, vboId); + glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexBuffer, GL_STATIC_DRAW); + + glBindVertexArray(vaoId); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0); + } + + 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); + } +} diff --git a/client/src/main/resources/shader/fragment.glsl b/client/src/main/resources/shader/fragment.glsl new file mode 100644 index 0000000..c4577df --- /dev/null +++ b/client/src/main/resources/shader/fragment.glsl @@ -0,0 +1,10 @@ +#version 460 core + +in vec3 vertexPosition; +in vec3 vertexColor; + +out vec4 fragmentColor; + +void main() { + fragmentColor = vec4(vertexColor, 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 new file mode 100644 index 0000000..735f874 --- /dev/null +++ b/client/src/main/resources/shader/vertex.glsl @@ -0,0 +1,15 @@ +#version 460 core + +layout (location = 0) in vec3 vertexPositionIn; + +uniform mat4 projectionTransform; +uniform mat4 viewTransform; + +out vec3 vertexPosition; +out vec3 vertexColor; + +void main() { + gl_Position = projectionTransform * viewTransform * vec4(vertexPositionIn, 1.0); + vertexPosition = vertexPositionIn; + vertexColor = vec3(1.0, 0.5, 0.5); +} diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..9d8f294 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,27 @@ + + + + ace-of-shades-2 + nl.andrewl + 1.0-SNAPSHOT + + 4.0.0 + + aos2-core + + + 17 + 17 + + + + + + org.joml + joml + 1.10.4 + + + \ No newline at end of file diff --git a/core/src/main/java/nl/andrewl/aos_core/FileUtils.java b/core/src/main/java/nl/andrewl/aos_core/FileUtils.java new file mode 100644 index 0000000..3dac464 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/FileUtils.java @@ -0,0 +1,15 @@ +package nl.andrewl.aos_core; + +import java.io.IOException; +import java.io.InputStream; + +public final class FileUtils { + private FileUtils() {} + + public static String readClasspathFile(String resource) throws IOException { + try (InputStream in = FileUtils.class.getClassLoader().getResourceAsStream(resource)) { + if (in == null) throw new IOException("Could not load classpath resource: " + resource); + return new String(in.readAllBytes()); + } + } +} diff --git a/core/src/main/java/nl/andrewl/aos_core/Pair.java b/core/src/main/java/nl/andrewl/aos_core/Pair.java new file mode 100644 index 0000000..64ad365 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/Pair.java @@ -0,0 +1,3 @@ +package nl.andrewl.aos_core; + +public record Pair(A first, B second) {} diff --git a/core/src/main/java/nl/andrewl/aos_core/Triple.java b/core/src/main/java/nl/andrewl/aos_core/Triple.java new file mode 100644 index 0000000..5863c25 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/Triple.java @@ -0,0 +1,3 @@ +package nl.andrewl.aos_core; + +public record Triple(A first, B second, C third) {} 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 new file mode 100644 index 0000000..b670fb4 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/model/Chunk.java @@ -0,0 +1,97 @@ +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; + +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; + + private final byte[] blocks = new byte[SIZE * SIZE * SIZE]; + + public byte getBlockAt(int x, int y, int z) { + 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]; + } + + public byte getBlockAt(Vector3i localPosition) { + return getBlockAt(localPosition.x, localPosition.y, localPosition.z); + } + + 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 + } + } + } + + return new Pair<>(vertexList, indexList); + } + + public static Chunk of(byte value) { + Chunk c = new Chunk(); + Arrays.fill(c.blocks, value); + return c; + } + + public static Vector3f getColor(byte blockValue) { + float v = blockValue / 128.0f; + return new Vector3f(v); + } +} diff --git a/core/src/main/java/nl/andrewl/aos_core/model/World.java b/core/src/main/java/nl/andrewl/aos_core/model/World.java new file mode 100644 index 0000000..632a2a0 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/model/World.java @@ -0,0 +1,27 @@ +package nl.andrewl.aos_core.model; + +import org.joml.Vector3i; + +import java.util.HashMap; +import java.util.Map; + +public class World { + Map chunkMap = new HashMap<>(); + + public byte getBlockAt(int x, int y, int z) { + int chunkX = x / Chunk.SIZE; + int localX = x % Chunk.SIZE; + int chunkY = y / Chunk.SIZE; + int localY = y % Chunk.SIZE; + int chunkZ = z / Chunk.SIZE; + int localZ = z % Chunk.SIZE; + Vector3i chunkPos = new Vector3i(chunkX, chunkY, chunkZ); + Chunk chunk = chunkMap.get(chunkPos); + if (chunk == null) return 0; + return chunk.getBlockAt(localX, localY, localZ); + } + + public Chunk getChunkAt(Vector3i chunkPos) { + return chunkMap.get(chunkPos); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..324b911 --- /dev/null +++ b/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + nl.andrewl + ace-of-shades-2 + pom + 1.0-SNAPSHOT + + core + server + client + + + + 17 + 17 + + + \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 0000000..288e8a8 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,26 @@ + + + + ace-of-shades-2 + nl.andrewl + 1.0-SNAPSHOT + + 4.0.0 + + aos2-server + + + 17 + 17 + + + + + nl.andrewl + aos2-core + ${parent.version} + + + \ No newline at end of file