diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/Freeworld.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/Freeworld.java index a9f32e5..39c3ebd 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/Freeworld.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/Freeworld.java @@ -45,6 +45,7 @@ public final class Freeworld implements AutoCloseable { private static final Logger logger = Logging.caller(); private static final int INIT_WINDOW_WIDTH = 854; private static final int INIT_WINDOW_HEIGHT = 480; + private static final double MOUSE_SENSITIVITY = 0.15; private final GLFW glfw; private GLFlags glFlags; private GLStateMgr gl; @@ -57,6 +58,7 @@ public final class Freeworld implements AutoCloseable { private double cursorY; private double cursorDeltaX; private double cursorDeltaY; + private boolean disableCursor = false; private GameRenderer gameRenderer; private World world; private Entity player; @@ -87,11 +89,12 @@ public void start() { glfw.windowHint(GLFW.POSITION_Y, (videoMode.height() - INIT_WINDOW_HEIGHT) / 2); } - window = glfw.createWindow(INIT_WINDOW_WIDTH, INIT_WINDOW_HEIGHT, "freeworld", MemorySegment.NULL, MemorySegment.NULL); + window = glfw.createWindow(INIT_WINDOW_WIDTH, INIT_WINDOW_HEIGHT, "freeworld ~: toggle camera", MemorySegment.NULL, MemorySegment.NULL); if (Unmarshal.isNullPointer(window)) { throw new IllegalStateException("Failed to create GLFW window"); } + glfw.setKeyCallback(window, (_, key, scancode, action, mods) -> onKey(key, scancode, action, mods)); glfw.setFramebufferSizeCallback(window, (_, width, height) -> onResize(width, height)); glfw.setCursorPosCallback(window, (_, xpos, ypos) -> onCursorPos(xpos, ypos)); @@ -115,6 +118,19 @@ public void start() { logger.info("Closing client"); } + private void onKey(int key, int scancode, int action, int mods) { + switch (action) { + case GLFW.RELEASE -> { + switch (key) { + case GLFW.KEY_GRAVE_ACCENT -> { + disableCursor = !disableCursor; + glfw.setInputMode(window, GLFW.CURSOR, disableCursor ? GLFW.CURSOR_DISABLED : GLFW.CURSOR_NORMAL); + } + } + } + } + } + private void onResize(int width, int height) { framebufferWidth = width; framebufferHeight = height; @@ -124,8 +140,8 @@ private void onResize(int width, int height) { private void onCursorPos(double x, double y) { cursorDeltaX = x - cursorX; cursorDeltaY = y - cursorY; - if (glfw.getMouseButton(window, GLFW.MOUSE_BUTTON_RIGHT) == GLFW.PRESS) { - camera.rotate(-cursorDeltaY * 0.7, -cursorDeltaX * 0.7); + if (disableCursor) { + camera.rotate(-cursorDeltaY * MOUSE_SENSITIVITY, -cursorDeltaX * MOUSE_SENSITIVITY); } cursorX = x; cursorY = y; @@ -164,6 +180,7 @@ public void run() { tick(); } gameRenderer.render(gl, timer.partialTick()); + glfw.swapBuffers(window); } } diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java index eddae1d..b8b6098 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java @@ -11,6 +11,7 @@ package io.github.xenfork.freeworld.client.render; import io.github.xenfork.freeworld.client.Freeworld; +import io.github.xenfork.freeworld.client.render.gl.GLDrawMode; import io.github.xenfork.freeworld.client.render.gl.GLProgram; import io.github.xenfork.freeworld.client.render.gl.GLResource; import io.github.xenfork.freeworld.client.render.gl.GLStateMgr; @@ -48,6 +49,7 @@ public final class GameRenderer implements GLResource { private TextureManager textureManager; private BlockRenderer blockRenderer; private WorldRenderer worldRenderer; + private Tessellator tessellator; public GameRenderer(Freeworld client) { this.client = client; @@ -68,6 +70,8 @@ public void init(GLStateMgr gl) { blockRenderer = new BlockRenderer(this); worldRenderer = new WorldRenderer(client, this, client.world()); + + tessellator = new Tessellator(); } private void initGLPrograms(GLStateMgr gl) { @@ -87,13 +91,13 @@ private GLProgram initBootstrapProgram(GLStateMgr gl, String path, VertexLayout public void render(GLStateMgr gl, double partialTick) { gl.clear(GL10C.COLOR_BUFFER_BIT | GL10C.DEPTH_BUFFER_BIT); - gl.enable(GL10C.CULL_FACE); - gl.enable(GL10C.DEPTH_TEST); - gl.depthFunc(GL10C.LEQUAL); + gl.enableCullFace(); + gl.enableDepthTest(); + gl.setDepthFunc(GL10C.LEQUAL); texture.bind(gl); positionColorTexProgram.use(gl); projectionView.setPerspective( - (float) Math.toRadians(90.0), + (float) Math.toRadians(70.0), (float) client.framebufferWidth() / client.framebufferHeight(), 0.01f, 1000.0f @@ -102,12 +106,43 @@ public void render(GLStateMgr gl, double partialTick) { camera.updateLerp(partialTick); camera.updateViewMatrix(); projectionView.mul(camera.viewMatrix()); + matrix.identity(); positionColorTexProgram.getUniform(GLProgram.UNIFORM_PROJECTION_VIEW_MATRIX).set(projectionView); positionColorTexProgram.getUniform(GLProgram.UNIFORM_MODEL_MATRIX).set(matrix); positionColorTexProgram.uploadUniforms(gl); - worldRenderer.render(gl); + worldRenderer.render(gl, tessellator); + + renderGui(gl, partialTick); + } - client.glfw().swapBuffers(client.window()); + private void renderGui(GLStateMgr gl, double partialTick) { + final int width = client.framebufferWidth(); + final int height = client.framebufferHeight(); + + gl.clear(GL10C.DEPTH_BUFFER_BIT); + + gl.disableCullFace(); + gl.disableDepthTest(); + gl.setTextureBinding2D(0); + positionColorProgram.use(gl); + projectionView.setOrtho(0.0f, width, 0.0f, height, -100.0f, 100.0f); + matrix.translation(width * 0.5f, height * 0.5f, 0.0f); + positionColorProgram.getUniform(GLProgram.UNIFORM_PROJECTION_VIEW_MATRIX).set(projectionView); + positionColorProgram.getUniform(GLProgram.UNIFORM_MODEL_MATRIX).set(matrix); + positionColorProgram.uploadUniforms(gl); + tessellator.begin(GLDrawMode.TRIANGLES); + tessellator.color(1.0f, 1.0f, 1.0f); + tessellator.index(gl, 0, 1, 2, 2, 3, 0); + tessellator.position(-8, 1, 0).emit(gl); + tessellator.position(-8, -1, 0).emit(gl); + tessellator.position(8, -1, 0).emit(gl); + tessellator.position(8, 1, 0).emit(gl); + tessellator.index(gl, 0, 1, 2, 2, 3, 0); + tessellator.position(1, 8, 0).emit(gl); + tessellator.position(1, -8, 0).emit(gl); + tessellator.position(-1, -8, 0).emit(gl); + tessellator.position(-1, 8, 0).emit(gl); + tessellator.end(gl); } @Override @@ -119,7 +154,7 @@ public void close(GLStateMgr gl) { if (positionColorProgram != null) positionColorProgram.close(gl); if (positionColorTexProgram != null) positionColorTexProgram.close(gl); - Tessellator.free(gl); + if (tessellator != null) tessellator.close(gl); } public GLProgram positionColorProgram() { diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/Tessellator.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/Tessellator.java index bc2092c..c85a894 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/Tessellator.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/Tessellator.java @@ -11,7 +11,9 @@ package io.github.xenfork.freeworld.client.render; import io.github.xenfork.freeworld.client.render.gl.GLDrawMode; +import io.github.xenfork.freeworld.client.render.gl.GLResource; import io.github.xenfork.freeworld.client.render.gl.GLStateMgr; +import io.github.xenfork.freeworld.client.render.model.VertexFormat; import io.github.xenfork.freeworld.client.render.model.VertexLayout; import io.github.xenfork.freeworld.client.render.model.VertexLayouts; import overrungl.opengl.GL10C; @@ -32,47 +34,51 @@ * @author squid233 * @since 0.1.0 */ -public final class Tessellator { +public final class Tessellator implements GLResource { private static final int MAX_VERTEX_COUNT = 60000; private static final int MAX_INDEX_COUNT = 90000; private static final VertexLayout VERTEX_LAYOUT = VertexLayouts.POSITION_COLOR_TEX; private static final StructLayout LAYOUT = VERTEX_LAYOUT.layout(); + private static final String POSITION_NAME = VertexFormat.POSITION.name(); + private static final String COLOR_NAME = VertexFormat.COLOR.name(); + private static final String UV_NAME = VertexFormat.UV.name(); private static final VarHandle X = LAYOUT.arrayElementVarHandle( - PathElement.groupElement(VertexLayouts.NAME_POSITION), + PathElement.groupElement(POSITION_NAME), PathElement.sequenceElement(0L) ); private static final VarHandle Y = LAYOUT.arrayElementVarHandle( - PathElement.groupElement(VertexLayouts.NAME_POSITION), + PathElement.groupElement(POSITION_NAME), PathElement.sequenceElement(1L) ); private static final VarHandle Z = LAYOUT.arrayElementVarHandle( - PathElement.groupElement(VertexLayouts.NAME_POSITION), + PathElement.groupElement(POSITION_NAME), PathElement.sequenceElement(2L) ); private static final VarHandle R = LAYOUT.arrayElementVarHandle( - PathElement.groupElement(VertexLayouts.NAME_COLOR), + PathElement.groupElement(COLOR_NAME), PathElement.sequenceElement(0L) ); private static final VarHandle G = LAYOUT.arrayElementVarHandle( - PathElement.groupElement(VertexLayouts.NAME_COLOR), + PathElement.groupElement(COLOR_NAME), PathElement.sequenceElement(1L) ); private static final VarHandle B = LAYOUT.arrayElementVarHandle( - PathElement.groupElement(VertexLayouts.NAME_COLOR), + PathElement.groupElement(COLOR_NAME), PathElement.sequenceElement(2L) ); private static final VarHandle A = LAYOUT.arrayElementVarHandle( - PathElement.groupElement(VertexLayouts.NAME_COLOR), + PathElement.groupElement(COLOR_NAME), PathElement.sequenceElement(3L) ); private static final VarHandle U = LAYOUT.arrayElementVarHandle( - PathElement.groupElement(VertexLayouts.NAME_UV), + PathElement.groupElement(UV_NAME), PathElement.sequenceElement(0L) ); private static final VarHandle V = LAYOUT.arrayElementVarHandle( - PathElement.groupElement(VertexLayouts.NAME_UV), + PathElement.groupElement(UV_NAME), PathElement.sequenceElement(1L) ); + private final Arena arena; private final MemorySegment buffer; private final MemorySegment indexBuffer; private int vertexCount = 0; @@ -92,27 +98,10 @@ public final class Tessellator { private float u = 0f; private float v = 0f; - private Tessellator() { - final Arena arena = Arena.ofAuto(); - buffer = arena.allocate(LAYOUT, MAX_VERTEX_COUNT); - indexBuffer = arena.allocate(ValueLayout.JAVA_INT, MAX_INDEX_COUNT); - } - - /** - * @author squid233 - * @since 0.1.0 - */ - private static final class Holder { - private static final Tessellator INSTANCE = new Tessellator(); - } - - public static Tessellator getInstance() { - return Holder.INSTANCE; - } - - public static void free(GLStateMgr gl) { - gl.deleteVertexArrays(Holder.INSTANCE.vao); - gl.deleteBuffers(Holder.INSTANCE.vbo, Holder.INSTANCE.ebo); + public Tessellator() { + this.arena = Arena.ofConfined(); + this.buffer = arena.allocate(LAYOUT, MAX_VERTEX_COUNT); + this.indexBuffer = arena.allocate(ValueLayout.JAVA_INT, MAX_INDEX_COUNT); } public Tessellator position(float x, float y, float z) { @@ -222,4 +211,11 @@ public void end(GLStateMgr gl) { flush(gl); drawing = false; } + + @Override + public void close(GLStateMgr gl) { + arena.close(); + gl.deleteVertexArrays(vao); + gl.deleteBuffers(vbo, ebo); + } } diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/gl/GLStateMgr.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/gl/GLStateMgr.java index 9129c88..20cda00 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/gl/GLStateMgr.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/gl/GLStateMgr.java @@ -26,7 +26,10 @@ public abstract class GLStateMgr implements GL41C, DirectAccess { private int arrayBufferBinding = 0; + private boolean cullFace = false; private int currentProgram = 0; + private int depthFunc = LESS; + private boolean depthTest = false; private int textureBinding2D = 0; private int vertexArrayBinding = 0; @@ -43,6 +46,27 @@ public int arrayBufferBinding() { return arrayBufferBinding; } + @Skip + public void enableCullFace() { + if (!this.cullFace) { + this.cullFace = true; + enable(CULL_FACE); + } + } + + @Skip + public void disableCullFace() { + if (this.cullFace) { + this.cullFace = false; + disable(CULL_FACE); + } + } + + @Skip + public boolean cullFace() { + return cullFace; + } + @Skip public void setCurrentProgram(int currentProgram) { if (this.currentProgram != currentProgram) { @@ -56,6 +80,40 @@ public int currentProgram() { return currentProgram; } + @Skip + public void setDepthFunc(int depthFunc) { + if (this.depthFunc != depthFunc) { + this.depthFunc = depthFunc; + depthFunc(depthFunc); + } + } + + @Skip + public int depthFunc() { + return depthFunc; + } + + @Skip + public void enableDepthTest() { + if (!this.depthTest) { + this.depthTest = true; + enable(DEPTH_TEST); + } + } + + @Skip + public void disableDepthTest() { + if (this.depthTest) { + this.depthTest = false; + disable(DEPTH_TEST); + } + } + + @Skip + public boolean depthTest() { + return depthTest; + } + @Skip public void setTextureBinding2D(int textureBinding2D) { if (this.textureBinding2D != textureBinding2D) { diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/DefaultVertexFormat.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/DefaultVertexFormat.java index 5f67995..09d0672 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/DefaultVertexFormat.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/DefaultVertexFormat.java @@ -19,7 +19,7 @@ * @author squid233 * @since 0.1.0 */ -public record DefaultVertexFormat(int size, GLDataType type, boolean normalized) implements VertexFormat { +public record DefaultVertexFormat(String name, int size, GLDataType type, boolean normalized) implements VertexFormat { @Override public int usedAttribCount() { return 1; diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexFormat.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexFormat.java index 1267339..1a9e019 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexFormat.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexFormat.java @@ -19,9 +19,11 @@ * @since 0.1.0 */ public interface VertexFormat { - VertexFormat POSITION = new DefaultVertexFormat(3, GLDataType.FLOAT, false); - VertexFormat COLOR = new DefaultVertexFormat(4, GLDataType.UNSIGNED_BYTE, true); - VertexFormat UV = new DefaultVertexFormat(2, GLDataType.FLOAT, false); + VertexFormat POSITION = new DefaultVertexFormat("Position", 3, GLDataType.FLOAT, false); + VertexFormat COLOR = new DefaultVertexFormat("Color", 4, GLDataType.UNSIGNED_BYTE, true); + VertexFormat UV = new DefaultVertexFormat("UV", 2, GLDataType.FLOAT, false); + + String name(); int size(); diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexLayout.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexLayout.java index 7267937..79a09e3 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexLayout.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexLayout.java @@ -15,41 +15,38 @@ import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; import java.lang.foreign.StructLayout; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * @author squid233 * @since 0.1.0 */ public final class VertexLayout { - private final Map formatMap; + private final List formats; private final Map attribLocationMap; private final StructLayout layout; - public VertexLayout(Map formatMap) { - this.formatMap = formatMap; - this.attribLocationMap = HashMap.newHashMap(formatMap.size()); - final List elements = new ArrayList<>(formatMap.size()); + public VertexLayout(List formats) { + this.formats = formats; + this.attribLocationMap = HashMap.newHashMap(formats.size()); + final List elements = new ArrayList<>(formats.size()); int i = 0; - for (var entry : this.formatMap.entrySet()) { - final String key = entry.getKey(); - final VertexFormat value = entry.getValue(); - this.attribLocationMap.put(key, i); - elements.add(value.layout().withName(key)); - i += value.usedAttribCount(); + for (var format : formats) { + final String name = format.name(); + this.attribLocationMap.put(name, i); + elements.add(format.layout().withName(name)); + i += format.usedAttribCount(); } this.layout = MemoryLayout.structLayout(elements.toArray(MemoryLayout[]::new)); } - @SafeVarargs - public VertexLayout(Map.Entry... formats) { - final Map map = LinkedHashMap.newLinkedHashMap(formats.length); - for (var format : formats) { - map.put(format.getKey(), format.getValue()); - } - this(map); + public VertexLayout(VertexFormat... formats) { + this(List.of(formats)); } public void bindLocations(GLStateMgr gl, int program) { @@ -65,9 +62,8 @@ public void enableAttribs(GLStateMgr gl) { public void specifyAttribPointers(GLStateMgr gl) { final int stride = Math.toIntExact(layout().byteSize()); long offset = 0L; - for (var entry : formatMap.entrySet()) { - final VertexFormat format = entry.getValue(); - gl.vertexAttribPointer(attribLocationMap.get(entry.getKey()), + for (var format : formats) { + gl.vertexAttribPointer(attribLocationMap.get(format.name()), format.size(), format.type().value(), format.normalized(), diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexLayouts.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexLayouts.java index 9cea029..afbd931 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexLayouts.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/model/VertexLayouts.java @@ -10,8 +10,6 @@ package io.github.xenfork.freeworld.client.render.model; -import java.util.Map; - /** * Vertex layouts * @@ -19,18 +17,15 @@ * @since 0.1.0 */ public final class VertexLayouts { - public static final String NAME_POSITION = "Position"; - public static final String NAME_COLOR = "Color"; - public static final String NAME_UV = "UV"; - public static final VertexLayout POSITION_COLOR = new VertexLayout(Map.of( - NAME_POSITION, VertexFormat.POSITION, - NAME_COLOR, VertexFormat.COLOR - )); - public static final VertexLayout POSITION_COLOR_TEX = new VertexLayout(Map.of( - NAME_POSITION, VertexFormat.POSITION, - NAME_COLOR, VertexFormat.COLOR, - NAME_UV, VertexFormat.UV - )); + public static final VertexLayout POSITION_COLOR = new VertexLayout( + VertexFormat.POSITION, + VertexFormat.COLOR + ); + public static final VertexLayout POSITION_COLOR_TEX = new VertexLayout( + VertexFormat.POSITION, + VertexFormat.COLOR, + VertexFormat.UV + ); private VertexLayouts() { } diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java index 9ed3902..c2f1f88 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java @@ -50,25 +50,24 @@ public WorldRenderer(Freeworld client, GameRenderer gameRenderer, World world) { public void compileChunks() { } - public void render(GLStateMgr gl) { + public void render(GLStateMgr gl, Tessellator tessellator) { // final Vector3dc position = client.camera().position(); final BlockRenderer blockRenderer = gameRenderer.blockRenderer(); - final Tessellator t = Tessellator.getInstance(); - t.begin(GLDrawMode.TRIANGLES); + tessellator.begin(GLDrawMode.TRIANGLES); for (Chunk chunk : world.chunks) { for (Direction direction : Direction.LIST) { for (int x = chunk.fromX(), x1 = chunk.toX(); x < x1; x++) { for (int y = chunk.fromY(), y1 = chunk.toY(); y < y1; y++) { for (int z = chunk.fromZ(), z1 = chunk.toZ(); z < z1; z++) { if (chunk.getBlockType(x + direction.axisX(), y + direction.axisY(), z + direction.axisZ()).air()) { - blockRenderer.renderBlockFace(gl, t, chunk.getBlockType(x, y, z), x, y, z, direction); + blockRenderer.renderBlockFace(gl, tessellator, chunk.getBlockType(x, y, z), x, y, z, direction); } } } } } } - t.end(gl); + tessellator.end(gl); } @Override