Added erroneous but somewhat working example.

This commit is contained in:
Andrew Lalis 2022-07-05 15:51:57 +02:00
parent 969576b34d
commit d34a4aa017
19 changed files with 688 additions and 212 deletions

View File

@ -212,4 +212,31 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>nl.andrewl.aos2_client.Aos2Client</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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);

View File

@ -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.
* <p>
* The Y axis orientation is limited to between 0 and PI, with 0
* being looking straight down, and PI looking straight up.
* </p>
* <p>
* The X axis orientation is limited to between 0 and 2 PI, with 0
* being looking at the - Z axis.
* </p>
*/
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);

View File

@ -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<float[], int[]> generateMesh(Chunk c) {
List<BlockVertexData> vertexList = new ArrayList<>();
List<Integer> 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);
}
}

View File

@ -1,4 +1,4 @@
package nl.andrewl.aos2_client;
package nl.andrewl.aos2_client.render;
import org.joml.Vector3f;

View File

@ -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);
}
}

View File

@ -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
) {}

View File

@ -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<Vector3f> 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);
}
}

View File

@ -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();
}

View File

@ -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);
}
}
}

View File

@ -1,4 +1,4 @@
package nl.andrewl.aos2_client;
package nl.andrewl.aos2_client.render;
import org.joml.Vector2f;
import org.lwjgl.BufferUtils;

View File

@ -15,5 +15,4 @@ void main() {
// No specular component.
fragmentColor = vec4((ambientComponent + diffuseComponent) * vertexColor, 1.0);
//fragmentColor = vec4((vertexNormal + 1) / 2.0, 1.0);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -23,5 +23,20 @@
<artifactId>joml</artifactId>
<version>1.10.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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));
}
}

267
design/block_rendering.svg Normal file
View File

@ -0,0 +1,267 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="block_rendering.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="587.56814"
inkscape:cy="738.24207"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#4f4f4f;stroke-width:1.05833327;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:3.17499981, 3.17499981000000009;stroke-dashoffset:0"
d="m 76.305449,108.17408 38.032351,0.40479 0.18719,-33.546215"
id="path912"
inkscape:connector-curvature="0" />
<g
id="g837" />
<g
id="g846"
style="stroke-width:2.11666667;stroke-miterlimit:4;stroke-dasharray:none;stroke:#000000;stroke-opacity:1;fill:none;fill-opacity:1;stroke-linejoin:round"
transform="translate(2.5390605,-2.80633)">
<path
style="fill:none;stroke:#000000;stroke-width:2.11666667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="m 73.766388,77.838987 v 33.141423 l 27.472262,23.78699 h 38.54331 V 90.400655 h -38.48681 z"
id="path831"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:none;stroke:#000000;stroke-width:2.11666667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="m 73.766388,77.838987 h 38.219542 l 27.79603,12.561668"
id="path833"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
id="path839"
d="m 101.29515,90.400655 -0.0565,44.366745"
style="fill:none;stroke:#000000;stroke-width:2.11666667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
sodipodi:nodetypes="cc" />
</g>
<path
style="fill:none;stroke:#27bf00;stroke-width:2.11666667;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="M 99.785711,179.07143 V 45.834821"
id="path850"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="109.31324"
y="46.033909"
id="text854"><tspan
sodipodi:role="line"
id="tspan852"
x="109.31324"
y="46.033909"
style="stroke-width:0.26458332">+Y</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="109.58051"
y="182.60864"
id="text858"><tspan
sodipodi:role="line"
id="tspan856"
x="109.58051"
y="182.60864"
style="stroke-width:0.26458332">-Y</tspan></text>
<path
style="fill:none;stroke:#ea3400;stroke-width:2.11666656;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 19.243406,136.63829 H 192.70133"
id="path860"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="180.40692"
y="149.73448"
id="text864"><tspan
sodipodi:role="line"
id="tspan862"
x="180.40692"
y="149.73448"
style="stroke-width:0.26458332">+Z</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="18.708866"
y="147.32906"
id="text868"><tspan
sodipodi:role="line"
id="tspan866"
x="18.708866"
y="147.32906"
style="stroke-width:0.26458332">-Z</tspan></text>
<path
style="fill:none;stroke:#0019dc;stroke-width:2.11666656;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 44.634011,87.995228 158.49082,189.02311"
id="path870"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="164.63803"
y="193.03215"
id="text874"><tspan
sodipodi:role="line"
id="tspan872"
x="164.63803"
y="193.03215"
style="stroke-width:0.26458332">-X</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="39.555889"
y="78.373528"
id="text878"><tspan
sodipodi:role="line"
id="tspan876"
x="39.555889"
y="78.373528"
style="stroke-width:0.26458332">+X</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="133.42558"
y="96.483635"
id="text882"><tspan
sodipodi:role="line"
id="tspan880"
x="133.42558"
y="96.483635"
style="stroke-width:0.26458332">a</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="106.77827"
y="97.617554"
id="text886"><tspan
sodipodi:role="line"
id="tspan884"
x="106.77827"
y="97.617554"
style="stroke-width:0.26458332">b</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="108.66816"
y="126.72173"
id="text890"><tspan
sodipodi:role="line"
id="tspan888"
x="108.66816"
y="126.72173"
style="stroke-width:0.26458332">c</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:10.58333302px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332"
x="134.55952"
y="127.85565"
id="text894"><tspan
sodipodi:role="line"
id="tspan892"
x="134.55952"
y="127.85565"
style="stroke-width:0.26458332">d</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:5.73591566px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.1433979"
x="78.88652"
y="83.134109"
id="text898"><tspan
sodipodi:role="line"
id="tspan896"
x="78.88652"
y="83.134109"
style="stroke-width:0.1433979">e</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:6.24058294px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.15601456"
x="107.44491"
y="82.687492"
id="text902"><tspan
sodipodi:role="line"
id="tspan900"
x="107.44491"
y="82.687492"
style="stroke-width:0.15601456">f</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:6.33006239px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.15825155"
x="108.33587"
y="105.30584"
id="text906"><tspan
sodipodi:role="line"
id="tspan904"
x="108.33587"
y="105.30584"
style="stroke-width:0.15825155">g</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:6.04439497px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.15110987"
x="79.220268"
y="106.68898"
id="text910"><tspan
sodipodi:role="line"
id="tspan908"
x="79.220268"
y="106.68898"
style="stroke-width:0.15110987">h</tspan></text>
<flowRoot
xml:space="preserve"
id="flowRoot914"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
transform="scale(0.26458333)"><flowRegion
id="flowRegion916"><rect
id="rect918"
width="345.71429"
height="110.71429"
x="33.57143"
y="19.66254" /></flowRegion><flowPara
id="flowPara920">Block rendering schema</flowPara></flowRoot> </g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB