From 4bd16026e1313509bf9b5971baacb0a6ca6092f5 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Tue, 22 Apr 2025 15:02:24 +0200 Subject: [PATCH 01/40] begin 3d --- .gitattributes | 3 +-- app/src/main/java/chess/App.java | 10 +++++++--- app/src/main/java/chess/OpenGLMain.java | 9 +++++++++ .../java/chess/view/{render => DDDrender}/Camera.java | 2 +- .../view/{render => DDDrender}/ElementBuffer.java | 2 +- .../chess/view/{render => DDDrender}/Renderer.java | 10 +++++----- .../chess/view/{render => DDDrender}/VertexArray.java | 2 +- .../{render => DDDrender}/VertexAttribPointer.java | 2 +- .../chess/view/{render => DDDrender}/VertexBuffer.java | 2 +- .../java/chess/view/{render => DDDrender}/Window.java | 6 +++++- .../view/{render => DDDrender}/shader/BoardShader.java | 2 +- .../{render => DDDrender}/shader/ShaderProgram.java | 2 +- 12 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/chess/OpenGLMain.java rename app/src/main/java/chess/view/{render => DDDrender}/Camera.java (98%) rename app/src/main/java/chess/view/{render => DDDrender}/ElementBuffer.java (95%) rename app/src/main/java/chess/view/{render => DDDrender}/Renderer.java (95%) rename app/src/main/java/chess/view/{render => DDDrender}/VertexArray.java (96%) rename app/src/main/java/chess/view/{render => DDDrender}/VertexAttribPointer.java (71%) rename app/src/main/java/chess/view/{render => DDDrender}/VertexBuffer.java (97%) rename app/src/main/java/chess/view/{render => DDDrender}/Window.java (97%) rename app/src/main/java/chess/view/{render => DDDrender}/shader/BoardShader.java (96%) rename app/src/main/java/chess/view/{render => DDDrender}/shader/ShaderProgram.java (98%) diff --git a/.gitattributes b/.gitattributes index 097f9f9..c3786a9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,7 +3,6 @@ # # Linux start script should use lf /gradlew text eol=lf - # These are Windows script files and should use crlf *.bat text eol=crlf - +*.glb filter=lfs diff=lfs merge=lfs -text diff --git a/app/src/main/java/chess/App.java b/app/src/main/java/chess/App.java index 2de48b3..0fd5919 100644 --- a/app/src/main/java/chess/App.java +++ b/app/src/main/java/chess/App.java @@ -1,16 +1,17 @@ package chess; -import chess.view.consolerender.Colors; - import java.util.Scanner; +import chess.view.consolerender.Colors; + public class App { public static void main(String[] args) { System.out.println(Colors.RED + "Credits: Grenier Lilas, Pribylski Simon." + Colors.RESET); System.out.println(""" Pick the version to use: 1 - Console - 2 - Window."""); + 2 - Window + 3 - 3D."""); switch (new Scanner(System.in).nextLine()) { case "1", "Console", "console": ConsoleMain.main(args); @@ -18,6 +19,9 @@ public class App { case "2", "Window", "window": SwingMain.main(args); break; + case "3", "3D", "3d": + OpenGLMain.main(args); + break; default: System.out.println("Invalid input"); break; diff --git a/app/src/main/java/chess/OpenGLMain.java b/app/src/main/java/chess/OpenGLMain.java new file mode 100644 index 0000000..3929ae5 --- /dev/null +++ b/app/src/main/java/chess/OpenGLMain.java @@ -0,0 +1,9 @@ +package chess; + +import chess.view.DDDrender.Window; + +public class OpenGLMain { + public static void main(String[] args) { + new Window().run(); + } +} diff --git a/app/src/main/java/chess/view/render/Camera.java b/app/src/main/java/chess/view/DDDrender/Camera.java similarity index 98% rename from app/src/main/java/chess/view/render/Camera.java rename to app/src/main/java/chess/view/DDDrender/Camera.java index 58a403a..8efdbeb 100644 --- a/app/src/main/java/chess/view/render/Camera.java +++ b/app/src/main/java/chess/view/DDDrender/Camera.java @@ -1,4 +1,4 @@ -package chess.view.render; +package chess.view.DDDrender; import org.joml.Matrix4f; import org.joml.Vector3f; diff --git a/app/src/main/java/chess/view/render/ElementBuffer.java b/app/src/main/java/chess/view/DDDrender/ElementBuffer.java similarity index 95% rename from app/src/main/java/chess/view/render/ElementBuffer.java rename to app/src/main/java/chess/view/DDDrender/ElementBuffer.java index 2ac7746..fc55725 100644 --- a/app/src/main/java/chess/view/render/ElementBuffer.java +++ b/app/src/main/java/chess/view/DDDrender/ElementBuffer.java @@ -1,4 +1,4 @@ -package chess.view.render; +package chess.view.DDDrender; import org.lwjgl.opengl.GL30; diff --git a/app/src/main/java/chess/view/render/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java similarity index 95% rename from app/src/main/java/chess/view/render/Renderer.java rename to app/src/main/java/chess/view/DDDrender/Renderer.java index 3ddeed8..2033785 100644 --- a/app/src/main/java/chess/view/render/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -1,11 +1,11 @@ -package chess.view.render; +package chess.view.DDDrender; + +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; import org.joml.Vector3f; -import org.lwjgl.opengl.*; +import org.lwjgl.opengl.GL30; -import chess.view.render.shader.BoardShader; - -import static org.lwjgl.opengl.GL30.*; +import chess.view.DDDrender.shader.BoardShader; public class Renderer { private BoardShader shader; diff --git a/app/src/main/java/chess/view/render/VertexArray.java b/app/src/main/java/chess/view/DDDrender/VertexArray.java similarity index 96% rename from app/src/main/java/chess/view/render/VertexArray.java rename to app/src/main/java/chess/view/DDDrender/VertexArray.java index 13bd712..85d879a 100644 --- a/app/src/main/java/chess/view/render/VertexArray.java +++ b/app/src/main/java/chess/view/DDDrender/VertexArray.java @@ -1,4 +1,4 @@ -package chess.view.render; +package chess.view.DDDrender; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/chess/view/render/VertexAttribPointer.java b/app/src/main/java/chess/view/DDDrender/VertexAttribPointer.java similarity index 71% rename from app/src/main/java/chess/view/render/VertexAttribPointer.java rename to app/src/main/java/chess/view/DDDrender/VertexAttribPointer.java index b2335eb..f4af07e 100644 --- a/app/src/main/java/chess/view/render/VertexAttribPointer.java +++ b/app/src/main/java/chess/view/DDDrender/VertexAttribPointer.java @@ -1,4 +1,4 @@ -package chess.view.render; +package chess.view.DDDrender; public record VertexAttribPointer(int index, int size, int offset) { diff --git a/app/src/main/java/chess/view/render/VertexBuffer.java b/app/src/main/java/chess/view/DDDrender/VertexBuffer.java similarity index 97% rename from app/src/main/java/chess/view/render/VertexBuffer.java rename to app/src/main/java/chess/view/DDDrender/VertexBuffer.java index 1e6f347..1995736 100644 --- a/app/src/main/java/chess/view/render/VertexBuffer.java +++ b/app/src/main/java/chess/view/DDDrender/VertexBuffer.java @@ -1,4 +1,4 @@ -package chess.view.render; +package chess.view.DDDrender; import static org.lwjgl.opengl.GL11.GL_FLOAT; diff --git a/app/src/main/java/chess/view/render/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java similarity index 97% rename from app/src/main/java/chess/view/render/Window.java rename to app/src/main/java/chess/view/DDDrender/Window.java index 6855dea..e2b8ff7 100644 --- a/app/src/main/java/chess/view/render/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -1,4 +1,4 @@ -package chess.view.render; +package chess.view.DDDrender; import org.lwjgl.*; import org.lwjgl.glfw.*; @@ -26,6 +26,10 @@ public class Window { this.cam = new Camera(); } + public static void main(String[] args) { + new Window().run(); + } + public void run() { System.out.println("LWJGL " + Version.getVersion() + "!"); diff --git a/app/src/main/java/chess/view/render/shader/BoardShader.java b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java similarity index 96% rename from app/src/main/java/chess/view/render/shader/BoardShader.java rename to app/src/main/java/chess/view/DDDrender/shader/BoardShader.java index dee6299..cc3fdbe 100644 --- a/app/src/main/java/chess/view/render/shader/BoardShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java @@ -1,4 +1,4 @@ -package chess.view.render.shader; +package chess.view.DDDrender.shader; import org.joml.Matrix4f; diff --git a/app/src/main/java/chess/view/render/shader/ShaderProgram.java b/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java similarity index 98% rename from app/src/main/java/chess/view/render/shader/ShaderProgram.java rename to app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java index 5853f75..cfeb07f 100644 --- a/app/src/main/java/chess/view/render/shader/ShaderProgram.java +++ b/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java @@ -1,4 +1,4 @@ -package chess.view.render.shader; +package chess.view.DDDrender.shader; import java.nio.FloatBuffer; import java.nio.IntBuffer; From ce0424ff8b3eb72debf9f8cbb9c72107c58a076a Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Tue, 22 Apr 2025 17:05:26 +0200 Subject: [PATCH 02/40] osekour --- app/build.gradle | 2 + .../chess/view/DDDrender/ModelLoader.java | 141 ++++++++++++++++++ .../java/chess/view/DDDrender/Window.java | 6 + 3 files changed, 149 insertions(+) create mode 100644 app/src/main/java/chess/view/DDDrender/ModelLoader.java diff --git a/app/build.gradle b/app/build.gradle index e3a8f95..dd7732b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,11 +26,13 @@ dependencies { implementation "org.lwjgl:lwjgl:$lwjgl_version" implementation "org.lwjgl:lwjgl-opengl:$lwjgl_version" implementation "org.lwjgl:lwjgl-glfw:$lwjgl_version" + implementation "org.lwjgl:lwjgl-assimp:$lwjgl_version" implementation "org.joml:joml:1.10.8" implementation "org.lwjgl:lwjgl::$lwjgl_natives" implementation "org.lwjgl:lwjgl-opengl::$lwjgl_natives" implementation "org.lwjgl:lwjgl-glfw::$lwjgl_natives" + implementation "org.lwjgl:lwjgl-assimp::$lwjgl_natives" } application { diff --git a/app/src/main/java/chess/view/DDDrender/ModelLoader.java b/app/src/main/java/chess/view/DDDrender/ModelLoader.java new file mode 100644 index 0000000..d77cf4c --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/ModelLoader.java @@ -0,0 +1,141 @@ +package chess.view.DDDrender; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; + +import org.lwjgl.PointerBuffer; +import org.lwjgl.assimp.AIFace.Buffer; +import org.lwjgl.assimp.AIMesh; +import org.lwjgl.assimp.AINode; +import org.lwjgl.assimp.AIScene; +import org.lwjgl.assimp.AIVector3D; +import org.lwjgl.assimp.Assimp; +import org.lwjgl.system.MemoryUtil; + +import chess.view.AssetManager; + +public class ModelLoader { + + private static final int VERTEX_SIZE = 3; + private static final int UV_SIZE = 2; + private static final int VERTEX_POSITION_INDEX = 0; + private static final int VERTEX_UV_INDEX = 1; + private static final int VERTEX_NORMAL_INDEX = 2; + + private static float[] toFloatArray(List list) { + float[] result = new float[list.size()]; + for (int i = 0; i < list.size(); i++) { + result[i] = list.get(i); + } + return result; + } + + private static int[] toIntArray(List list) { + int[] result = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + result[i] = list.get(i); + } + return result; + } + + private static VertexArray processMesh(AIMesh mesh, AIScene scene) { + List positions = new ArrayList<>(); + List textureCoords = new ArrayList<>(); + List normals = new ArrayList<>(); + + List indicies = new ArrayList<>(); + + Buffer faces = mesh.mFaces(); + int faceNumber = mesh.mNumFaces(); + + for (int i = 0; i < faceNumber; i++) { + int offset = indicies.size(); + int numIndices = faces.get(i).mNumIndices(); + IntBuffer faceIndicies = faces.get(i).mIndices(); + // IntStream.of(faceIndicies.array()).forEach(indicies::add); + for (int j = 0; j < faceIndicies.capacity(); j++) { + indicies.add(faceIndicies.get(j)); + } + } + + int vertNumber = mesh.mNumVertices(); + org.lwjgl.assimp.AIVector3D.Buffer vertecies = mesh.mVertices(); + for (int i = 0; i < vertNumber; i++) { + AIVector3D vertex = vertecies.get(i); + positions.add(vertex.x()); + positions.add(vertex.y()); + positions.add(vertex.z()); + } + + org.lwjgl.assimp.AIVector3D.Buffer vertexNormals = mesh.mNormals(); + for (int i = 0; i < vertNumber; i++) { + AIVector3D normal = vertexNormals.get(i); + normals.add(normal.x()); + normals.add(normal.y()); + normals.add(normal.z()); + } + + PointerBuffer vertexTexture = mesh.mTextureCoords(); + for (int i = 0; i < vertNumber; i++) { + // PointerBuffer buff = mesh.mTextureCoords(); + // textureCoords.add(buff.get(i).x()); + // textureCoords.add(buff.get(i).y()); + } + + VertexBuffer positionVBO = new VertexBuffer(toFloatArray(positions), vertNumber); + positionVBO.AddVertexAttribPointer(VERTEX_POSITION_INDEX, VERTEX_SIZE, 0); + VertexBuffer textureVBO = new VertexBuffer(toFloatArray(positions), vertNumber); + textureVBO.AddVertexAttribPointer(VERTEX_UV_INDEX, UV_SIZE, 0); + VertexBuffer normalVBO = new VertexBuffer(toFloatArray(positions), vertNumber); + normalVBO.AddVertexAttribPointer(VERTEX_NORMAL_INDEX, VERTEX_SIZE, 0); + + VertexArray vao = new VertexArray(new ElementBuffer(toIntArray(indicies))); + vao.Bind(); + vao.BindVertexBuffer(positionVBO); + vao.BindVertexBuffer(textureVBO); + vao.BindVertexBuffer(normalVBO); + vao.Unbind(); + return vao; + + } + + private static void processNode(AINode node, AIScene scene, List meshes) { + for (int i = 0; i < node.mNumChildren(); i++) { + AINode child = AINode.create(node.mChildren().get(i)); + processNode(child, scene, meshes); + } + for (int i = 0; i < node.mNumMeshes(); i++) { + AIMesh mesh = AIMesh.create(scene.mMeshes().get(node.mMeshes().get(i))); + meshes.add(processMesh(mesh, scene)); + } + } + + public static List loadModel(String filename) throws IOException { + InputStream input = AssetManager.getResource(filename); + byte[] buffer = input.readAllBytes(); + ByteBuffer data = MemoryUtil.memCalloc(buffer.length); + data.put(buffer); + data.flip(); + + AIScene scene = Assimp.aiImportFileFromMemory( + data, + Assimp.aiProcess_Triangulate | Assimp.aiProcess_PreTransformVertices | + Assimp.aiProcess_ValidateDataStructure, + ""); + + List vertecies = new ArrayList<>(); + + processNode(scene.mRootNode(), scene, vertecies); + + MemoryUtil.memFree(data); + + return vertecies; + + } + +} diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index e2b8ff7..a1f6edd 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -5,6 +5,7 @@ import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; import org.lwjgl.system.*; +import java.io.IOException; import java.nio.*; import static org.lwjgl.glfw.Callbacks.*; @@ -24,6 +25,11 @@ public class Window { public Window() { this.renderer = new Renderer(); this.cam = new Camera(); + try { + ModelLoader.loadModel("3d/bishop.glb"); + } catch (IOException e) { + e.printStackTrace(); + } } public static void main(String[] args) { From 098b605799f983ea3a2f4b6eabc0c39953560623 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Thu, 24 Apr 2025 11:12:53 +0200 Subject: [PATCH 03/40] yodaaaaaa --- .../java/chess/view/DDDrender/Camera.java | 6 +- .../java/chess/view/DDDrender/DDDModel.java | 16 +++++ .../chess/view/DDDrender/DDDPlacement.java | 20 ++++++ .../chess/view/DDDrender/ModelLoader.java | 10 +-- .../java/chess/view/DDDrender/Renderer.java | 54 +++++++++++++--- .../java/chess/view/DDDrender/Window.java | 7 +-- .../view/DDDrender/shader/PieceShader.java | 61 +++++++++++++++++++ 7 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/chess/view/DDDrender/DDDModel.java create mode 100644 app/src/main/java/chess/view/DDDrender/DDDPlacement.java create mode 100644 app/src/main/java/chess/view/DDDrender/shader/PieceShader.java diff --git a/app/src/main/java/chess/view/DDDrender/Camera.java b/app/src/main/java/chess/view/DDDrender/Camera.java index 8efdbeb..73be15e 100644 --- a/app/src/main/java/chess/view/DDDrender/Camera.java +++ b/app/src/main/java/chess/view/DDDrender/Camera.java @@ -8,7 +8,7 @@ public class Camera { // should be changed to match screen public static final float aspect = 1.0f; public static final float zNear = 0.01f; - public static final float zFar = 100.0f; + public static final float zFar = 1000.0f; private Vector3f pos; @@ -16,7 +16,7 @@ public class Camera { private float pitch = 0.0f; public Camera() { - this.pos = new Vector3f(0, 2.0f, 0); + this.pos = new Vector3f(1, 1.0f, 0); setRotation(0.0f, -3.14150f / 2.0f); } @@ -123,6 +123,6 @@ public class Camera { return new Matrix4f() .perspective((float) (Math.toRadians(fov)), aspect, zNear, zFar) - .lookAt(pos, forward, new Vector3f(0.0f, 1.0f, 0.0f)); + .lookAt(pos, new Vector3f(0.0f, 0, 0), new Vector3f(0.0f, 1.0f, 0.0f)); } } diff --git a/app/src/main/java/chess/view/DDDrender/DDDModel.java b/app/src/main/java/chess/view/DDDrender/DDDModel.java new file mode 100644 index 0000000..577daab --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/DDDModel.java @@ -0,0 +1,16 @@ +package chess.view.DDDrender; + +import java.util.List; + +public class DDDModel { + private final List vaos; + + public DDDModel(List vaos) { + this.vaos = vaos; + } + + public List getVaos() { + return vaos; + } + +} diff --git a/app/src/main/java/chess/view/DDDrender/DDDPlacement.java b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java new file mode 100644 index 0000000..4efed75 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java @@ -0,0 +1,20 @@ +package chess.view.DDDrender; + +import org.joml.Vector2f; + +import chess.model.Coordinate; + +class DDDPlacement { + static public Vector2f coordinates_to_vector(Coordinate coo) { + // float newX = switch (x) { + // case 0 -> -1 + 0.125f; + // case 1 -> -1 + 0.375f; + // case 2 -> -1 + 0.625f; + // case 3 -> -1 + 0.875f; + + // default -> 0; + // }; + return new Vector2f(-1.0f + 0.125f + coo.getX() * 0.250f, -1.0f + 0.125f + coo.getY() * 0.250f); + } + +} \ No newline at end of file diff --git a/app/src/main/java/chess/view/DDDrender/ModelLoader.java b/app/src/main/java/chess/view/DDDrender/ModelLoader.java index d77cf4c..de7ec87 100644 --- a/app/src/main/java/chess/view/DDDrender/ModelLoader.java +++ b/app/src/main/java/chess/view/DDDrender/ModelLoader.java @@ -87,11 +87,11 @@ public class ModelLoader { // textureCoords.add(buff.get(i).y()); } - VertexBuffer positionVBO = new VertexBuffer(toFloatArray(positions), vertNumber); + VertexBuffer positionVBO = new VertexBuffer(toFloatArray(positions), VERTEX_SIZE); positionVBO.AddVertexAttribPointer(VERTEX_POSITION_INDEX, VERTEX_SIZE, 0); - VertexBuffer textureVBO = new VertexBuffer(toFloatArray(positions), vertNumber); + VertexBuffer textureVBO = new VertexBuffer(toFloatArray(textureCoords), UV_SIZE); textureVBO.AddVertexAttribPointer(VERTEX_UV_INDEX, UV_SIZE, 0); - VertexBuffer normalVBO = new VertexBuffer(toFloatArray(positions), vertNumber); + VertexBuffer normalVBO = new VertexBuffer(toFloatArray(normals), VERTEX_SIZE); normalVBO.AddVertexAttribPointer(VERTEX_NORMAL_INDEX, VERTEX_SIZE, 0); VertexArray vao = new VertexArray(new ElementBuffer(toIntArray(indicies))); @@ -115,7 +115,7 @@ public class ModelLoader { } } - public static List loadModel(String filename) throws IOException { + public static DDDModel loadModel(String filename) throws IOException { InputStream input = AssetManager.getResource(filename); byte[] buffer = input.readAllBytes(); ByteBuffer data = MemoryUtil.memCalloc(buffer.length); @@ -134,7 +134,7 @@ public class ModelLoader { MemoryUtil.memFree(data); - return vertecies; + return new DDDModel(vertecies); } diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 2033785..d784b9d 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -1,15 +1,25 @@ package chess.view.DDDrender; +import static org.lwjgl.opengl.GL11.GL_FLOAT; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import java.io.IOException; + +import org.joml.Matrix4f; +import org.joml.Vector2f; import org.joml.Vector3f; import org.lwjgl.opengl.GL30; +import chess.model.Coordinate; import chess.view.DDDrender.shader.BoardShader; +import chess.view.DDDrender.shader.PieceShader; +import chess.view.DDDrender.shader.ShaderProgram; public class Renderer { - private BoardShader shader; + private BoardShader boardShader; + private PieceShader pieceShader; private VertexArray vao; + private DDDModel yoda; private static int BOARD_WIDTH = 8; private static int BOARD_HEIGHT = 8; @@ -17,12 +27,19 @@ public class Renderer { private static int SQUARE_VERTEX_COUNT = 4; public Renderer() { - this.shader = new BoardShader(); + this.boardShader = new BoardShader(); + this.pieceShader = new PieceShader(); } public void Init() { - shader.LoadShader(); + boardShader.LoadShader(); + pieceShader.LoadShader(); InitBoard(); + try { + this.yoda = ModelLoader.loadModel("3d/king_yoda.glb"); + } catch (IOException e) { + e.printStackTrace(); + } } private float[] GetBoardPositions() { @@ -110,13 +127,34 @@ public class Renderer { } public void Render(Camera cam) { - this.shader.Start(); - this.shader.SetCamMatrix(cam.getMatrix()); - RenderVao(vao); + this.boardShader.Start(); + this.boardShader.SetCamMatrix(cam.getMatrix()); + this.pieceShader.Start(); + this.pieceShader.SetCamMatrix(cam.getMatrix()); + RenderVao(this.boardShader, vao); + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + Render(yoda, DDDPlacement.coordinates_to_vector(new Coordinate(i, j))); + } + } } - public void RenderVao(VertexArray vertexArray) { - this.shader.Start(); + public void Render(DDDModel model, Vector2f position) { + Vector3f realPos = new Vector3f(position.x(), 0, position.y()); + this.pieceShader.Start(); + this.pieceShader.setModelTransform(new Matrix4f().translate(realPos)); + Render(model); + } + + public void Render(DDDModel model) { + for (int i = 0; i < model.getVaos().size(); i++) { + VertexArray vao = model.getVaos().get(i); + RenderVao(this.pieceShader, vao); + } + } + + public void RenderVao(ShaderProgram shader, VertexArray vertexArray) { + shader.Start(); vertexArray.Bind(); GL30.glDrawElements(GL30.GL_TRIANGLES, vertexArray.GetVertexCount(), GL_UNSIGNED_INT, 0); vertexArray.Unbind(); diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index a1f6edd..d99f7ee 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -25,11 +25,6 @@ public class Window { public Window() { this.renderer = new Renderer(); this.cam = new Camera(); - try { - ModelLoader.loadModel("3d/bishop.glb"); - } catch (IOException e) { - e.printStackTrace(); - } } public static void main(String[] args) { @@ -98,7 +93,7 @@ public class Window { } private void render() { - cam.rotate(0.01f, 0.01f); + cam.move(0.001f, 0.001f); renderer.Render(cam); } diff --git a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java new file mode 100644 index 0000000..5b89450 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java @@ -0,0 +1,61 @@ +package chess.view.DDDrender.shader; + +import org.joml.Matrix4f; + +public class PieceShader extends ShaderProgram { + + private static String vertexShader = """ + #version 330 + + layout(location = 0) in vec3 position; + layout(location = 1) in vec2 uv; + layout(location = 2) in vec3 normal; + + uniform mat4 camMatrix; + uniform mat4 modelTransform; + + flat out vec3 pass_color; + + void main(void){ + gl_Position = camMatrix * modelTransform * vec4(position, 1.0); + pass_color = vec3(1, 0, 1); + } + """; + + private static String fragmentShader = """ + #version 330 + + flat in vec3 pass_color; + + out vec4 out_color; + + void main(void){ + out_color = vec4(pass_color, 1.0); + } + """; + + private int location_CamMatrix = 0; + private int location_ModelTransform = 0; + + public PieceShader() { + + } + + public void LoadShader() { + super.LoadProgram(vertexShader, fragmentShader); + } + + @Override + protected void GetAllUniformLocation() { + location_CamMatrix = GetUniformLocation("camMatrix"); + location_ModelTransform = GetUniformLocation("modelTransform"); + } + + public void SetCamMatrix(Matrix4f mat) { + LoadMat4(location_CamMatrix, mat); + } + + public void setModelTransform(Matrix4f mat) { + LoadMat4(location_ModelTransform, mat); + } +} From cff2d9207037ae2317630007b0f76d732de501bd Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Fri, 25 Apr 2025 17:03:28 +0200 Subject: [PATCH 04/40] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --- app/src/main/java/chess/OpenGLMain.java | 15 +++- .../java/chess/view/DDDrender/Camera.java | 2 +- .../java/chess/view/DDDrender/DDDView.java | 22 ++++++ .../java/chess/view/DDDrender/PieceModel.java | 73 +++++++++++++++++++ .../java/chess/view/DDDrender/Renderer.java | 28 ++++--- .../java/chess/view/DDDrender/Window.java | 39 ++++++++-- .../view/DDDrender/shader/PieceShader.java | 2 +- 7 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/chess/view/DDDrender/DDDView.java create mode 100644 app/src/main/java/chess/view/DDDrender/PieceModel.java diff --git a/app/src/main/java/chess/OpenGLMain.java b/app/src/main/java/chess/OpenGLMain.java index 3929ae5..30ce74e 100644 --- a/app/src/main/java/chess/OpenGLMain.java +++ b/app/src/main/java/chess/OpenGLMain.java @@ -1,9 +1,20 @@ package chess; -import chess.view.DDDrender.Window; +import chess.controller.CommandExecutor; +import chess.controller.commands.NewGameCommand; +import chess.model.Game; +import chess.view.DDDrender.DDDView; public class OpenGLMain { public static void main(String[] args) { - new Window().run(); + Game game = new Game(); + CommandExecutor commandExecutor = new CommandExecutor(game); + + DDDView ddd = new DDDView(commandExecutor); + commandExecutor.addListener(ddd); + + commandExecutor.executeCommand(new NewGameCommand()); + + ddd.run(); } } diff --git a/app/src/main/java/chess/view/DDDrender/Camera.java b/app/src/main/java/chess/view/DDDrender/Camera.java index 73be15e..db76bdb 100644 --- a/app/src/main/java/chess/view/DDDrender/Camera.java +++ b/app/src/main/java/chess/view/DDDrender/Camera.java @@ -16,7 +16,7 @@ public class Camera { private float pitch = 0.0f; public Camera() { - this.pos = new Vector3f(1, 1.0f, 0); + this.pos = new Vector3f(2, 2.0f, 0); setRotation(0.0f, -3.14150f / 2.0f); } diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java new file mode 100644 index 0000000..cbbd8ef --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -0,0 +1,22 @@ +package chess.view.DDDrender; + +import chess.controller.CommandExecutor; +import chess.controller.event.GameAdaptator; + +public class DDDView extends GameAdaptator{ + + private final CommandExecutor commandExecutor; + private final Window window; + private final Renderer renderer; + + public DDDView(CommandExecutor commandExecutor) { + this.commandExecutor = commandExecutor; + this.renderer = new Renderer(); + this.window = new Window(commandExecutor, this.renderer); + } + + public void run() { + this.window.run(); + } + +} diff --git a/app/src/main/java/chess/view/DDDrender/PieceModel.java b/app/src/main/java/chess/view/DDDrender/PieceModel.java new file mode 100644 index 0000000..55b067c --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/PieceModel.java @@ -0,0 +1,73 @@ +package chess.view.DDDrender; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import chess.model.Color; +import chess.model.Piece; +import chess.model.PieceVisitor; +import chess.model.pieces.Bishop; +import chess.model.pieces.King; +import chess.model.pieces.Knight; +import chess.model.pieces.Pawn; +import chess.model.pieces.Queen; +import chess.model.pieces.Rook; + +public class PieceModel implements PieceVisitor { + + private static final String basePath = "3d/"; + private static final Map cache = new HashMap<>(); + + public DDDModel getModel(Piece piece) throws IOException { + if (piece == null) + return null; + String path = basePath + colorToString(piece.getColor()) + "-" + visit(piece) + ".glb"; + return getModel(path); + } + + private DDDModel getModel(String path) throws IOException { + DDDModel model = cache.get(path); + if (model != null) + return model; + + model = ModelLoader.loadModel(path); + cache.put(path, model); + return model; + } + + private String colorToString(Color color) { + return color == Color.Black ? "black" : "white"; + } + + @Override + public String visitPiece(Bishop bishop) { + return "bishop"; + } + + @Override + public String visitPiece(King king) { + return "knight"; + } + + @Override + public String visitPiece(Knight knight) { + return "knight"; + } + + @Override + public String visitPiece(Pawn pawn) { + return "pawn"; + } + + @Override + public String visitPiece(Queen queen) { + return "queen"; + } + + @Override + public String visitPiece(Rook rook) { + return "rook"; + } + +} diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index d784b9d..b80f0ec 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -1,7 +1,8 @@ package chess.view.DDDrender; -import static org.lwjgl.opengl.GL11.GL_FLOAT; +import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL11.glEnable; import java.io.IOException; @@ -11,6 +12,7 @@ import org.joml.Vector3f; import org.lwjgl.opengl.GL30; import chess.model.Coordinate; +import chess.model.Piece; import chess.view.DDDrender.shader.BoardShader; import chess.view.DDDrender.shader.PieceShader; import chess.view.DDDrender.shader.ShaderProgram; @@ -19,7 +21,7 @@ public class Renderer { private BoardShader boardShader; private PieceShader pieceShader; private VertexArray vao; - private DDDModel yoda; + private final PieceModel models; private static int BOARD_WIDTH = 8; private static int BOARD_HEIGHT = 8; @@ -29,17 +31,14 @@ public class Renderer { public Renderer() { this.boardShader = new BoardShader(); this.pieceShader = new PieceShader(); + this.models = new PieceModel(); } public void Init() { boardShader.LoadShader(); pieceShader.LoadShader(); + glEnable(GL_DEPTH_TEST); InitBoard(); - try { - this.yoda = ModelLoader.loadModel("3d/king_yoda.glb"); - } catch (IOException e) { - e.printStackTrace(); - } } private float[] GetBoardPositions() { @@ -126,17 +125,22 @@ public class Renderer { this.vao.Unbind(); } + public void RenderPiece(Piece piece, Coordinate pos) { + try { + DDDModel pieceModel = this.models.getModel(piece); + Render(pieceModel, DDDPlacement.coordinates_to_vector(pos)); + } catch (IOException e) { + e.printStackTrace(); + } + } + public void Render(Camera cam) { + GL30.glClear(GL30.GL_DEPTH_BUFFER_BIT); this.boardShader.Start(); this.boardShader.SetCamMatrix(cam.getMatrix()); this.pieceShader.Start(); this.pieceShader.SetCamMatrix(cam.getMatrix()); RenderVao(this.boardShader, vao); - for (int i = 0; i < 8; i++) { - for (int j = 0; j < 8; j++) { - Render(yoda, DDDPlacement.coordinates_to_vector(new Coordinate(i, j))); - } - } } public void Render(DDDModel model, Vector2f position) { diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index d99f7ee..d51ea6c 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -1,10 +1,16 @@ package chess.view.DDDrender; +import org.joml.Vector3f; import org.lwjgl.*; import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; import org.lwjgl.system.*; +import chess.controller.CommandExecutor; +import chess.controller.commands.GetPieceAtCommand; +import chess.model.Coordinate; +import chess.model.Piece; + import java.io.IOException; import java.nio.*; @@ -21,14 +27,12 @@ public class Window { private Renderer renderer; private Camera cam; + private final CommandExecutor commandExecutor; - public Window() { + public Window(CommandExecutor commandExecutor, Renderer renderer) { this.renderer = new Renderer(); this.cam = new Camera(); - } - - public static void main(String[] args) { - new Window().run(); + this.commandExecutor = commandExecutor; } public void run() { @@ -93,8 +97,31 @@ public class Window { } private void render() { - cam.move(0.001f, 0.001f); + final float angle = 0.01f; + float x = cam.getPos().x(); + float y = cam.getPos().z(); + cam.setPosition(new Vector3f(x * (float) Math.cos(angle) - y * (float) Math.sin(angle), 1.0f, + x * (float) Math.sin(angle) + y * (float) Math.cos(angle))); renderer.Render(cam); + renderPieces(); + } + + private Piece pieceAt(Coordinate pos) { + GetPieceAtCommand cmd = new GetPieceAtCommand(pos); + this.commandExecutor.executeCommand(cmd); + return cmd.getPiece(); + } + + private void renderPieces() { + for (int i = 0; i < Coordinate.VALUE_MAX; i++) { + for (int j = 0; j < Coordinate.VALUE_MAX; j++) { + Coordinate pos = new Coordinate(i, j); + Piece piece = pieceAt(pos); + if (piece == null) + continue; + this.renderer.RenderPiece(pieceAt(pos), pos); + } + } } private void loop() { diff --git a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java index 5b89450..dd2e293 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java @@ -18,7 +18,7 @@ public class PieceShader extends ShaderProgram { void main(void){ gl_Position = camMatrix * modelTransform * vec4(position, 1.0); - pass_color = vec3(1, 0, 1); + pass_color = position; } """; From 7af980712703ba9a33c997d48b77584801846296 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 26 Apr 2025 12:00:48 +0200 Subject: [PATCH 05/40] =?UTF-8?q?omg=20=C3=A7a=20marche?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 1 + app/src/main/java/chess/OpenGLMain.java | 1 + .../java/chess/view/DDDrender/Camera.java | 2 +- .../chess/view/DDDrender/DDDPlacement.java | 10 +----- .../chess/view/DDDrender/ModelLoader.java | 7 +++-- .../java/chess/view/DDDrender/PieceModel.java | 5 +-- .../java/chess/view/DDDrender/Renderer.java | 2 +- .../view/DDDrender/shader/BoardShader.java | 31 ++++++++++++++++++- .../view/DDDrender/shader/PieceShader.java | 25 +++++++++++++-- app/src/main/resources/3d/black-bishop.fbx | 3 ++ app/src/main/resources/3d/black-king.fbx | 3 ++ app/src/main/resources/3d/black-knight.fbx | 3 ++ app/src/main/resources/3d/black-pawn.fbx | 3 ++ app/src/main/resources/3d/black-queen.fbx | 3 ++ app/src/main/resources/3d/black-rook.fbx | 3 ++ app/src/main/resources/3d/white-bishop.fbx | 3 ++ app/src/main/resources/3d/white-king.fbx | 3 ++ app/src/main/resources/3d/white-knight.fbx | 3 ++ app/src/main/resources/3d/white-pawn.fbx | 3 ++ app/src/main/resources/3d/white-queen.fbx | 3 ++ app/src/main/resources/3d/white-rook.fbx | 3 ++ 21 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 app/src/main/resources/3d/black-bishop.fbx create mode 100644 app/src/main/resources/3d/black-king.fbx create mode 100644 app/src/main/resources/3d/black-knight.fbx create mode 100644 app/src/main/resources/3d/black-pawn.fbx create mode 100644 app/src/main/resources/3d/black-queen.fbx create mode 100644 app/src/main/resources/3d/black-rook.fbx create mode 100644 app/src/main/resources/3d/white-bishop.fbx create mode 100644 app/src/main/resources/3d/white-king.fbx create mode 100644 app/src/main/resources/3d/white-knight.fbx create mode 100644 app/src/main/resources/3d/white-pawn.fbx create mode 100644 app/src/main/resources/3d/white-queen.fbx create mode 100644 app/src/main/resources/3d/white-rook.fbx diff --git a/.gitattributes b/.gitattributes index c3786a9..4884201 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,4 @@ # These are Windows script files and should use crlf *.bat text eol=crlf *.glb filter=lfs diff=lfs merge=lfs -text +*.fbx filter=lfs diff=lfs merge=lfs -text diff --git a/app/src/main/java/chess/OpenGLMain.java b/app/src/main/java/chess/OpenGLMain.java index 30ce74e..b3e6195 100644 --- a/app/src/main/java/chess/OpenGLMain.java +++ b/app/src/main/java/chess/OpenGLMain.java @@ -16,5 +16,6 @@ public class OpenGLMain { commandExecutor.executeCommand(new NewGameCommand()); ddd.run(); + commandExecutor.close(); } } diff --git a/app/src/main/java/chess/view/DDDrender/Camera.java b/app/src/main/java/chess/view/DDDrender/Camera.java index db76bdb..a3bc9ae 100644 --- a/app/src/main/java/chess/view/DDDrender/Camera.java +++ b/app/src/main/java/chess/view/DDDrender/Camera.java @@ -16,7 +16,7 @@ public class Camera { private float pitch = 0.0f; public Camera() { - this.pos = new Vector3f(2, 2.0f, 0); + this.pos = new Vector3f(1.5f, 1.5f, 0); setRotation(0.0f, -3.14150f / 2.0f); } diff --git a/app/src/main/java/chess/view/DDDrender/DDDPlacement.java b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java index 4efed75..cf35bcb 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDPlacement.java +++ b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java @@ -6,15 +6,7 @@ import chess.model.Coordinate; class DDDPlacement { static public Vector2f coordinates_to_vector(Coordinate coo) { - // float newX = switch (x) { - // case 0 -> -1 + 0.125f; - // case 1 -> -1 + 0.375f; - // case 2 -> -1 + 0.625f; - // case 3 -> -1 + 0.875f; - - // default -> 0; - // }; - return new Vector2f(-1.0f + 0.125f + coo.getX() * 0.250f, -1.0f + 0.125f + coo.getY() * 0.250f); + return new Vector2f(1.0f - 0.125f - coo.getX() * 0.250f, 1.0f - 0.125f - coo.getY() * 0.250f); } } \ No newline at end of file diff --git a/app/src/main/java/chess/view/DDDrender/ModelLoader.java b/app/src/main/java/chess/view/DDDrender/ModelLoader.java index de7ec87..47048f1 100644 --- a/app/src/main/java/chess/view/DDDrender/ModelLoader.java +++ b/app/src/main/java/chess/view/DDDrender/ModelLoader.java @@ -124,10 +124,13 @@ public class ModelLoader { AIScene scene = Assimp.aiImportFileFromMemory( data, - Assimp.aiProcess_Triangulate | Assimp.aiProcess_PreTransformVertices | - Assimp.aiProcess_ValidateDataStructure, + Assimp.aiProcess_Triangulate | Assimp.aiProcess_PreTransformVertices | Assimp.aiProcess_GlobalScale + | Assimp.aiProcess_ValidateDataStructure, ""); + if (scene == null) + System.err.println(Assimp.aiGetErrorString()); + List vertecies = new ArrayList<>(); processNode(scene.mRootNode(), scene, vertecies); diff --git a/app/src/main/java/chess/view/DDDrender/PieceModel.java b/app/src/main/java/chess/view/DDDrender/PieceModel.java index 55b067c..e7f5571 100644 --- a/app/src/main/java/chess/view/DDDrender/PieceModel.java +++ b/app/src/main/java/chess/view/DDDrender/PieceModel.java @@ -22,7 +22,8 @@ public class PieceModel implements PieceVisitor { public DDDModel getModel(Piece piece) throws IOException { if (piece == null) return null; - String path = basePath + colorToString(piece.getColor()) + "-" + visit(piece) + ".glb"; + + String path = basePath + colorToString(piece.getColor()) + "-" + visit(piece) + ".fbx"; return getModel(path); } @@ -47,7 +48,7 @@ public class PieceModel implements PieceVisitor { @Override public String visitPiece(King king) { - return "knight"; + return "king"; } @Override diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index b80f0ec..2044733 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -80,7 +80,7 @@ public class Renderer { for (int i = 0; i < BOARD_WIDTH; i++) { for (int j = 0; j < BOARD_HEIGHT; j++) { Vector3f color; - if ((i + j) % 2 != 0) { + if ((i + j) % 2 == 0) { color = new Vector3f(1.0f, 1.0f, 1.0f); } else { color = new Vector3f(0.0f, 0.0f, 0.0f); diff --git a/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java index cc3fdbe..887ab08 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java @@ -11,13 +11,19 @@ public class BoardShader extends ShaderProgram { layout(location = 1) in vec3 color; uniform mat4 camMatrix; + uniform vec3 lightPosition; flat out vec3 pass_color; + out vec3 toLightVector; void main(void){ gl_Position = camMatrix * vec4(position, 1.0); + + toLightVector = lightPosition - position; + pass_color = color; } + """; private static String fragmentShader = """ @@ -25,11 +31,34 @@ public class BoardShader extends ShaderProgram { flat in vec3 pass_color; + in vec3 toLightVector; + out vec4 out_color; void main(void){ - out_color = vec4(pass_color, 1.0); + const float shineDamper = 10.0; + const float reflectivity = 1.0; + + float lightDistance = length(toLightVector); + + const vec3 attenuation = vec3(0.3, 0.03, 0); + float attenuationFactor = attenuation.x + attenuation.y * lightDistance + attenuation.z * lightDistance * lightDistance; + + vec3 unitNormal = vec3(0, 1, 0); + vec3 unitLightVector = normalize(toLightVector); + + vec3 lightDirection = -unitLightVector; + vec3 reflectedLightDirection = reflect(lightDirection, unitNormal); + + float diffuse = max(0.2, dot(unitNormal, unitLightVector)); + + float brightness = diffuse / attenuationFactor; + + out_color = brightness * vec4(pass_color, 1.0); + out_color.w = 1.0; + } + """; private int location_CamMatrix = 0; diff --git a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java index dd2e293..129b802 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java @@ -13,11 +13,20 @@ public class PieceShader extends ShaderProgram { uniform mat4 camMatrix; uniform mat4 modelTransform; + uniform vec3 lightPosition = vec3(0, 1, 0); + + out vec3 toLightVector; + out vec3 surfaceNormal; flat out vec3 pass_color; void main(void){ - gl_Position = camMatrix * modelTransform * vec4(position, 1.0); + vec4 worldPos = modelTransform * vec4(position, 1.0); + + toLightVector = lightPosition - worldPos.xyz; + surfaceNormal = (modelTransform * vec4(normal, 0.0)).xyz; + + gl_Position = camMatrix * worldPos; pass_color = position; } """; @@ -25,12 +34,24 @@ public class PieceShader extends ShaderProgram { private static String fragmentShader = """ #version 330 + in vec3 toLightVector; + in vec3 surfaceNormal; + flat in vec3 pass_color; out vec4 out_color; void main(void){ - out_color = vec4(pass_color, 1.0); + vec3 unitNormal = normalize(surfaceNormal); + vec3 unitLightVector = normalize(toLightVector); + + float diffuse = max(0.5, dot(unitNormal, unitLightVector)); + + float brightness = diffuse; + + out_color = vec4(pass_color, 1.0) * brightness; + out_color.w = 1.0; + } """; diff --git a/app/src/main/resources/3d/black-bishop.fbx b/app/src/main/resources/3d/black-bishop.fbx new file mode 100644 index 0000000..bd546c4 --- /dev/null +++ b/app/src/main/resources/3d/black-bishop.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd63f658fdf7aa1a84675a912b093719f7467c3b6a763d938c1068f61f7036bd +size 535276 diff --git a/app/src/main/resources/3d/black-king.fbx b/app/src/main/resources/3d/black-king.fbx new file mode 100644 index 0000000..800bb07 --- /dev/null +++ b/app/src/main/resources/3d/black-king.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aed7f6760d887baf5a902f0c5e2d4be6045de31513a4b790092d30860e69b9b5 +size 2522300 diff --git a/app/src/main/resources/3d/black-knight.fbx b/app/src/main/resources/3d/black-knight.fbx new file mode 100644 index 0000000..d34d5ab --- /dev/null +++ b/app/src/main/resources/3d/black-knight.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b6464861afdb54825032fee689abcae5d593fce6230223b8b4bfa52443f953e +size 606060 diff --git a/app/src/main/resources/3d/black-pawn.fbx b/app/src/main/resources/3d/black-pawn.fbx new file mode 100644 index 0000000..b0b54ea --- /dev/null +++ b/app/src/main/resources/3d/black-pawn.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cee8e15b189278079211f2714bdd537aaeb3b0b7388996e76da49ce58839cf9 +size 1048716 diff --git a/app/src/main/resources/3d/black-queen.fbx b/app/src/main/resources/3d/black-queen.fbx new file mode 100644 index 0000000..0229b78 --- /dev/null +++ b/app/src/main/resources/3d/black-queen.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cec1568b26c3a62135be1d910ecdfa9a390cb12d0f61a2140c757e3119700538 +size 2292300 diff --git a/app/src/main/resources/3d/black-rook.fbx b/app/src/main/resources/3d/black-rook.fbx new file mode 100644 index 0000000..4d71677 --- /dev/null +++ b/app/src/main/resources/3d/black-rook.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:130e2f687e4fd58cd0b1a05b7d004febfc67d2e7f0a9cd5f774d3bd40a98ca08 +size 439452 diff --git a/app/src/main/resources/3d/white-bishop.fbx b/app/src/main/resources/3d/white-bishop.fbx new file mode 100644 index 0000000..4ee9353 --- /dev/null +++ b/app/src/main/resources/3d/white-bishop.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09cfbbc1407082c43cda1db89788fffeac0bc4b159eccc2b30f2cb3305b83754 +size 1810972 diff --git a/app/src/main/resources/3d/white-king.fbx b/app/src/main/resources/3d/white-king.fbx new file mode 100644 index 0000000..0b895f7 --- /dev/null +++ b/app/src/main/resources/3d/white-king.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d24e89efc58f0c4a0768fe2e0b0175f4f842fa589a2faa9d8ea7b24294bb8a42 +size 601708 diff --git a/app/src/main/resources/3d/white-knight.fbx b/app/src/main/resources/3d/white-knight.fbx new file mode 100644 index 0000000..f7ccb80 --- /dev/null +++ b/app/src/main/resources/3d/white-knight.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d1b1d936c001889d133da7819fa1d567fcc1a59c085e95b2cc27fccc1981da4 +size 2804348 diff --git a/app/src/main/resources/3d/white-pawn.fbx b/app/src/main/resources/3d/white-pawn.fbx new file mode 100644 index 0000000..e16ee88 --- /dev/null +++ b/app/src/main/resources/3d/white-pawn.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d48ffec243c6a5ed0a1e8d8c84ff32c064e8ed4152857c252b28bd106692e53c +size 1672236 diff --git a/app/src/main/resources/3d/white-queen.fbx b/app/src/main/resources/3d/white-queen.fbx new file mode 100644 index 0000000..ecd726d --- /dev/null +++ b/app/src/main/resources/3d/white-queen.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ee2426381ab9e59bc4564d661a4da4069cea17df788b2edbcc3edd1c186982d +size 4496716 diff --git a/app/src/main/resources/3d/white-rook.fbx b/app/src/main/resources/3d/white-rook.fbx new file mode 100644 index 0000000..088a2b7 --- /dev/null +++ b/app/src/main/resources/3d/white-rook.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab688a54abb56900f4e3f221d5abb2dbedf0d0b7ca722e958cb3b6054ec9665e +size 1956844 From ab309ae48a183732b9c347dd7a1f7947b1a97a38 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 26 Apr 2025 12:14:22 +0200 Subject: [PATCH 06/40] fix aspectRatio --- .../main/java/chess/view/DDDrender/Camera.java | 15 +++++++-------- .../main/java/chess/view/DDDrender/Window.java | 4 ++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/Camera.java b/app/src/main/java/chess/view/DDDrender/Camera.java index a3bc9ae..08154e6 100644 --- a/app/src/main/java/chess/view/DDDrender/Camera.java +++ b/app/src/main/java/chess/view/DDDrender/Camera.java @@ -5,11 +5,11 @@ import org.joml.Vector3f; public class Camera { public static final float fov = 70.0f; - // should be changed to match screen - public static final float aspect = 1.0f; public static final float zNear = 0.01f; public static final float zFar = 1000.0f; + private float aspectRatio; + private Vector3f pos; private float yaw = 0.0f; @@ -115,14 +115,13 @@ public class Camera { this.pitch = pitch; } - public Matrix4f getMatrix() { - Vector3f forward = new Vector3f( - (float) (Math.cos(yaw) * Math.cos(pitch)), - (float) (Math.sin(pitch)), - (float) (Math.sin(yaw) * Math.cos(pitch))); + public void setAspectRatio(float aspectRatio) { + this.aspectRatio = aspectRatio; + } + public Matrix4f getMatrix() { return new Matrix4f() - .perspective((float) (Math.toRadians(fov)), aspect, zNear, zFar) + .perspective((float) (Math.toRadians(fov)), aspectRatio, zNear, zFar) .lookAt(pos, new Vector3f(0.0f, 0, 0), new Vector3f(0.0f, 1.0f, 0.0f)); } } diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index d51ea6c..1e7a833 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -102,6 +102,10 @@ public class Window { float y = cam.getPos().z(); cam.setPosition(new Vector3f(x * (float) Math.cos(angle) - y * (float) Math.sin(angle), 1.0f, x * (float) Math.sin(angle) + y * (float) Math.cos(angle))); + int width[] = new int[1]; + int height[] = new int[1]; + glfwGetWindowSize(window, width, height); + cam.setAspectRatio((float) width[0] / (float) height[0]); renderer.Render(cam); renderPieces(); } From 5b6fce11bc45ab141717fca7b638d33c28e4c7fd Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 26 Apr 2025 12:20:00 +0200 Subject: [PATCH 07/40] refactor board model --- .../view/DDDrender/BoardModelLoader.java | 96 +++++++++++++++++++ .../java/chess/view/DDDrender/Renderer.java | 96 +------------------ .../java/chess/view/DDDrender/Window.java | 2 + 3 files changed, 101 insertions(+), 93 deletions(-) create mode 100644 app/src/main/java/chess/view/DDDrender/BoardModelLoader.java diff --git a/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java b/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java new file mode 100644 index 0000000..07b3f60 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java @@ -0,0 +1,96 @@ +package chess.view.DDDrender; + +import org.joml.Vector3f; + +public class BoardModelLoader { + + private static int BOARD_WIDTH = 8; + private static int BOARD_HEIGHT = 8; + private static int BOARD_SIZE = BOARD_WIDTH * BOARD_HEIGHT; + private static int SQUARE_VERTEX_COUNT = 4; + + private static float[] GetBoardPositions() { + float[] positions = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3]; + for (int i = 0; i < BOARD_WIDTH; i++) { + for (int j = 0; j < BOARD_HEIGHT; j++) { + float x = i / (float) BOARD_WIDTH; + float dx = (i + 1) / (float) BOARD_WIDTH; + float z = j / (float) BOARD_HEIGHT; + float dz = (j + 1) / (float) BOARD_HEIGHT; + + float trueX = 2 * x - 1; + float trueZ = 2 * z - 1; + float trueDX = 2 * dx - 1; + float trueDZ = 2 * dz - 1; + + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3] = trueX; + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 1] = 0.0f; + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 2] = trueZ; + + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 3] = trueDX; + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 4] = 0.0f; + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 5] = trueZ; + + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 6] = trueX; + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 7] = 0.0f; + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 8] = trueDZ; + + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 9] = trueDX; + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 10] = 0.0f; + positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 11] = trueDZ; + } + } + return positions; + } + + private static float[] GetBoardColors() { + float[] colors = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3]; + for (int i = 0; i < BOARD_WIDTH; i++) { + for (int j = 0; j < BOARD_HEIGHT; j++) { + Vector3f color; + if ((i + j) % 2 == 0) { + color = new Vector3f(1.0f, 1.0f, 1.0f); + } else { + color = new Vector3f(0.0f, 0.0f, 0.0f); + } + int squareIndex = i * BOARD_WIDTH + j; + for (int k = 0; k < SQUARE_VERTEX_COUNT; k++) { + colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3] = color.x; + colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3 + 1] = color.y; + colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3 + 2] = color.z; + } + } + } + return colors; + } + + private static int[] GetBoardIndicies() { + int[] indices = new int[BOARD_SIZE * 6]; + for (int i = 0; i < BOARD_SIZE; i++) { + indices[i * 6] = i * 4; + indices[i * 6 + 1] = i * 4 + 1; + indices[i * 6 + 2] = i * 4 + 2; + indices[i * 6 + 3] = i * 4 + 1; + indices[i * 6 + 4] = i * 4 + 2; + indices[i * 6 + 5] = i * 4 + 3; + } + return indices; + } + + public static VertexArray GetBoardModel() { + ElementBuffer eBuffer = new ElementBuffer(GetBoardIndicies()); + VertexArray vao = new VertexArray(eBuffer); + + VertexBuffer positionBuffer = new VertexBuffer(GetBoardPositions(), 3); + positionBuffer.AddVertexAttribPointer(0, 3, 0); + + VertexBuffer colorBuffer = new VertexBuffer(GetBoardColors(), 3); + colorBuffer.AddVertexAttribPointer(1, 3, 0); + + vao.Bind(); + vao.BindVertexBuffer(positionBuffer); + vao.BindVertexBuffer(colorBuffer); + vao.Unbind(); + return vao; + } +} diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 2044733..084fac3 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -20,14 +20,9 @@ import chess.view.DDDrender.shader.ShaderProgram; public class Renderer { private BoardShader boardShader; private PieceShader pieceShader; - private VertexArray vao; + private VertexArray boardVao; private final PieceModel models; - private static int BOARD_WIDTH = 8; - private static int BOARD_HEIGHT = 8; - private static int BOARD_SIZE = BOARD_WIDTH * BOARD_HEIGHT; - private static int SQUARE_VERTEX_COUNT = 4; - public Renderer() { this.boardShader = new BoardShader(); this.pieceShader = new PieceShader(); @@ -37,93 +32,9 @@ public class Renderer { public void Init() { boardShader.LoadShader(); pieceShader.LoadShader(); - glEnable(GL_DEPTH_TEST); - InitBoard(); + this.boardVao = BoardModelLoader.GetBoardModel(); } - private float[] GetBoardPositions() { - float[] positions = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3]; - for (int i = 0; i < BOARD_WIDTH; i++) { - for (int j = 0; j < BOARD_HEIGHT; j++) { - float x = i / (float) BOARD_WIDTH; - float dx = (i + 1) / (float) BOARD_WIDTH; - float z = j / (float) BOARD_HEIGHT; - float dz = (j + 1) / (float) BOARD_HEIGHT; - - float trueX = 2 * x - 1; - float trueZ = 2 * z - 1; - float trueDX = 2 * dx - 1; - float trueDZ = 2 * dz - 1; - - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3] = trueX; - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 1] = 0.0f; - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 2] = trueZ; - - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 3] = trueDX; - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 4] = 0.0f; - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 5] = trueZ; - - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 6] = trueX; - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 7] = 0.0f; - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 8] = trueDZ; - - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 9] = trueDX; - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 10] = 0.0f; - positions[(BOARD_WIDTH * i + j) * SQUARE_VERTEX_COUNT * 3 + 11] = trueDZ; - } - } - return positions; - } - - private float[] GetBoardColors() { - float[] colors = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3]; - for (int i = 0; i < BOARD_WIDTH; i++) { - for (int j = 0; j < BOARD_HEIGHT; j++) { - Vector3f color; - if ((i + j) % 2 == 0) { - color = new Vector3f(1.0f, 1.0f, 1.0f); - } else { - color = new Vector3f(0.0f, 0.0f, 0.0f); - } - int squareIndex = i * BOARD_WIDTH + j; - for (int k = 0; k < SQUARE_VERTEX_COUNT; k++) { - colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3] = color.x; - colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3 + 1] = color.y; - colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3 + 2] = color.z; - } - } - } - return colors; - } - - private int[] GetBoardIndicies() { - int[] indices = new int[BOARD_SIZE * 6]; - for (int i = 0; i < BOARD_SIZE; i++) { - indices[i * 6] = i * 4; - indices[i * 6 + 1] = i * 4 + 1; - indices[i * 6 + 2] = i * 4 + 2; - indices[i * 6 + 3] = i * 4 + 1; - indices[i * 6 + 4] = i * 4 + 2; - indices[i * 6 + 5] = i * 4 + 3; - } - return indices; - } - - private void InitBoard() { - ElementBuffer eBuffer = new ElementBuffer(GetBoardIndicies()); - this.vao = new VertexArray(eBuffer); - - VertexBuffer positionBuffer = new VertexBuffer(GetBoardPositions(), 3); - positionBuffer.AddVertexAttribPointer(0, 3, 0); - - VertexBuffer colorBuffer = new VertexBuffer(GetBoardColors(), 3); - colorBuffer.AddVertexAttribPointer(1, 3, 0); - - this.vao.Bind(); - this.vao.BindVertexBuffer(positionBuffer); - this.vao.BindVertexBuffer(colorBuffer); - this.vao.Unbind(); - } public void RenderPiece(Piece piece, Coordinate pos) { try { @@ -135,12 +46,11 @@ public class Renderer { } public void Render(Camera cam) { - GL30.glClear(GL30.GL_DEPTH_BUFFER_BIT); this.boardShader.Start(); this.boardShader.SetCamMatrix(cam.getMatrix()); this.pieceShader.Start(); this.pieceShader.SetCamMatrix(cam.getMatrix()); - RenderVao(this.boardShader, vao); + RenderVao(this.boardShader, this.boardVao); } public void Render(DDDModel model, Vector2f position) { diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 1e7a833..3e36e2e 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -141,6 +141,8 @@ public class Window { // Set the clear color glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glEnable(GL_DEPTH_TEST); + glColor4f(1.0f, 0.0f, 0.0f, 1.0f); // Run the rendering loop until the user has attempted to close From 24104fedf58fe88a791a7a2dc273c9ea8deab6b6 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 26 Apr 2025 12:23:14 +0200 Subject: [PATCH 08/40] fix pieces rotation --- app/src/main/java/chess/view/DDDrender/Renderer.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 084fac3..2ef392b 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -1,8 +1,6 @@ package chess.view.DDDrender; -import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; -import static org.lwjgl.opengl.GL11.glEnable; import java.io.IOException; @@ -11,6 +9,7 @@ import org.joml.Vector2f; import org.joml.Vector3f; import org.lwjgl.opengl.GL30; +import chess.model.Color; import chess.model.Coordinate; import chess.model.Piece; import chess.view.DDDrender.shader.BoardShader; @@ -35,11 +34,10 @@ public class Renderer { this.boardVao = BoardModelLoader.GetBoardModel(); } - public void RenderPiece(Piece piece, Coordinate pos) { try { DDDModel pieceModel = this.models.getModel(piece); - Render(pieceModel, DDDPlacement.coordinates_to_vector(pos)); + Render(pieceModel, DDDPlacement.coordinates_to_vector(pos), piece.getColor() == Color.White ? 0.0f : 3.14f); } catch (IOException e) { e.printStackTrace(); } @@ -53,10 +51,10 @@ public class Renderer { RenderVao(this.boardShader, this.boardVao); } - public void Render(DDDModel model, Vector2f position) { + public void Render(DDDModel model, Vector2f position, float rotation) { Vector3f realPos = new Vector3f(position.x(), 0, position.y()); this.pieceShader.Start(); - this.pieceShader.setModelTransform(new Matrix4f().translate(realPos)); + this.pieceShader.setModelTransform(new Matrix4f().translate(realPos).rotate(rotation, new Vector3f(0, 1, 0))); Render(model); } From 1b22de17d824e013491a33489bd5728561f7a885 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 26 Apr 2025 12:30:29 +0200 Subject: [PATCH 09/40] change background color --- app/src/main/java/chess/view/DDDrender/Window.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 3e36e2e..925139d 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -139,7 +139,7 @@ public class Window { renderer.Init(); // Set the clear color - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClearColor(0.4f, 0.4f, 0.6f, 1.0f); glEnable(GL_DEPTH_TEST); From f8ae19fee837d39dd613d3c3623938f42f13c52e Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 26 Apr 2025 12:35:37 +0200 Subject: [PATCH 10/40] set pieces color --- .../main/java/chess/view/DDDrender/Renderer.java | 9 +++++++-- .../chess/view/DDDrender/shader/PieceShader.java | 14 +++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 2ef392b..17451c4 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -22,6 +22,9 @@ public class Renderer { private VertexArray boardVao; private final PieceModel models; + private static final Vector3f BLACK = new Vector3f(0.1f, 0.1f, 0.1f); + private static final Vector3f WHITE = new Vector3f(0.7f, 0.7f, 0.7f); + public Renderer() { this.boardShader = new BoardShader(); this.pieceShader = new PieceShader(); @@ -37,7 +40,8 @@ public class Renderer { public void RenderPiece(Piece piece, Coordinate pos) { try { DDDModel pieceModel = this.models.getModel(piece); - Render(pieceModel, DDDPlacement.coordinates_to_vector(pos), piece.getColor() == Color.White ? 0.0f : 3.14f); + Render(pieceModel, piece.getColor() == Color.White ? WHITE : BLACK, DDDPlacement.coordinates_to_vector(pos), + piece.getColor() == Color.White ? 0.0f : 3.14f); } catch (IOException e) { e.printStackTrace(); } @@ -51,9 +55,10 @@ public class Renderer { RenderVao(this.boardShader, this.boardVao); } - public void Render(DDDModel model, Vector2f position, float rotation) { + public void Render(DDDModel model, Vector3f color, Vector2f position, float rotation) { Vector3f realPos = new Vector3f(position.x(), 0, position.y()); this.pieceShader.Start(); + this.pieceShader.setModelColor(color); this.pieceShader.setModelTransform(new Matrix4f().translate(realPos).rotate(rotation, new Vector3f(0, 1, 0))); Render(model); } diff --git a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java index 129b802..69699ba 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java @@ -1,6 +1,7 @@ package chess.view.DDDrender.shader; import org.joml.Matrix4f; +import org.joml.Vector3f; public class PieceShader extends ShaderProgram { @@ -18,8 +19,6 @@ public class PieceShader extends ShaderProgram { out vec3 toLightVector; out vec3 surfaceNormal; - flat out vec3 pass_color; - void main(void){ vec4 worldPos = modelTransform * vec4(position, 1.0); @@ -27,7 +26,6 @@ public class PieceShader extends ShaderProgram { surfaceNormal = (modelTransform * vec4(normal, 0.0)).xyz; gl_Position = camMatrix * worldPos; - pass_color = position; } """; @@ -37,7 +35,7 @@ public class PieceShader extends ShaderProgram { in vec3 toLightVector; in vec3 surfaceNormal; - flat in vec3 pass_color; + uniform vec3 modelColor = vec3(1, 1, 1); out vec4 out_color; @@ -49,7 +47,7 @@ public class PieceShader extends ShaderProgram { float brightness = diffuse; - out_color = vec4(pass_color, 1.0) * brightness; + out_color = vec4(modelColor, 1.0) * brightness; out_color.w = 1.0; } @@ -57,6 +55,7 @@ public class PieceShader extends ShaderProgram { private int location_CamMatrix = 0; private int location_ModelTransform = 0; + private int location_ModelColor = 0; public PieceShader() { @@ -70,6 +69,7 @@ public class PieceShader extends ShaderProgram { protected void GetAllUniformLocation() { location_CamMatrix = GetUniformLocation("camMatrix"); location_ModelTransform = GetUniformLocation("modelTransform"); + location_ModelColor = GetUniformLocation("modelColor"); } public void SetCamMatrix(Matrix4f mat) { @@ -79,4 +79,8 @@ public class PieceShader extends ShaderProgram { public void setModelTransform(Matrix4f mat) { LoadMat4(location_ModelTransform, mat); } + + public void setModelColor(Vector3f color) { + LoadVector(location_ModelColor, color); + } } From 65c904478fd742cf55d67d798e9e6e2fa8e13249 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 26 Apr 2025 17:26:27 +0200 Subject: [PATCH 11/40] enable face culling --- app/src/main/java/chess/view/DDDrender/BoardModelLoader.java | 4 ++-- app/src/main/java/chess/view/DDDrender/Window.java | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java b/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java index 07b3f60..64ede5e 100644 --- a/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java +++ b/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java @@ -68,8 +68,8 @@ public class BoardModelLoader { int[] indices = new int[BOARD_SIZE * 6]; for (int i = 0; i < BOARD_SIZE; i++) { indices[i * 6] = i * 4; - indices[i * 6 + 1] = i * 4 + 1; - indices[i * 6 + 2] = i * 4 + 2; + indices[i * 6 + 1] = i * 4 + 2; + indices[i * 6 + 2] = i * 4 + 1; indices[i * 6 + 3] = i * 4 + 1; indices[i * 6 + 4] = i * 4 + 2; indices[i * 6 + 5] = i * 4 + 3; diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 925139d..0264cc9 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -143,6 +143,10 @@ public class Window { glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + glFrontFace(GL_CW); + glColor4f(1.0f, 0.0f, 0.0f, 1.0f); // Run the rendering loop until the user has attempted to close From b62dcffcb11d199b34a049dccda1d1bc2ff1fd17 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 26 Apr 2025 18:18:27 +0200 Subject: [PATCH 12/40] refactor --- .../java/chess/view/DDDrender/Camera.java | 102 +++++------------- .../java/chess/view/DDDrender/Renderer.java | 4 +- .../java/chess/view/DDDrender/Window.java | 37 +++---- .../view/DDDrender/shader/BoardShader.java | 18 ++-- .../view/DDDrender/shader/PieceShader.java | 18 ++-- 5 files changed, 70 insertions(+), 109 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/Camera.java b/app/src/main/java/chess/view/DDDrender/Camera.java index 08154e6..02cbf8e 100644 --- a/app/src/main/java/chess/view/DDDrender/Camera.java +++ b/app/src/main/java/chess/view/DDDrender/Camera.java @@ -8,16 +8,21 @@ public class Camera { public static final float zNear = 0.01f; public static final float zFar = 1000.0f; + private static final Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f); + private static final Vector3f center = new Vector3f(0.0f, 0.0f, 0.0f); + + private final float distance = 1.5f; + private final float camHeight = 1.5f; + + private float aspectRatio; + private float angle; private Vector3f pos; - private float yaw = 0.0f; - private float pitch = 0.0f; - public Camera() { - this.pos = new Vector3f(1.5f, 1.5f, 0); - setRotation(0.0f, -3.14150f / 2.0f); + this.pos = new Vector3f(0.0f, camHeight, 0.0f); + this.angle = 0.0f; } public void move(float x, float y) { @@ -25,23 +30,25 @@ public class Camera { this.pos.y += y; } - public void rotate(float yaw, float pitch) { - this.yaw += yaw; - this.pitch += pitch; + public void setRotateAngle(float angle) { + this.angle = angle; + updatePostion(); + } + + private void updatePostion() { + final float finalX = (float) Math.sin(angle); + final float finalZ = (float) -Math.cos(angle); + this.pos.set(distance * finalX, this.pos.get(1), distance * finalZ); + } + + public float getRotateAngle() { + return angle; } public Vector3f getPos() { return pos; } - public float getYaw() { - return yaw; - } - - public float getPitch() { - return pitch; - } - public float getFov() { return fov; } @@ -58,70 +65,19 @@ public class Camera { this.pos.z = z; } - public void setYaw(float yaw) { - this.yaw = yaw; - } - - public void setPitch(float pitch) { - this.pitch = pitch; - } - - public void reset() { - resetPosition(); - resetRotation(); - } - - public void resetPosition() { - pos = new Vector3f(0.0f, 0.0f, 0.0f); - } - - public void resetRotation() { - yaw = 0.0f; - pitch = 0.0f; - } - - public void moveForward(float distance) { - pos.x += distance * (float) Math.cos(yaw); - pos.y += distance * (float) Math.sin(yaw); - } - - public void moveRight(float distance) { - pos.x += distance * (float) Math.cos(yaw); - pos.y += distance * (float) Math.sin(yaw); - } - - public void moveUp(float distance) { - pos.z += distance; - } - - public void moveDown(float distance) { - pos.z -= distance; - } - - public void addYaw(float angle) { - yaw += angle; - } - - public void addPitch(float angle) { - pitch += angle; - } - public void setPosition(Vector3f pos) { this.pos = pos; } - public void setRotation(float yaw, float pitch) { - this.yaw = yaw; - this.pitch = pitch; - } - public void setAspectRatio(float aspectRatio) { this.aspectRatio = aspectRatio; } - public Matrix4f getMatrix() { - return new Matrix4f() - .perspective((float) (Math.toRadians(fov)), aspectRatio, zNear, zFar) - .lookAt(pos, new Vector3f(0.0f, 0, 0), new Vector3f(0.0f, 1.0f, 0.0f)); + public Matrix4f getPerspectiveMatrix() { + return new Matrix4f().perspective((float) (Math.toRadians(fov)), aspectRatio, zNear, zFar); + } + + public Matrix4f getViewMatrix() { + return new Matrix4f().lookAt(pos, center, up); } } diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 17451c4..d78d760 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -49,9 +49,9 @@ public class Renderer { public void Render(Camera cam) { this.boardShader.Start(); - this.boardShader.SetCamMatrix(cam.getMatrix()); + this.boardShader.SetCamMatrix(cam); this.pieceShader.Start(); - this.pieceShader.SetCamMatrix(cam.getMatrix()); + this.pieceShader.SetCamMatrix(cam); RenderVao(this.boardShader, this.boardVao); } diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 0264cc9..62b04ce 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -1,6 +1,5 @@ package chess.view.DDDrender; -import org.joml.Vector3f; import org.lwjgl.*; import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; @@ -11,7 +10,6 @@ import chess.controller.commands.GetPieceAtCommand; import chess.model.Coordinate; import chess.model.Piece; -import java.io.IOException; import java.nio.*; import static org.lwjgl.glfw.Callbacks.*; @@ -96,16 +94,11 @@ public class Window { glfwShowWindow(window); } - private void render() { - final float angle = 0.01f; - float x = cam.getPos().x(); - float y = cam.getPos().z(); - cam.setPosition(new Vector3f(x * (float) Math.cos(angle) - y * (float) Math.sin(angle), 1.0f, - x * (float) Math.sin(angle) + y * (float) Math.cos(angle))); - int width[] = new int[1]; - int height[] = new int[1]; - glfwGetWindowSize(window, width, height); - cam.setAspectRatio((float) width[0] / (float) height[0]); + private void render(float delta, float aspectRatio) { + final float angle = 1f; + cam.setRotateAngle(cam.getRotateAngle() + angle * delta); + + cam.setAspectRatio(aspectRatio); renderer.Render(cam); renderPieces(); } @@ -149,12 +142,20 @@ public class Window { glColor4f(1.0f, 0.0f, 0.0f, 1.0f); + double lastTime = glfwGetTime(); + + int width[] = new int[1]; + int height[] = new int[1]; + glfwGetWindowSize(window, width, height); + // Run the rendering loop until the user has attempted to close // the window or has pressed the ESCAPE key. while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer - render(); + double currentTime = glfwGetTime(); + render((float) (currentTime - lastTime), (float) width[0] / (float) height[0]); + lastTime = glfwGetTime(); glfwSwapBuffers(window); // swap the color buffers @@ -162,15 +163,9 @@ public class Window { // invoked during this call. glfwPollEvents(); - try (MemoryStack stack = stackPush()) { - IntBuffer pWidth = stack.mallocInt(1); // int* - IntBuffer pHeight = stack.mallocInt(1); // int* + glfwGetWindowSize(window, width, height); + glViewport(0, 0, width[0], height[0]); - // Get the window size passed to glfwCreateWindow - glfwGetWindowSize(window, pWidth, pHeight); - - glViewport(0, 0, pWidth.get(), pHeight.get()); - } // the stack frame is popped automatically } } diff --git a/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java index 887ab08..2df8097 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java @@ -1,6 +1,6 @@ package chess.view.DDDrender.shader; -import org.joml.Matrix4f; +import chess.view.DDDrender.Camera; public class BoardShader extends ShaderProgram { @@ -10,14 +10,15 @@ public class BoardShader extends ShaderProgram { layout(location = 0) in vec3 position; layout(location = 1) in vec3 color; - uniform mat4 camMatrix; + uniform mat4 viewMatrix; + uniform mat4 projectionMatrix; uniform vec3 lightPosition; flat out vec3 pass_color; out vec3 toLightVector; void main(void){ - gl_Position = camMatrix * vec4(position, 1.0); + gl_Position = projectionMatrix * viewMatrix * vec4(position, 1.0); toLightVector = lightPosition - position; @@ -61,7 +62,8 @@ public class BoardShader extends ShaderProgram { """; - private int location_CamMatrix = 0; + private int location_ProjectionMatrix = 0; + private int location_ViewMatrix = 0; public BoardShader() { @@ -73,10 +75,12 @@ public class BoardShader extends ShaderProgram { @Override protected void GetAllUniformLocation() { - location_CamMatrix = GetUniformLocation("camMatrix"); + location_ProjectionMatrix = GetUniformLocation("projectionMatrix"); + location_ViewMatrix = GetUniformLocation("viewMatrix"); } - public void SetCamMatrix(Matrix4f mat) { - LoadMat4(location_CamMatrix, mat); + public void SetCamMatrix(Camera camera) { + LoadMat4(location_ProjectionMatrix, camera.getPerspectiveMatrix()); + LoadMat4(location_ViewMatrix, camera.getViewMatrix()); } } diff --git a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java index 69699ba..4f21373 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java @@ -3,6 +3,8 @@ package chess.view.DDDrender.shader; import org.joml.Matrix4f; import org.joml.Vector3f; +import chess.view.DDDrender.Camera; + public class PieceShader extends ShaderProgram { private static String vertexShader = """ @@ -12,7 +14,8 @@ public class PieceShader extends ShaderProgram { layout(location = 1) in vec2 uv; layout(location = 2) in vec3 normal; - uniform mat4 camMatrix; + uniform mat4 projectionMatrix; + uniform mat4 viewMatrix; uniform mat4 modelTransform; uniform vec3 lightPosition = vec3(0, 1, 0); @@ -25,7 +28,7 @@ public class PieceShader extends ShaderProgram { toLightVector = lightPosition - worldPos.xyz; surfaceNormal = (modelTransform * vec4(normal, 0.0)).xyz; - gl_Position = camMatrix * worldPos; + gl_Position = projectionMatrix * viewMatrix * worldPos; } """; @@ -53,7 +56,8 @@ public class PieceShader extends ShaderProgram { } """; - private int location_CamMatrix = 0; + private int location_ProjectionMatrix = 0; + private int location_ViewMatrix = 0; private int location_ModelTransform = 0; private int location_ModelColor = 0; @@ -67,13 +71,15 @@ public class PieceShader extends ShaderProgram { @Override protected void GetAllUniformLocation() { - location_CamMatrix = GetUniformLocation("camMatrix"); + location_ProjectionMatrix = GetUniformLocation("projectionMatrix"); + location_ViewMatrix = GetUniformLocation("viewMatrix"); location_ModelTransform = GetUniformLocation("modelTransform"); location_ModelColor = GetUniformLocation("modelColor"); } - public void SetCamMatrix(Matrix4f mat) { - LoadMat4(location_CamMatrix, mat); + public void SetCamMatrix(Camera camera) { + LoadMat4(location_ProjectionMatrix, camera.getPerspectiveMatrix()); + LoadMat4(location_ViewMatrix, camera.getViewMatrix()); } public void setModelTransform(Matrix4f mat) { From 6ca5d1294fe0fd321ac4d14c72d5665d7ba0cb2f Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 26 Apr 2025 19:24:52 +0200 Subject: [PATCH 13/40] better light --- .../java/chess/view/DDDrender/Renderer.java | 4 +- .../view/DDDrender/shader/BoardShader.java | 24 +++++++++-- .../view/DDDrender/shader/PieceShader.java | 43 +++++++++++++++---- .../view/DDDrender/shader/ShaderProgram.java | 4 +- 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index d78d760..73bc8c5 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -22,8 +22,8 @@ public class Renderer { private VertexArray boardVao; private final PieceModel models; - private static final Vector3f BLACK = new Vector3f(0.1f, 0.1f, 0.1f); - private static final Vector3f WHITE = new Vector3f(0.7f, 0.7f, 0.7f); + private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); + private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); public Renderer() { this.boardShader = new BoardShader(); diff --git a/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java index 2df8097..4c676f5 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java @@ -12,16 +12,26 @@ public class BoardShader extends ShaderProgram { uniform mat4 viewMatrix; uniform mat4 projectionMatrix; - uniform vec3 lightPosition; + uniform vec3 lightPosition = vec3(0, 10, 0); flat out vec3 pass_color; + out vec3 toLightVector; + out vec3 toCameraVector; + out vec3 surfaceNormal; void main(void){ + const vec4 normal = vec4(0.0, 1.0, 0.0, 1.0); + gl_Position = projectionMatrix * viewMatrix * vec4(position, 1.0); + vec3 camPos = (inverse(viewMatrix) * vec4(0.0, 0.0, 0.0, 1.0)).xyz; + toLightVector = lightPosition - position; + toCameraVector = camPos - position; + surfaceNormal = (normal).xyz; + pass_color = color; } @@ -33,6 +43,8 @@ public class BoardShader extends ShaderProgram { flat in vec3 pass_color; in vec3 toLightVector; + in vec3 toCameraVector; + in vec3 surfaceNormal; out vec4 out_color; @@ -42,18 +54,22 @@ public class BoardShader extends ShaderProgram { float lightDistance = length(toLightVector); - const vec3 attenuation = vec3(0.3, 0.03, 0); + const vec3 attenuation = vec3(0.2, 0.1, 0.0); float attenuationFactor = attenuation.x + attenuation.y * lightDistance + attenuation.z * lightDistance * lightDistance; - vec3 unitNormal = vec3(0, 1, 0); + vec3 unitNormal = normalize(surfaceNormal); vec3 unitLightVector = normalize(toLightVector); + vec3 unitCamVector = normalize(toCameraVector); vec3 lightDirection = -unitLightVector; vec3 reflectedLightDirection = reflect(lightDirection, unitNormal); float diffuse = max(0.2, dot(unitNormal, unitLightVector)); - float brightness = diffuse / attenuationFactor; + float specularFactor = max(0.0, dot(reflectedLightDirection, unitCamVector)); + float specular = pow(specularFactor, shineDamper) * reflectivity; + + float brightness = (diffuse + specular) / attenuationFactor; out_color = brightness * vec4(pass_color, 1.0); out_color.w = 1.0; diff --git a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java index 4f21373..1f49eaf 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java @@ -17,43 +17,68 @@ public class PieceShader extends ShaderProgram { uniform mat4 projectionMatrix; uniform mat4 viewMatrix; uniform mat4 modelTransform; - uniform vec3 lightPosition = vec3(0, 1, 0); + uniform vec3 lightPosition = vec3(0, 10, 0); out vec3 toLightVector; + out vec3 toCameraVector; out vec3 surfaceNormal; void main(void){ - vec4 worldPos = modelTransform * vec4(position, 1.0); - toLightVector = lightPosition - worldPos.xyz; - surfaceNormal = (modelTransform * vec4(normal, 0.0)).xyz; + vec4 modelPos = modelTransform * vec4(position, 1.0); + vec4 globalNormal = modelTransform * vec4(normal, 1.0); - gl_Position = projectionMatrix * viewMatrix * worldPos; + gl_Position = projectionMatrix * viewMatrix * modelPos; + + vec3 camPos = (inverse(viewMatrix) * vec4(0.0, 0.0, 0.0, 1.0)).xyz; + + toLightVector = lightPosition - modelPos.xyz; + + toCameraVector = camPos - position; + surfaceNormal = normalize(globalNormal.xyz); } + """; private static String fragmentShader = """ #version 330 in vec3 toLightVector; + in vec3 toCameraVector; in vec3 surfaceNormal; - uniform vec3 modelColor = vec3(1, 1, 1); + uniform vec3 modelColor; out vec4 out_color; void main(void){ + const float shineDamper = 10.0; + const float reflectivity = 1.0; + + float lightDistance = length(toLightVector); + + const vec3 attenuation = vec3(0.2, 0.1, 0.0); + float attenuationFactor = attenuation.x + attenuation.y * lightDistance + attenuation.z * lightDistance * lightDistance; + vec3 unitNormal = normalize(surfaceNormal); vec3 unitLightVector = normalize(toLightVector); + vec3 unitCamVector = normalize(toCameraVector); - float diffuse = max(0.5, dot(unitNormal, unitLightVector)); + vec3 lightDirection = -unitLightVector; + vec3 reflectedLightDirection = reflect(lightDirection, unitNormal); - float brightness = diffuse; + float diffuse = max(0.2, dot(unitNormal, unitLightVector)); - out_color = vec4(modelColor, 1.0) * brightness; + float specularFactor = max(0.0, dot(reflectedLightDirection, unitCamVector)); + float specular = pow(specularFactor, shineDamper) * reflectivity; + + float brightness = (diffuse + specular) / attenuationFactor; + + out_color = brightness * vec4(modelColor, 1.0); out_color.w = 1.0; } + """; private int location_ProjectionMatrix = 0; diff --git a/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java b/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java index cfeb07f..1cf3214 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java +++ b/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java @@ -56,8 +56,8 @@ public abstract class ShaderProgram { if (compileSuccesful.get() != 1) { System.out.println("Shader did not compile !"); + System.err.println(GL30.glGetShaderInfoLog(shaderId)); return -1; - } return shaderId; @@ -68,7 +68,7 @@ public abstract class ShaderProgram { protected int GetUniformLocation(String uniformName) { int location = GL30.glGetUniformLocation(programId, uniformName); if (location == -1) { - System.out.println("Uniform value not found !"); + System.out.println("Uniform value \"" + uniformName + "\" not found !"); } return location; } From 9f35bd3c308d0834a9da664fa864f64b860f1c41 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sun, 27 Apr 2025 10:12:52 +0200 Subject: [PATCH 14/40] opengl package --- .../main/java/chess/view/DDDrender/BoardModelLoader.java | 4 ++++ app/src/main/java/chess/view/DDDrender/DDDModel.java | 2 ++ app/src/main/java/chess/view/DDDrender/DDDPlacement.java | 6 +++++- app/src/main/java/chess/view/DDDrender/ModelLoader.java | 3 +++ app/src/main/java/chess/view/DDDrender/Renderer.java | 1 + .../chess/view/DDDrender/{ => opengl}/ElementBuffer.java | 2 +- .../java/chess/view/DDDrender/{ => opengl}/VertexArray.java | 2 +- .../view/DDDrender/{ => opengl}/VertexAttribPointer.java | 2 +- .../chess/view/DDDrender/{ => opengl}/VertexBuffer.java | 2 +- 9 files changed, 19 insertions(+), 5 deletions(-) rename app/src/main/java/chess/view/DDDrender/{ => opengl}/ElementBuffer.java (95%) rename app/src/main/java/chess/view/DDDrender/{ => opengl}/VertexArray.java (96%) rename app/src/main/java/chess/view/DDDrender/{ => opengl}/VertexAttribPointer.java (66%) rename app/src/main/java/chess/view/DDDrender/{ => opengl}/VertexBuffer.java (97%) diff --git a/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java b/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java index 64ede5e..24144e1 100644 --- a/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java +++ b/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java @@ -2,6 +2,10 @@ package chess.view.DDDrender; import org.joml.Vector3f; +import chess.view.DDDrender.opengl.ElementBuffer; +import chess.view.DDDrender.opengl.VertexArray; +import chess.view.DDDrender.opengl.VertexBuffer; + public class BoardModelLoader { private static int BOARD_WIDTH = 8; diff --git a/app/src/main/java/chess/view/DDDrender/DDDModel.java b/app/src/main/java/chess/view/DDDrender/DDDModel.java index 577daab..5e83978 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDModel.java +++ b/app/src/main/java/chess/view/DDDrender/DDDModel.java @@ -2,6 +2,8 @@ package chess.view.DDDrender; import java.util.List; +import chess.view.DDDrender.opengl.VertexArray; + public class DDDModel { private final List vaos; diff --git a/app/src/main/java/chess/view/DDDrender/DDDPlacement.java b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java index cf35bcb..9300027 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDPlacement.java +++ b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java @@ -6,7 +6,11 @@ import chess.model.Coordinate; class DDDPlacement { static public Vector2f coordinates_to_vector(Coordinate coo) { - return new Vector2f(1.0f - 0.125f - coo.getX() * 0.250f, 1.0f - 0.125f - coo.getY() * 0.250f); + return coordinates_to_vector(coo.getX(), coo.getY()); + } + + static public Vector2f coordinates_to_vector(float x, float y) { + return new Vector2f(1.0f - 0.125f - x * 0.250f, 1.0f - 0.125f - y * 0.250f); } } \ No newline at end of file diff --git a/app/src/main/java/chess/view/DDDrender/ModelLoader.java b/app/src/main/java/chess/view/DDDrender/ModelLoader.java index 47048f1..de2beb5 100644 --- a/app/src/main/java/chess/view/DDDrender/ModelLoader.java +++ b/app/src/main/java/chess/view/DDDrender/ModelLoader.java @@ -18,6 +18,9 @@ import org.lwjgl.assimp.Assimp; import org.lwjgl.system.MemoryUtil; import chess.view.AssetManager; +import chess.view.DDDrender.opengl.ElementBuffer; +import chess.view.DDDrender.opengl.VertexArray; +import chess.view.DDDrender.opengl.VertexBuffer; public class ModelLoader { diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 73bc8c5..4ffa5e5 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -12,6 +12,7 @@ import org.lwjgl.opengl.GL30; import chess.model.Color; import chess.model.Coordinate; import chess.model.Piece; +import chess.view.DDDrender.opengl.VertexArray; import chess.view.DDDrender.shader.BoardShader; import chess.view.DDDrender.shader.PieceShader; import chess.view.DDDrender.shader.ShaderProgram; diff --git a/app/src/main/java/chess/view/DDDrender/ElementBuffer.java b/app/src/main/java/chess/view/DDDrender/opengl/ElementBuffer.java similarity index 95% rename from app/src/main/java/chess/view/DDDrender/ElementBuffer.java rename to app/src/main/java/chess/view/DDDrender/opengl/ElementBuffer.java index fc55725..bd89480 100644 --- a/app/src/main/java/chess/view/DDDrender/ElementBuffer.java +++ b/app/src/main/java/chess/view/DDDrender/opengl/ElementBuffer.java @@ -1,4 +1,4 @@ -package chess.view.DDDrender; +package chess.view.DDDrender.opengl; import org.lwjgl.opengl.GL30; diff --git a/app/src/main/java/chess/view/DDDrender/VertexArray.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java similarity index 96% rename from app/src/main/java/chess/view/DDDrender/VertexArray.java rename to app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java index 85d879a..74ee5d9 100644 --- a/app/src/main/java/chess/view/DDDrender/VertexArray.java +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java @@ -1,4 +1,4 @@ -package chess.view.DDDrender; +package chess.view.DDDrender.opengl; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/chess/view/DDDrender/VertexAttribPointer.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexAttribPointer.java similarity index 66% rename from app/src/main/java/chess/view/DDDrender/VertexAttribPointer.java rename to app/src/main/java/chess/view/DDDrender/opengl/VertexAttribPointer.java index f4af07e..dd4fc01 100644 --- a/app/src/main/java/chess/view/DDDrender/VertexAttribPointer.java +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexAttribPointer.java @@ -1,4 +1,4 @@ -package chess.view.DDDrender; +package chess.view.DDDrender.opengl; public record VertexAttribPointer(int index, int size, int offset) { diff --git a/app/src/main/java/chess/view/DDDrender/VertexBuffer.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java similarity index 97% rename from app/src/main/java/chess/view/DDDrender/VertexBuffer.java rename to app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java index 1995736..82439ec 100644 --- a/app/src/main/java/chess/view/DDDrender/VertexBuffer.java +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java @@ -1,4 +1,4 @@ -package chess.view.DDDrender; +package chess.view.DDDrender.opengl; import static org.lwjgl.opengl.GL11.GL_FLOAT; From c488f3b4e0f95d9430ed05bda85621751ac3cd29 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sun, 27 Apr 2025 10:14:05 +0200 Subject: [PATCH 15/40] rename PieceModel --- .../view/DDDrender/{PieceModel.java => Piece3DModel.java} | 2 +- app/src/main/java/chess/view/DDDrender/Renderer.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/src/main/java/chess/view/DDDrender/{PieceModel.java => Piece3DModel.java} (96%) diff --git a/app/src/main/java/chess/view/DDDrender/PieceModel.java b/app/src/main/java/chess/view/DDDrender/Piece3DModel.java similarity index 96% rename from app/src/main/java/chess/view/DDDrender/PieceModel.java rename to app/src/main/java/chess/view/DDDrender/Piece3DModel.java index e7f5571..2081d84 100644 --- a/app/src/main/java/chess/view/DDDrender/PieceModel.java +++ b/app/src/main/java/chess/view/DDDrender/Piece3DModel.java @@ -14,7 +14,7 @@ import chess.model.pieces.Pawn; import chess.model.pieces.Queen; import chess.model.pieces.Rook; -public class PieceModel implements PieceVisitor { +public class Piece3DModel implements PieceVisitor { private static final String basePath = "3d/"; private static final Map cache = new HashMap<>(); diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 4ffa5e5..758f921 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -21,7 +21,7 @@ public class Renderer { private BoardShader boardShader; private PieceShader pieceShader; private VertexArray boardVao; - private final PieceModel models; + private final Piece3DModel models; private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); @@ -29,7 +29,7 @@ public class Renderer { public Renderer() { this.boardShader = new BoardShader(); this.pieceShader = new PieceShader(); - this.models = new PieceModel(); + this.models = new Piece3DModel(); } public void Init() { From 9bc09cf8121817302c1b82be6797b96a5324a968 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sun, 27 Apr 2025 11:00:14 +0200 Subject: [PATCH 16/40] add world interface --- .../java/chess/view/DDDrender/DDDView.java | 66 ++++++++++++++-- .../java/chess/view/DDDrender/Renderer.java | 32 ++------ .../java/chess/view/DDDrender/Window.java | 79 ++++++++++++------- .../{ => loader}/BoardModelLoader.java | 2 +- .../DDDrender/{ => loader}/ModelLoader.java | 20 ++--- .../DDDrender/{ => loader}/Piece3DModel.java | 3 +- .../chess/view/DDDrender/world/Entity.java | 9 +++ .../view/DDDrender/world/PieceEntity.java | 55 +++++++++++++ .../chess/view/DDDrender/world/World.java | 40 ++++++++++ 9 files changed, 235 insertions(+), 71 deletions(-) rename app/src/main/java/chess/view/DDDrender/{ => loader}/BoardModelLoader.java (98%) rename app/src/main/java/chess/view/DDDrender/{ => loader}/ModelLoader.java (89%) rename app/src/main/java/chess/view/DDDrender/{ => loader}/Piece3DModel.java (95%) create mode 100644 app/src/main/java/chess/view/DDDrender/world/Entity.java create mode 100644 app/src/main/java/chess/view/DDDrender/world/PieceEntity.java create mode 100644 app/src/main/java/chess/view/DDDrender/world/World.java diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index cbbd8ef..b7b657f 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -1,22 +1,78 @@ package chess.view.DDDrender; -import chess.controller.CommandExecutor; -import chess.controller.event.GameAdaptator; +import org.joml.Vector2f; +import org.joml.Vector3f; -public class DDDView extends GameAdaptator{ +import chess.controller.CommandExecutor; +import chess.controller.commands.GetPieceAtCommand; +import chess.controller.event.GameAdaptator; +import chess.model.Color; +import chess.model.Coordinate; +import chess.model.Piece; +import chess.view.DDDrender.world.PieceEntity; +import chess.view.DDDrender.world.World; + +public class DDDView extends GameAdaptator { + + private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); + private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); private final CommandExecutor commandExecutor; private final Window window; private final Renderer renderer; + private final World world; public DDDView(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; this.renderer = new Renderer(); - this.window = new Window(commandExecutor, this.renderer); + this.world = new World(new Camera()); + this.window = new Window(this.renderer, this.world); + this.window.OnCellClick.connect(this::onCellClick); + } + + // Invoked when a cell is clicked + private void onCellClick(Coordinate coordinate) { + + } + + private Piece pieceAt(Coordinate pos) { + GetPieceAtCommand cmd = new GetPieceAtCommand(pos); + this.commandExecutor.executeCommand(cmd); + return cmd.getPiece(); + } + + @Override + public void onGameStart() { + this.window.scheduleTask(() -> { + initBoard(); + }); + } + + private void initBoard() { + for (int i = 0; i < Coordinate.VALUE_MAX; i++) { + for (int j = 0; j < Coordinate.VALUE_MAX; j++) { + Coordinate pos = new Coordinate(i, j); + Piece piece = pieceAt(pos); + if (piece == null) + continue; + + Vector2f pieceBoardPos = DDDPlacement.coordinates_to_vector(pos); + Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); + + this.world.addEntity(new PieceEntity(piece, pieceWorldPos, + piece.getColor() == Color.White ? WHITE : BLACK, + piece.getColor() == Color.White ? 0.0f : (float) Math.PI)); + } + } } public void run() { + this.window.addRegularTask((delta) -> { + final float angle = 1f; + final Camera cam = this.world.getCamera(); + cam.setRotateAngle(cam.getRotateAngle() + angle * delta); + }); this.window.run(); } - + } diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 758f921..20e402e 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -2,16 +2,11 @@ package chess.view.DDDrender; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; -import java.io.IOException; - import org.joml.Matrix4f; -import org.joml.Vector2f; import org.joml.Vector3f; import org.lwjgl.opengl.GL30; -import chess.model.Color; -import chess.model.Coordinate; -import chess.model.Piece; +import chess.view.DDDrender.loader.BoardModelLoader; import chess.view.DDDrender.opengl.VertexArray; import chess.view.DDDrender.shader.BoardShader; import chess.view.DDDrender.shader.PieceShader; @@ -21,15 +16,10 @@ public class Renderer { private BoardShader boardShader; private PieceShader pieceShader; private VertexArray boardVao; - private final Piece3DModel models; - - private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); - private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); public Renderer() { this.boardShader = new BoardShader(); this.pieceShader = new PieceShader(); - this.models = new Piece3DModel(); } public void Init() { @@ -38,29 +28,21 @@ public class Renderer { this.boardVao = BoardModelLoader.GetBoardModel(); } - public void RenderPiece(Piece piece, Coordinate pos) { - try { - DDDModel pieceModel = this.models.getModel(piece); - Render(pieceModel, piece.getColor() == Color.White ? WHITE : BLACK, DDDPlacement.coordinates_to_vector(pos), - piece.getColor() == Color.White ? 0.0f : 3.14f); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public void Render(Camera cam) { + public void Update(Camera cam) { this.boardShader.Start(); this.boardShader.SetCamMatrix(cam); this.pieceShader.Start(); this.pieceShader.SetCamMatrix(cam); + } + + public void RenderBoard() { RenderVao(this.boardShader, this.boardVao); } - public void Render(DDDModel model, Vector3f color, Vector2f position, float rotation) { - Vector3f realPos = new Vector3f(position.x(), 0, position.y()); + public void Render(DDDModel model, Vector3f color, Vector3f position, float rotation) { this.pieceShader.Start(); this.pieceShader.setModelColor(color); - this.pieceShader.setModelTransform(new Matrix4f().translate(realPos).rotate(rotation, new Vector3f(0, 1, 0))); + this.pieceShader.setModelTransform(new Matrix4f().translate(position).rotate(rotation, new Vector3f(0, 1, 0))); Render(model); } diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 62b04ce..8841569 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -5,12 +5,17 @@ import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; import org.lwjgl.system.*; -import chess.controller.CommandExecutor; -import chess.controller.commands.GetPieceAtCommand; import chess.model.Coordinate; -import chess.model.Piece; +import chess.view.DDDrender.world.Entity; +import chess.view.DDDrender.world.World; +import common.Signal1; import java.nio.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.Consumer; import static org.lwjgl.glfw.Callbacks.*; import static org.lwjgl.glfw.GLFW.*; @@ -24,13 +29,32 @@ public class Window { private long window; private Renderer renderer; - private Camera cam; - private final CommandExecutor commandExecutor; + private final Camera cam; + private final World world; - public Window(CommandExecutor commandExecutor, Renderer renderer) { + private final Queue tasks; + private final List> regularTasks; + + public final Signal1 OnCellClick = new Signal1<>(); + + public Window(Renderer renderer, World world) { this.renderer = new Renderer(); - this.cam = new Camera(); - this.commandExecutor = commandExecutor; + this.cam = world.getCamera(); + this.tasks = new ConcurrentLinkedDeque<>(); + this.world = world; + this.regularTasks = new ArrayList<>(); + } + + public void addRegularTask(Consumer task) { + this.regularTasks.add(task); + } + + public synchronized void scheduleTask(Runnable runnable) { + this.tasks.add(runnable); + } + + public synchronized Runnable getNextTask() { + return this.tasks.poll(); } public void run() { @@ -95,29 +119,27 @@ public class Window { } private void render(float delta, float aspectRatio) { - final float angle = 1f; - cam.setRotateAngle(cam.getRotateAngle() + angle * delta); - cam.setAspectRatio(aspectRatio); - renderer.Render(cam); - renderPieces(); + renderer.Update(cam); + renderer.RenderBoard(); + renderWorld(); + // renderPieces(); } - private Piece pieceAt(Coordinate pos) { - GetPieceAtCommand cmd = new GetPieceAtCommand(pos); - this.commandExecutor.executeCommand(cmd); - return cmd.getPiece(); + private void renderWorld() { + for (Entity entity : this.world.getEntites()) { + entity.render(this.renderer); + } } - private void renderPieces() { - for (int i = 0; i < Coordinate.VALUE_MAX; i++) { - for (int j = 0; j < Coordinate.VALUE_MAX; j++) { - Coordinate pos = new Coordinate(i, j); - Piece piece = pieceAt(pos); - if (piece == null) - continue; - this.renderer.RenderPiece(pieceAt(pos), pos); - } + private void executeTasks(float delta) { + Runnable task = getNextTask(); + while (task != null) { + task.run(); + task = getNextTask(); + } + for (Consumer consumer : regularTasks) { + consumer.accept(delta); } } @@ -154,7 +176,8 @@ public class Window { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer double currentTime = glfwGetTime(); - render((float) (currentTime - lastTime), (float) width[0] / (float) height[0]); + float deltaTime = (float) (currentTime - lastTime); + render(deltaTime, (float) width[0] / (float) height[0]); lastTime = glfwGetTime(); glfwSwapBuffers(window); // swap the color buffers @@ -163,6 +186,8 @@ public class Window { // invoked during this call. glfwPollEvents(); + executeTasks(deltaTime); + glfwGetWindowSize(window, width, height); glViewport(0, 0, width[0], height[0]); diff --git a/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java b/app/src/main/java/chess/view/DDDrender/loader/BoardModelLoader.java similarity index 98% rename from app/src/main/java/chess/view/DDDrender/BoardModelLoader.java rename to app/src/main/java/chess/view/DDDrender/loader/BoardModelLoader.java index 24144e1..0ae12fe 100644 --- a/app/src/main/java/chess/view/DDDrender/BoardModelLoader.java +++ b/app/src/main/java/chess/view/DDDrender/loader/BoardModelLoader.java @@ -1,4 +1,4 @@ -package chess.view.DDDrender; +package chess.view.DDDrender.loader; import org.joml.Vector3f; diff --git a/app/src/main/java/chess/view/DDDrender/ModelLoader.java b/app/src/main/java/chess/view/DDDrender/loader/ModelLoader.java similarity index 89% rename from app/src/main/java/chess/view/DDDrender/ModelLoader.java rename to app/src/main/java/chess/view/DDDrender/loader/ModelLoader.java index de2beb5..3499cb3 100644 --- a/app/src/main/java/chess/view/DDDrender/ModelLoader.java +++ b/app/src/main/java/chess/view/DDDrender/loader/ModelLoader.java @@ -1,4 +1,4 @@ -package chess.view.DDDrender; +package chess.view.DDDrender.loader; import java.io.IOException; import java.io.InputStream; @@ -6,9 +6,7 @@ import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.ArrayList; import java.util.List; -import java.util.stream.IntStream; -import org.lwjgl.PointerBuffer; import org.lwjgl.assimp.AIFace.Buffer; import org.lwjgl.assimp.AIMesh; import org.lwjgl.assimp.AINode; @@ -18,6 +16,7 @@ import org.lwjgl.assimp.Assimp; import org.lwjgl.system.MemoryUtil; import chess.view.AssetManager; +import chess.view.DDDrender.DDDModel; import chess.view.DDDrender.opengl.ElementBuffer; import chess.view.DDDrender.opengl.VertexArray; import chess.view.DDDrender.opengl.VertexBuffer; @@ -57,10 +56,7 @@ public class ModelLoader { int faceNumber = mesh.mNumFaces(); for (int i = 0; i < faceNumber; i++) { - int offset = indicies.size(); - int numIndices = faces.get(i).mNumIndices(); IntBuffer faceIndicies = faces.get(i).mIndices(); - // IntStream.of(faceIndicies.array()).forEach(indicies::add); for (int j = 0; j < faceIndicies.capacity(); j++) { indicies.add(faceIndicies.get(j)); } @@ -83,12 +79,12 @@ public class ModelLoader { normals.add(normal.z()); } - PointerBuffer vertexTexture = mesh.mTextureCoords(); - for (int i = 0; i < vertNumber; i++) { - // PointerBuffer buff = mesh.mTextureCoords(); - // textureCoords.add(buff.get(i).x()); - // textureCoords.add(buff.get(i).y()); - } + // PointerBuffer vertexTexture = mesh.mTextureCoords(); + // for (int i = 0; i < vertNumber; i++) { + // PointerBuffer buff = mesh.mTextureCoords(); + // textureCoords.add(buff.get(i).x()); + // textureCoords.add(buff.get(i).y()); + // } VertexBuffer positionVBO = new VertexBuffer(toFloatArray(positions), VERTEX_SIZE); positionVBO.AddVertexAttribPointer(VERTEX_POSITION_INDEX, VERTEX_SIZE, 0); diff --git a/app/src/main/java/chess/view/DDDrender/Piece3DModel.java b/app/src/main/java/chess/view/DDDrender/loader/Piece3DModel.java similarity index 95% rename from app/src/main/java/chess/view/DDDrender/Piece3DModel.java rename to app/src/main/java/chess/view/DDDrender/loader/Piece3DModel.java index 2081d84..7b9c21f 100644 --- a/app/src/main/java/chess/view/DDDrender/Piece3DModel.java +++ b/app/src/main/java/chess/view/DDDrender/loader/Piece3DModel.java @@ -1,4 +1,4 @@ -package chess.view.DDDrender; +package chess.view.DDDrender.loader; import java.io.IOException; import java.util.HashMap; @@ -13,6 +13,7 @@ import chess.model.pieces.Knight; import chess.model.pieces.Pawn; import chess.model.pieces.Queen; import chess.model.pieces.Rook; +import chess.view.DDDrender.DDDModel; public class Piece3DModel implements PieceVisitor { diff --git a/app/src/main/java/chess/view/DDDrender/world/Entity.java b/app/src/main/java/chess/view/DDDrender/world/Entity.java new file mode 100644 index 0000000..47f0b69 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/Entity.java @@ -0,0 +1,9 @@ +package chess.view.DDDrender.world; + +import chess.view.DDDrender.Renderer; + +public abstract class Entity { + + public abstract void render(Renderer renderer); + +} diff --git a/app/src/main/java/chess/view/DDDrender/world/PieceEntity.java b/app/src/main/java/chess/view/DDDrender/world/PieceEntity.java new file mode 100644 index 0000000..4f8d880 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/PieceEntity.java @@ -0,0 +1,55 @@ +package chess.view.DDDrender.world; + +import java.io.IOException; + +import org.joml.Vector3f; + +import chess.model.Piece; +import chess.view.DDDrender.DDDModel; +import chess.view.DDDrender.Renderer; +import chess.view.DDDrender.loader.Piece3DModel; + +public class PieceEntity extends Entity { + + private static final Piece3DModel modelLoader = new Piece3DModel(); + + private final Piece piece; + private Vector3f position; + private Vector3f color; + private float rotation; + private DDDModel model; + + public PieceEntity(Piece piece, Vector3f position, Vector3f color, float rotation) { + this.piece = piece; + this.position = position; + this.color = color; + this.rotation = rotation; + try { + this.model = modelLoader.getModel(piece); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public Piece getPiece() { + return piece; + } + + @Override + public void render(Renderer renderer) { + renderer.Render(model, color, position, rotation); + } + + public void setPosition(Vector3f position) { + this.position = position; + } + + public void setColor(Vector3f color) { + this.color = color; + } + + public void setRotation(float rotation) { + this.rotation = rotation; + } + +} diff --git a/app/src/main/java/chess/view/DDDrender/world/World.java b/app/src/main/java/chess/view/DDDrender/world/World.java new file mode 100644 index 0000000..0d30f44 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/World.java @@ -0,0 +1,40 @@ +package chess.view.DDDrender.world; + +import java.util.ArrayList; +import java.util.List; + +import chess.model.Piece; +import chess.view.DDDrender.Camera; + +public class World { + private final Camera camera; + private final List entites; + + public World(Camera camera) { + this.camera = camera; + this.entites = new ArrayList<>(); + } + + public PieceEntity getEntity(Piece piece) { + for (Entity entity : entites) { + if (entity instanceof PieceEntity p) { + if (p.getPiece() == piece) + return p; + } + } + return null; + } + + public void addEntity(Entity entity) { + this.entites.add(entity); + } + + public Camera getCamera() { + return camera; + } + + public List getEntites() { + return entites; + } + +} From 1b61eca58bc2a113856a90e8973f573e1719f696 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sun, 27 Apr 2025 11:02:55 +0200 Subject: [PATCH 17/40] models: remove uv --- .../chess/view/DDDrender/loader/ModelLoader.java | 15 +-------------- .../chess/view/DDDrender/shader/PieceShader.java | 3 +-- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/loader/ModelLoader.java b/app/src/main/java/chess/view/DDDrender/loader/ModelLoader.java index 3499cb3..851703c 100644 --- a/app/src/main/java/chess/view/DDDrender/loader/ModelLoader.java +++ b/app/src/main/java/chess/view/DDDrender/loader/ModelLoader.java @@ -24,10 +24,8 @@ import chess.view.DDDrender.opengl.VertexBuffer; public class ModelLoader { private static final int VERTEX_SIZE = 3; - private static final int UV_SIZE = 2; private static final int VERTEX_POSITION_INDEX = 0; - private static final int VERTEX_UV_INDEX = 1; - private static final int VERTEX_NORMAL_INDEX = 2; + private static final int VERTEX_NORMAL_INDEX = 1; private static float[] toFloatArray(List list) { float[] result = new float[list.size()]; @@ -47,7 +45,6 @@ public class ModelLoader { private static VertexArray processMesh(AIMesh mesh, AIScene scene) { List positions = new ArrayList<>(); - List textureCoords = new ArrayList<>(); List normals = new ArrayList<>(); List indicies = new ArrayList<>(); @@ -79,24 +76,14 @@ public class ModelLoader { normals.add(normal.z()); } - // PointerBuffer vertexTexture = mesh.mTextureCoords(); - // for (int i = 0; i < vertNumber; i++) { - // PointerBuffer buff = mesh.mTextureCoords(); - // textureCoords.add(buff.get(i).x()); - // textureCoords.add(buff.get(i).y()); - // } - VertexBuffer positionVBO = new VertexBuffer(toFloatArray(positions), VERTEX_SIZE); positionVBO.AddVertexAttribPointer(VERTEX_POSITION_INDEX, VERTEX_SIZE, 0); - VertexBuffer textureVBO = new VertexBuffer(toFloatArray(textureCoords), UV_SIZE); - textureVBO.AddVertexAttribPointer(VERTEX_UV_INDEX, UV_SIZE, 0); VertexBuffer normalVBO = new VertexBuffer(toFloatArray(normals), VERTEX_SIZE); normalVBO.AddVertexAttribPointer(VERTEX_NORMAL_INDEX, VERTEX_SIZE, 0); VertexArray vao = new VertexArray(new ElementBuffer(toIntArray(indicies))); vao.Bind(); vao.BindVertexBuffer(positionVBO); - vao.BindVertexBuffer(textureVBO); vao.BindVertexBuffer(normalVBO); vao.Unbind(); return vao; diff --git a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java index 1f49eaf..48bee93 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java @@ -11,8 +11,7 @@ public class PieceShader extends ShaderProgram { #version 330 layout(location = 0) in vec3 position; - layout(location = 1) in vec2 uv; - layout(location = 2) in vec3 normal; + layout(location = 1) in vec3 normal; uniform mat4 projectionMatrix; uniform mat4 viewMatrix; From cbbce43ede7dfe26fe54af1f10a256625538b9fa Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sun, 27 Apr 2025 20:12:38 +0200 Subject: [PATCH 18/40] raycast cursor --- .../java/chess/view/DDDrender/Camera.java | 23 +++++++++++-- .../chess/view/DDDrender/DDDPlacement.java | 11 +++++-- .../java/chess/view/DDDrender/DDDView.java | 12 +++++++ .../java/chess/view/DDDrender/Window.java | 33 +++++++++++++++++++ 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/Camera.java b/app/src/main/java/chess/view/DDDrender/Camera.java index 02cbf8e..03f5375 100644 --- a/app/src/main/java/chess/view/DDDrender/Camera.java +++ b/app/src/main/java/chess/view/DDDrender/Camera.java @@ -1,7 +1,9 @@ package chess.view.DDDrender; import org.joml.Matrix4f; +import org.joml.Vector2f; import org.joml.Vector3f; +import org.joml.Vector4f; public class Camera { public static final float fov = 70.0f; @@ -14,7 +16,6 @@ public class Camera { private final float distance = 1.5f; private final float camHeight = 1.5f; - private float aspectRatio; private float angle; @@ -22,7 +23,7 @@ public class Camera { public Camera() { this.pos = new Vector3f(0.0f, camHeight, 0.0f); - this.angle = 0.0f; + setRotateAngle(0.0f); } public void move(float x, float y) { @@ -80,4 +81,22 @@ public class Camera { public Matrix4f getViewMatrix() { return new Matrix4f().lookAt(pos, center, up); } + + public Vector2f getCursorWorldFloorPos(Vector2f screenPos, int windowWidth, int windowHeight) { + float relativeX = (screenPos.x / (float) windowWidth * 2.0f) - 1.0f; + float relativeY = 1.0f - (screenPos.y / (float) windowHeight * 2.0f); + + Vector4f rayClip = new Vector4f(relativeX, relativeY, -1.0f, 1.0f); + + Vector4f rayEye = getPerspectiveMatrix().invert().transform(rayClip); + + rayEye = new Vector4f(rayEye.x, rayEye.y, -1.0f, 0.0f); + + Vector4f rayWorld = getViewMatrix().invert().transform(rayEye); + + float lambda = -this.pos.y / rayWorld.y; + + return new Vector2f(lambda * rayWorld.x + this.pos.x, lambda * + rayWorld.z + this.pos.z); + } } diff --git a/app/src/main/java/chess/view/DDDrender/DDDPlacement.java b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java index 9300027..81275b8 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDPlacement.java +++ b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java @@ -5,12 +5,17 @@ import org.joml.Vector2f; import chess.model.Coordinate; class DDDPlacement { - static public Vector2f coordinates_to_vector(Coordinate coo) { + public static Vector2f coordinates_to_vector(Coordinate coo) { return coordinates_to_vector(coo.getX(), coo.getY()); } - static public Vector2f coordinates_to_vector(float x, float y) { + public static Vector2f coordinates_to_vector(float x, float y) { return new Vector2f(1.0f - 0.125f - x * 0.250f, 1.0f - 0.125f - y * 0.250f); } - + + public static Coordinate vector_to_coordinates(Vector2f pos) { + int x = (int) ((1.0f - pos.x) * 4.0f); + int y = (int) ((1.0f - pos.y) * 4.0f); + return new Coordinate(x, y); + } } \ No newline at end of file diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index b7b657f..0163bb2 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -28,11 +28,23 @@ public class DDDView extends GameAdaptator { this.world = new World(new Camera()); this.window = new Window(this.renderer, this.world); this.window.OnCellClick.connect(this::onCellClick); + this.window.OnCellEnter.connect(this::onCellEnter); + this.window.OnCellExit.connect(this::onCellExit); } // Invoked when a cell is clicked private void onCellClick(Coordinate coordinate) { + System.out.println("Cell clicked : " + coordinate); + } + // Invoked when a cell is hovered + private void onCellEnter(Coordinate coordinate) { + System.out.println("Enter : " + coordinate); + } + + // Invoked when a cell is not hovered anymore + private void onCellExit(Coordinate coordinate) { + System.out.println("Exit : " + coordinate); } private Piece pieceAt(Coordinate pos) { diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 8841569..208885e 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -1,5 +1,6 @@ package chess.view.DDDrender; +import org.joml.Vector2f; import org.lwjgl.*; import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; @@ -35,7 +36,11 @@ public class Window { private final Queue tasks; private final List> regularTasks; + private Coordinate lastCell = null; + public final Signal1 OnCellClick = new Signal1<>(); + public final Signal1 OnCellEnter = new Signal1<>(); + public final Signal1 OnCellExit = new Signal1<>(); public Window(Renderer renderer, World world) { this.renderer = new Renderer(); @@ -143,6 +148,32 @@ public class Window { } } + private void checkCursor(int windowWidth, int windowHeight) { + double x[] = new double[1]; + double y[] = new double[1]; + glfwGetCursorPos(this.window, x, y); + Vector2f cursorPos = this.cam.getCursorWorldFloorPos(new Vector2f((float) x[0], (float) y[0]), windowWidth, windowHeight); + Coordinate selectedCell = DDDPlacement.vector_to_coordinates(cursorPos); + if (this.lastCell == null) { + this.lastCell = selectedCell; + if (selectedCell.isValid()) + this.OnCellEnter.emit(selectedCell); + } + else if (!this.lastCell.equals(selectedCell)) { + if (this.lastCell.isValid()) + this.OnCellExit.emit(this.lastCell); + if (selectedCell.isValid()) + this.OnCellEnter.emit(selectedCell); + this.lastCell = selectedCell; + } + glfwSetMouseButtonCallback(this.window, (window, button, action, mods) -> { + if (button == GLFW_MOUSE_BUTTON_1 && action == GLFW_PRESS) { + if (this.lastCell.isValid()) + this.OnCellClick.emit(this.lastCell); + } + }); + } + private void loop() { // This line is critical for LWJGL's interoperation with GLFW's // OpenGL context, or any context that is managed externally. @@ -186,6 +217,8 @@ public class Window { // invoked during this call. glfwPollEvents(); + checkCursor(width[0], height[0]); + executeTasks(deltaTime); glfwGetWindowSize(window, width, height); From b57fa1482b348c2878f5b93300c28d8bd6991b9f Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sun, 27 Apr 2025 20:18:31 +0200 Subject: [PATCH 19/40] add small example --- .../java/chess/view/DDDrender/DDDView.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 0163bb2..2a8a994 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -39,12 +39,19 @@ public class DDDView extends GameAdaptator { // Invoked when a cell is hovered private void onCellEnter(Coordinate coordinate) { - System.out.println("Enter : " + coordinate); + // small test turning a piece red when hovered + Piece p = pieceAt(coordinate); + if (p == null) + return; + this.world.getEntity(p).setColor(new Vector3f(1, 0, 0)); } // Invoked when a cell is not hovered anymore private void onCellExit(Coordinate coordinate) { - System.out.println("Exit : " + coordinate); + Piece p = pieceAt(coordinate); + if (p == null) + return; + this.world.getEntity(p).setColor(p.getColor() == Color.White ? WHITE : BLACK); } private Piece pieceAt(Coordinate pos) { @@ -79,11 +86,11 @@ public class DDDView extends GameAdaptator { } public void run() { - this.window.addRegularTask((delta) -> { - final float angle = 1f; - final Camera cam = this.world.getCamera(); - cam.setRotateAngle(cam.getRotateAngle() + angle * delta); - }); + // this.window.addRegularTask((delta) -> { + // final float angle = 1f; + // final Camera cam = this.world.getCamera(); + // cam.setRotateAngle(cam.getRotateAngle() + angle * delta); + // }); this.window.run(); } From de4ed869ea39d5bc3872a5df3c05623fd824b07e Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 28 Apr 2025 11:04:41 +0200 Subject: [PATCH 20/40] feat: change board cells color --- .../java/chess/view/DDDrender/DDDView.java | 14 +++++-- .../java/chess/view/DDDrender/Renderer.java | 11 ++--- .../java/chess/view/DDDrender/Window.java | 2 - .../view/DDDrender/opengl/VertexArray.java | 4 ++ .../view/DDDrender/opengl/VertexBuffer.java | 6 +++ .../view/DDDrender/world/BoardEntity.java | 41 +++++++++++++++++++ 6 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/chess/view/DDDrender/world/BoardEntity.java diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 2a8a994..510aa75 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -9,6 +9,7 @@ import chess.controller.event.GameAdaptator; import chess.model.Color; import chess.model.Coordinate; import chess.model.Piece; +import chess.view.DDDrender.world.BoardEntity; import chess.view.DDDrender.world.PieceEntity; import chess.view.DDDrender.world.World; @@ -21,15 +22,13 @@ public class DDDView extends GameAdaptator { private final Window window; private final Renderer renderer; private final World world; + private BoardEntity boardEntity; public DDDView(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; this.renderer = new Renderer(); this.world = new World(new Camera()); this.window = new Window(this.renderer, this.world); - this.window.OnCellClick.connect(this::onCellClick); - this.window.OnCellEnter.connect(this::onCellEnter); - this.window.OnCellExit.connect(this::onCellExit); } // Invoked when a cell is clicked @@ -39,7 +38,8 @@ public class DDDView extends GameAdaptator { // Invoked when a cell is hovered private void onCellEnter(Coordinate coordinate) { - // small test turning a piece red when hovered + // small test turning a cell red when hovered + this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); Piece p = pieceAt(coordinate); if (p == null) return; @@ -48,6 +48,7 @@ public class DDDView extends GameAdaptator { // Invoked when a cell is not hovered anymore private void onCellExit(Coordinate coordinate) { + this.boardEntity.resetCellColor(coordinate); Piece p = pieceAt(coordinate); if (p == null) return; @@ -64,6 +65,9 @@ public class DDDView extends GameAdaptator { public void onGameStart() { this.window.scheduleTask(() -> { initBoard(); + this.window.OnCellClick.connect(this::onCellClick); + this.window.OnCellEnter.connect(this::onCellEnter); + this.window.OnCellExit.connect(this::onCellExit); }); } @@ -83,6 +87,8 @@ public class DDDView extends GameAdaptator { piece.getColor() == Color.White ? 0.0f : (float) Math.PI)); } } + this.boardEntity = new BoardEntity(); + this.world.addEntity(this.boardEntity); } public void run() { diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 20e402e..1cd2fca 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -6,7 +6,6 @@ import org.joml.Matrix4f; import org.joml.Vector3f; import org.lwjgl.opengl.GL30; -import chess.view.DDDrender.loader.BoardModelLoader; import chess.view.DDDrender.opengl.VertexArray; import chess.view.DDDrender.shader.BoardShader; import chess.view.DDDrender.shader.PieceShader; @@ -15,7 +14,6 @@ import chess.view.DDDrender.shader.ShaderProgram; public class Renderer { private BoardShader boardShader; private PieceShader pieceShader; - private VertexArray boardVao; public Renderer() { this.boardShader = new BoardShader(); @@ -25,7 +23,6 @@ public class Renderer { public void Init() { boardShader.LoadShader(); pieceShader.LoadShader(); - this.boardVao = BoardModelLoader.GetBoardModel(); } public void Update(Camera cam) { @@ -34,10 +31,6 @@ public class Renderer { this.pieceShader.Start(); this.pieceShader.SetCamMatrix(cam); } - - public void RenderBoard() { - RenderVao(this.boardShader, this.boardVao); - } public void Render(DDDModel model, Vector3f color, Vector3f position, float rotation) { this.pieceShader.Start(); @@ -59,4 +52,8 @@ public class Renderer { GL30.glDrawElements(GL30.GL_TRIANGLES, vertexArray.GetVertexCount(), GL_UNSIGNED_INT, 0); vertexArray.Unbind(); } + + public BoardShader getBoardShader() { + return boardShader; + } } diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 208885e..bf4eeb6 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -126,9 +126,7 @@ public class Window { private void render(float delta, float aspectRatio) { cam.setAspectRatio(aspectRatio); renderer.Update(cam); - renderer.RenderBoard(); renderWorld(); - // renderPieces(); } private void renderWorld() { diff --git a/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java index 74ee5d9..d4c8143 100644 --- a/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java @@ -44,4 +44,8 @@ public class VertexArray { private void BindElementArrayBuffer() { this.elementBuffer.Bind(); } + + public List getVertexBuffers() { + return vertexBuffers; + } } diff --git a/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java index 82439ec..b3add99 100644 --- a/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java @@ -22,6 +22,12 @@ public class VertexBuffer { Unbind(); } + public void UpdateData(int offset, float[] data) { + Bind(); + GL30.glBufferSubData(GL30.GL_ARRAY_BUFFER, offset, data); + Unbind(); + } + public void Destroy() { GL30.glDeleteBuffers(id); } diff --git a/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java b/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java new file mode 100644 index 0000000..9a20295 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java @@ -0,0 +1,41 @@ +package chess.view.DDDrender.world; + +import org.joml.Vector3f; + +import chess.model.Coordinate; +import chess.view.DDDrender.Renderer; +import chess.view.DDDrender.loader.BoardModelLoader; +import chess.view.DDDrender.opengl.VertexArray; +import chess.view.DDDrender.opengl.VertexBuffer; + +public class BoardEntity extends Entity { + + private final VertexArray vao; + private final VertexBuffer colorVbo; + + private static final Vector3f WHITE = new Vector3f(1, 1, 1); + private static final Vector3f BLACK = new Vector3f(0, 0, 0); + + public BoardEntity() { + this.vao = BoardModelLoader.GetBoardModel(); + this.colorVbo = this.vao.getVertexBuffers().get(1); + } + + public void setCellColor(Coordinate coord, Vector3f color) { + float[] data = { color.x, color.y, color.z, color.x, color.y, color.z, color.x, color.y, color.z, color.x, + color.y, color.z}; + int cellNumber = (Coordinate.VALUE_MAX - 1 - coord.getX()) * Coordinate.VALUE_MAX + Coordinate.VALUE_MAX - 1 - coord.getY(); + int offset = cellNumber * 4 * 4 * 3; + this.colorVbo.UpdateData(offset, data); + } + + public void resetCellColor(Coordinate coord) { + setCellColor(coord, (coord.getX() + coord.getY()) % 2 == 0 ? WHITE : BLACK); + } + + @Override + public void render(Renderer renderer) { + renderer.RenderVao(renderer.getBoardShader(), vao); + } + +} From dd043794d6723b8584eede7035eefedd98141762 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 28 Apr 2025 17:31:46 +0200 Subject: [PATCH 21/40] add comment --- app/src/main/java/chess/view/DDDrender/DDDView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 510aa75..c7982e5 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -65,6 +65,7 @@ public class DDDView extends GameAdaptator { public void onGameStart() { this.window.scheduleTask(() -> { initBoard(); + // start listening to mouse events this.window.OnCellClick.connect(this::onCellClick); this.window.OnCellEnter.connect(this::onCellEnter); this.window.OnCellExit.connect(this::onCellExit); From 5f70daea919875a3ac55dd01b8810d0cbd8cf06f Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 28 Apr 2025 17:32:24 +0200 Subject: [PATCH 22/40] board model add constants --- .../DDDrender/loader/BoardModelLoader.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/loader/BoardModelLoader.java b/app/src/main/java/chess/view/DDDrender/loader/BoardModelLoader.java index 0ae12fe..af5f027 100644 --- a/app/src/main/java/chess/view/DDDrender/loader/BoardModelLoader.java +++ b/app/src/main/java/chess/view/DDDrender/loader/BoardModelLoader.java @@ -8,11 +8,14 @@ import chess.view.DDDrender.opengl.VertexBuffer; public class BoardModelLoader { - private static int BOARD_WIDTH = 8; - private static int BOARD_HEIGHT = 8; - private static int BOARD_SIZE = BOARD_WIDTH * BOARD_HEIGHT; - private static int SQUARE_VERTEX_COUNT = 4; + private static final int BOARD_WIDTH = 8; + private static final int BOARD_HEIGHT = 8; + private static final int BOARD_SIZE = BOARD_WIDTH * BOARD_HEIGHT; + private static final int SQUARE_VERTEX_COUNT = 4; + private static final Vector3f WHITE = new Vector3f(1, 1, 1); + private static final Vector3f BLACK = new Vector3f(0, 0, 0); + private static float[] GetBoardPositions() { float[] positions = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3]; for (int i = 0; i < BOARD_WIDTH; i++) { @@ -51,12 +54,7 @@ public class BoardModelLoader { float[] colors = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3]; for (int i = 0; i < BOARD_WIDTH; i++) { for (int j = 0; j < BOARD_HEIGHT; j++) { - Vector3f color; - if ((i + j) % 2 == 0) { - color = new Vector3f(1.0f, 1.0f, 1.0f); - } else { - color = new Vector3f(0.0f, 0.0f, 0.0f); - } + Vector3f color = (i + j) % 2 == 0 ? WHITE : BLACK; int squareIndex = i * BOARD_WIDTH + j; for (int k = 0; k < SQUARE_VERTEX_COUNT; k++) { colors[squareIndex * SQUARE_VERTEX_COUNT * 3 + k * 3] = color.x; From 0c6ab1df4be634c44c48616ec8903501657b959f Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 28 Apr 2025 17:52:29 +0200 Subject: [PATCH 23/40] add 3d position cache --- .../java/chess/view/DDDrender/DDDView.java | 37 ++++++++++++------ .../java/chess/view/DDDrender/Window.java | 4 +- .../view/DDDrender/world/ModelEntity.java | 39 +++++++++++++++++++ .../view/DDDrender/world/PieceEntity.java | 36 ++--------------- .../chess/view/DDDrender/world/World.java | 28 +++++++++---- 5 files changed, 90 insertions(+), 54 deletions(-) create mode 100644 app/src/main/java/chess/view/DDDrender/world/ModelEntity.java diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index c7982e5..1073735 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -1,5 +1,7 @@ package chess.view.DDDrender; +import java.io.IOException; + import org.joml.Vector2f; import org.joml.Vector3f; @@ -8,6 +10,7 @@ import chess.controller.commands.GetPieceAtCommand; import chess.controller.event.GameAdaptator; import chess.model.Color; import chess.model.Coordinate; +import chess.model.Move; import chess.model.Piece; import chess.view.DDDrender.world.BoardEntity; import chess.view.DDDrender.world.PieceEntity; @@ -27,8 +30,8 @@ public class DDDView extends GameAdaptator { public DDDView(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; this.renderer = new Renderer(); - this.world = new World(new Camera()); - this.window = new Window(this.renderer, this.world); + this.world = new World(); + this.window = new Window(this.renderer, this.world, new Camera()); } // Invoked when a cell is clicked @@ -43,7 +46,7 @@ public class DDDView extends GameAdaptator { Piece p = pieceAt(coordinate); if (p == null) return; - this.world.getEntity(p).setColor(new Vector3f(1, 0, 0)); + this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); } // Invoked when a cell is not hovered anymore @@ -52,7 +55,7 @@ public class DDDView extends GameAdaptator { Piece p = pieceAt(coordinate); if (p == null) return; - this.world.getEntity(p).setColor(p.getColor() == Color.White ? WHITE : BLACK); + this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK); } private Piece pieceAt(Coordinate pos) { @@ -64,7 +67,11 @@ public class DDDView extends GameAdaptator { @Override public void onGameStart() { this.window.scheduleTask(() -> { - initBoard(); + try { + initBoard(); + } catch (IOException e) { + e.printStackTrace(); + } // start listening to mouse events this.window.OnCellClick.connect(this::onCellClick); this.window.OnCellEnter.connect(this::onCellEnter); @@ -72,7 +79,7 @@ public class DDDView extends GameAdaptator { }); } - private void initBoard() { + private void initBoard() throws IOException { for (int i = 0; i < Coordinate.VALUE_MAX; i++) { for (int j = 0; j < Coordinate.VALUE_MAX; j++) { Coordinate pos = new Coordinate(i, j); @@ -83,20 +90,28 @@ public class DDDView extends GameAdaptator { Vector2f pieceBoardPos = DDDPlacement.coordinates_to_vector(pos); Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); - this.world.addEntity(new PieceEntity(piece, pieceWorldPos, + PieceEntity entity = new PieceEntity(piece, pieceWorldPos, piece.getColor() == Color.White ? WHITE : BLACK, - piece.getColor() == Color.White ? 0.0f : (float) Math.PI)); + piece.getColor() == Color.White ? 0.0f : (float) Math.PI); + + this.world.addPiece(entity, pos); } } this.boardEntity = new BoardEntity(); this.world.addEntity(this.boardEntity); } + @Override + public void onMove(Move move) { + // update world internal positions + this.world.movePiece(this.world.getPiece(move.getStart()), move.getFinish()); + } + public void run() { // this.window.addRegularTask((delta) -> { - // final float angle = 1f; - // final Camera cam = this.world.getCamera(); - // cam.setRotateAngle(cam.getRotateAngle() + angle * delta); + // final float angle = 1f; + // final Camera cam = this.world.getCamera(); + // cam.setRotateAngle(cam.getRotateAngle() + angle * delta); // }); this.window.run(); } diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index bf4eeb6..5ae081b 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -42,9 +42,9 @@ public class Window { public final Signal1 OnCellEnter = new Signal1<>(); public final Signal1 OnCellExit = new Signal1<>(); - public Window(Renderer renderer, World world) { + public Window(Renderer renderer, World world, Camera camera) { this.renderer = new Renderer(); - this.cam = world.getCamera(); + this.cam = camera; this.tasks = new ConcurrentLinkedDeque<>(); this.world = world; this.regularTasks = new ArrayList<>(); diff --git a/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java b/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java new file mode 100644 index 0000000..4304eea --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java @@ -0,0 +1,39 @@ +package chess.view.DDDrender.world; + +import org.joml.Vector3f; + +import chess.view.DDDrender.DDDModel; +import chess.view.DDDrender.Renderer; + +public class ModelEntity extends Entity { + + protected Vector3f position; + protected Vector3f color; + protected float rotation; + protected DDDModel model; + + public ModelEntity(DDDModel model, Vector3f position, Vector3f color, float rotation) { + this.position = position; + this.color = color; + this.rotation = rotation; + this.model = model; + } + + @Override + public void render(Renderer renderer) { + renderer.Render(model, color, position, rotation); + } + + public void setPosition(Vector3f position) { + this.position = position; + } + + public void setColor(Vector3f color) { + this.color = color; + } + + public void setRotation(float rotation) { + this.rotation = rotation; + } + +} diff --git a/app/src/main/java/chess/view/DDDrender/world/PieceEntity.java b/app/src/main/java/chess/view/DDDrender/world/PieceEntity.java index 4f8d880..7192134 100644 --- a/app/src/main/java/chess/view/DDDrender/world/PieceEntity.java +++ b/app/src/main/java/chess/view/DDDrender/world/PieceEntity.java @@ -5,51 +5,21 @@ import java.io.IOException; import org.joml.Vector3f; import chess.model.Piece; -import chess.view.DDDrender.DDDModel; -import chess.view.DDDrender.Renderer; import chess.view.DDDrender.loader.Piece3DModel; -public class PieceEntity extends Entity { +public class PieceEntity extends ModelEntity { private static final Piece3DModel modelLoader = new Piece3DModel(); private final Piece piece; - private Vector3f position; - private Vector3f color; - private float rotation; - private DDDModel model; - public PieceEntity(Piece piece, Vector3f position, Vector3f color, float rotation) { + public PieceEntity(Piece piece, Vector3f position, Vector3f color, float rotation) throws IOException { + super(modelLoader.getModel(piece), position, color, rotation); this.piece = piece; - this.position = position; - this.color = color; - this.rotation = rotation; - try { - this.model = modelLoader.getModel(piece); - } catch (IOException e) { - e.printStackTrace(); - } } public Piece getPiece() { return piece; } - - @Override - public void render(Renderer renderer) { - renderer.Render(model, color, position, rotation); - } - - public void setPosition(Vector3f position) { - this.position = position; - } - - public void setColor(Vector3f color) { - this.color = color; - } - - public void setRotation(float rotation) { - this.rotation = rotation; - } } diff --git a/app/src/main/java/chess/view/DDDrender/world/World.java b/app/src/main/java/chess/view/DDDrender/world/World.java index 0d30f44..8c98ba8 100644 --- a/app/src/main/java/chess/view/DDDrender/world/World.java +++ b/app/src/main/java/chess/view/DDDrender/world/World.java @@ -3,16 +3,19 @@ package chess.view.DDDrender.world; import java.util.ArrayList; import java.util.List; +import chess.model.Coordinate; import chess.model.Piece; -import chess.view.DDDrender.Camera; public class World { - private final Camera camera; + /** Renderable entity list */ private final List entites; - public World(Camera camera) { - this.camera = camera; + /** provides fast access to 3d pieces */ + private final PieceEntity[] pieces; + + public World() { this.entites = new ArrayList<>(); + this.pieces = new PieceEntity[Coordinate.VALUE_MAX * Coordinate.VALUE_MAX]; } public PieceEntity getEntity(Piece piece) { @@ -25,12 +28,21 @@ public class World { return null; } - public void addEntity(Entity entity) { - this.entites.add(entity); + public void addPiece(PieceEntity entity, Coordinate coordinate) { + addEntity(entity); + movePiece(entity, coordinate); + } + + public void movePiece(PieceEntity entity, Coordinate coordinate) { + pieces[coordinate.toIndex()] = entity; } - public Camera getCamera() { - return camera; + public PieceEntity getPiece(Coordinate coordinate) { + return pieces[coordinate.toIndex()]; + } + + public void addEntity(Entity entity) { + this.entites.add(entity); } public List getEntites() { From 533e6260d562004ece07d57d38315bb9af169609 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 28 Apr 2025 18:06:37 +0200 Subject: [PATCH 24/40] refactor movePiece --- app/src/main/java/chess/view/DDDrender/DDDView.java | 2 +- .../main/java/chess/view/DDDrender/world/World.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 1073735..d37bc26 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -104,7 +104,7 @@ public class DDDView extends GameAdaptator { @Override public void onMove(Move move) { // update world internal positions - this.world.movePiece(this.world.getPiece(move.getStart()), move.getFinish()); + this.world.movePiece(this.world.getPiece(move.getStart()), move); } public void run() { diff --git a/app/src/main/java/chess/view/DDDrender/world/World.java b/app/src/main/java/chess/view/DDDrender/world/World.java index 8c98ba8..bcfa75e 100644 --- a/app/src/main/java/chess/view/DDDrender/world/World.java +++ b/app/src/main/java/chess/view/DDDrender/world/World.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import chess.model.Coordinate; +import chess.model.Move; import chess.model.Piece; public class World { @@ -30,11 +31,16 @@ public class World { public void addPiece(PieceEntity entity, Coordinate coordinate) { addEntity(entity); - movePiece(entity, coordinate); + setPieceCoords(entity, coordinate); + } + + private void setPieceCoords(PieceEntity entity, Coordinate coordinate) { + pieces[coordinate.toIndex()] = entity; } - public void movePiece(PieceEntity entity, Coordinate coordinate) { - pieces[coordinate.toIndex()] = entity; + public void movePiece(PieceEntity entity, Move move) { + setPieceCoords(entity, move.getFinish()); + setPieceCoords(null, move.getStart()); } public PieceEntity getPiece(Coordinate coordinate) { From 0fb24263e03bd38e2d42c97656d55890533ebf0d Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 28 Apr 2025 18:08:10 +0200 Subject: [PATCH 25/40] camel case --- app/src/main/java/chess/view/DDDrender/DDDPlacement.java | 8 ++++---- app/src/main/java/chess/view/DDDrender/DDDView.java | 2 +- app/src/main/java/chess/view/DDDrender/Window.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDPlacement.java b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java index 81275b8..ce0cc19 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDPlacement.java +++ b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java @@ -5,15 +5,15 @@ import org.joml.Vector2f; import chess.model.Coordinate; class DDDPlacement { - public static Vector2f coordinates_to_vector(Coordinate coo) { - return coordinates_to_vector(coo.getX(), coo.getY()); + public static Vector2f coordinatesToVector(Coordinate coo) { + return coordinatesToVector(coo.getX(), coo.getY()); } - public static Vector2f coordinates_to_vector(float x, float y) { + public static Vector2f coordinatesToVector(float x, float y) { return new Vector2f(1.0f - 0.125f - x * 0.250f, 1.0f - 0.125f - y * 0.250f); } - public static Coordinate vector_to_coordinates(Vector2f pos) { + public static Coordinate vectorToCoordinates(Vector2f pos) { int x = (int) ((1.0f - pos.x) * 4.0f); int y = (int) ((1.0f - pos.y) * 4.0f); return new Coordinate(x, y); diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index d37bc26..73156f0 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -87,7 +87,7 @@ public class DDDView extends GameAdaptator { if (piece == null) continue; - Vector2f pieceBoardPos = DDDPlacement.coordinates_to_vector(pos); + Vector2f pieceBoardPos = DDDPlacement.coordinatesToVector(pos); Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); PieceEntity entity = new PieceEntity(piece, pieceWorldPos, diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 5ae081b..0e31865 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -151,7 +151,7 @@ public class Window { double y[] = new double[1]; glfwGetCursorPos(this.window, x, y); Vector2f cursorPos = this.cam.getCursorWorldFloorPos(new Vector2f((float) x[0], (float) y[0]), windowWidth, windowHeight); - Coordinate selectedCell = DDDPlacement.vector_to_coordinates(cursorPos); + Coordinate selectedCell = DDDPlacement.vectorToCoordinates(cursorPos); if (this.lastCell == null) { this.lastCell = selectedCell; if (selectedCell.isValid()) From ec98b05d61d7953aa81deb5efb1ec7771d3fea7f Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Mon, 28 Apr 2025 18:23:29 +0200 Subject: [PATCH 26/40] free opengl resources --- .../java/chess/view/DDDrender/DDDModel.java | 11 ++++++++- .../java/chess/view/DDDrender/DDDView.java | 12 +++++++--- .../java/chess/view/DDDrender/Renderer.java | 11 ++++++++- .../java/chess/view/DDDrender/Window.java | 9 ++++++- .../view/DDDrender/opengl/ElementBuffer.java | 18 ++++++++------ .../view/DDDrender/opengl/VertexArray.java | 24 ++++++++++++------- .../view/DDDrender/opengl/VertexBuffer.java | 19 ++++++++------- .../view/DDDrender/shader/ShaderProgram.java | 11 ++++++++- .../view/DDDrender/world/BoardEntity.java | 7 ++++++ .../chess/view/DDDrender/world/Entity.java | 6 +++-- .../view/DDDrender/world/ModelEntity.java | 7 ++++++ .../chess/view/DDDrender/world/World.java | 11 ++++++++- 12 files changed, 113 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDModel.java b/app/src/main/java/chess/view/DDDrender/DDDModel.java index 5e83978..a2aec9f 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDModel.java +++ b/app/src/main/java/chess/view/DDDrender/DDDModel.java @@ -1,10 +1,12 @@ package chess.view.DDDrender; +import java.io.Closeable; +import java.io.IOException; import java.util.List; import chess.view.DDDrender.opengl.VertexArray; -public class DDDModel { +public class DDDModel implements Closeable { private final List vaos; public DDDModel(List vaos) { @@ -15,4 +17,11 @@ public class DDDModel { return vaos; } + @Override + public void close() throws IOException { + for (VertexArray vertexArray : vaos) { + vertexArray.close(); + } + } + } diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 73156f0..599f05e 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -23,15 +23,13 @@ public class DDDView extends GameAdaptator { private final CommandExecutor commandExecutor; private final Window window; - private final Renderer renderer; private final World world; private BoardEntity boardEntity; public DDDView(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; - this.renderer = new Renderer(); this.world = new World(); - this.window = new Window(this.renderer, this.world, new Camera()); + this.window = new Window(new Renderer(), this.world, new Camera()); } // Invoked when a cell is clicked @@ -114,6 +112,14 @@ public class DDDView extends GameAdaptator { // cam.setRotateAngle(cam.getRotateAngle() + angle * delta); // }); this.window.run(); + + // free OpenGL resources + try { + this.window.close(); + this.world.close(); + } catch (IOException e) { + e.printStackTrace(); + } } } diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 1cd2fca..c8781b7 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -2,6 +2,9 @@ package chess.view.DDDrender; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import java.io.Closeable; +import java.io.IOException; + import org.joml.Matrix4f; import org.joml.Vector3f; import org.lwjgl.opengl.GL30; @@ -11,7 +14,7 @@ import chess.view.DDDrender.shader.BoardShader; import chess.view.DDDrender.shader.PieceShader; import chess.view.DDDrender.shader.ShaderProgram; -public class Renderer { +public class Renderer implements Closeable{ private BoardShader boardShader; private PieceShader pieceShader; @@ -56,4 +59,10 @@ public class Renderer { public BoardShader getBoardShader() { return boardShader; } + + @Override + public void close() throws IOException { + this.boardShader.close(); + this.pieceShader.close(); + } } diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 0e31865..88e610e 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -11,6 +11,8 @@ import chess.view.DDDrender.world.Entity; import chess.view.DDDrender.world.World; import common.Signal1; +import java.io.Closeable; +import java.io.IOException; import java.nio.*; import java.util.ArrayList; import java.util.List; @@ -24,7 +26,7 @@ import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.system.MemoryStack.*; import static org.lwjgl.system.MemoryUtil.*; -public class Window { +public class Window implements Closeable{ // The window handle private long window; @@ -225,4 +227,9 @@ public class Window { } } + @Override + public void close() throws IOException { + this.renderer.close(); + } + } \ No newline at end of file diff --git a/app/src/main/java/chess/view/DDDrender/opengl/ElementBuffer.java b/app/src/main/java/chess/view/DDDrender/opengl/ElementBuffer.java index bd89480..6d18cb1 100644 --- a/app/src/main/java/chess/view/DDDrender/opengl/ElementBuffer.java +++ b/app/src/main/java/chess/view/DDDrender/opengl/ElementBuffer.java @@ -1,10 +1,13 @@ package chess.view.DDDrender.opengl; +import java.io.Closeable; +import java.io.IOException; + import org.lwjgl.opengl.GL30; -public class ElementBuffer { - private int id; - private int indiciesCount; +public class ElementBuffer implements Closeable { + private final int id; + private final int indiciesCount; public ElementBuffer(int[] indicies) { this.indiciesCount = indicies.length; @@ -15,10 +18,6 @@ public class ElementBuffer { Unbind(); } - public void Destroy() { - GL30.glDeleteBuffers(this.id); - } - public void Bind() { GL30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.id); } @@ -30,4 +29,9 @@ public class ElementBuffer { public int GetIndiciesCount() { return this.indiciesCount; } + + @Override + public void close() throws IOException { + GL30.glDeleteBuffers(this.id); + } } diff --git a/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java index d4c8143..bf9fb45 100644 --- a/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java @@ -1,14 +1,17 @@ package chess.view.DDDrender.opengl; +import java.io.Closeable; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.lwjgl.opengl.GL30; -public class VertexArray { - private int id; - private ElementBuffer elementBuffer; - private List vertexBuffers; +public class VertexArray implements Closeable { + + private final int id; + private final ElementBuffer elementBuffer; + private final List vertexBuffers; public VertexArray(ElementBuffer elementBuffer) { this.id = GL30.glGenVertexArrays(); @@ -19,10 +22,6 @@ public class VertexArray { Unbind(); } - public void Destroy() { - GL30.glDeleteBuffers(this.id); - } - public int GetVertexCount() { return this.elementBuffer.GetIndiciesCount(); } @@ -48,4 +47,13 @@ public class VertexArray { public List getVertexBuffers() { return vertexBuffers; } + + @Override + public void close() throws IOException { + GL30.glDeleteBuffers(this.id); + for (VertexBuffer vertexBuffer : vertexBuffers) { + vertexBuffer.close(); + } + elementBuffer.close(); + } } diff --git a/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java index b3add99..191c4d3 100644 --- a/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java @@ -2,15 +2,17 @@ package chess.view.DDDrender.opengl; import static org.lwjgl.opengl.GL11.GL_FLOAT; +import java.io.Closeable; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.lwjgl.opengl.GL30; -public class VertexBuffer { - private int id; - private int dataStride; - List vertexAttribs; +public class VertexBuffer implements Closeable { + private final int id; + private final int dataStride; + private final List vertexAttribs; public VertexBuffer(float[] data, int stride) { this.id = GL30.glGenBuffers(); @@ -28,10 +30,6 @@ public class VertexBuffer { Unbind(); } - public void Destroy() { - GL30.glDeleteBuffers(id); - } - public void Bind() { GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.id); } @@ -52,4 +50,9 @@ public class VertexBuffer { this.dataStride * 4, vertexAttribPointer.offset()); } } + + @Override + public void close() throws IOException { + GL30.glDeleteBuffers(id); + } } diff --git a/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java b/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java index 1cf3214..a9d142a 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java +++ b/app/src/main/java/chess/view/DDDrender/shader/ShaderProgram.java @@ -1,5 +1,7 @@ package chess.view.DDDrender.shader; +import java.io.Closeable; +import java.io.IOException; import java.nio.FloatBuffer; import java.nio.IntBuffer; @@ -9,7 +11,7 @@ import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL30; import org.lwjgl.system.MemoryStack; -public abstract class ShaderProgram { +public abstract class ShaderProgram implements Closeable { private int programId; private int vertexShaderId; private int fragmentShaderId; @@ -91,4 +93,11 @@ public abstract class ShaderProgram { GL30.glUniformMatrix4fv(location, false, buffer); } } + + @Override + public void close() throws IOException { + GL30.glDeleteShader(vertexShaderId); + GL30.glDeleteShader(fragmentShaderId); + GL30.glDeleteProgram(programId); + } } diff --git a/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java b/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java index 9a20295..a89d502 100644 --- a/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java +++ b/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java @@ -1,5 +1,7 @@ package chess.view.DDDrender.world; +import java.io.IOException; + import org.joml.Vector3f; import chess.model.Coordinate; @@ -38,4 +40,9 @@ public class BoardEntity extends Entity { renderer.RenderVao(renderer.getBoardShader(), vao); } + @Override + public void close() throws IOException { + vao.close(); + } + } diff --git a/app/src/main/java/chess/view/DDDrender/world/Entity.java b/app/src/main/java/chess/view/DDDrender/world/Entity.java index 47f0b69..d686902 100644 --- a/app/src/main/java/chess/view/DDDrender/world/Entity.java +++ b/app/src/main/java/chess/view/DDDrender/world/Entity.java @@ -1,9 +1,11 @@ package chess.view.DDDrender.world; +import java.io.Closeable; + import chess.view.DDDrender.Renderer; -public abstract class Entity { +public abstract class Entity implements Closeable{ public abstract void render(Renderer renderer); - + } diff --git a/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java b/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java index 4304eea..56b7767 100644 --- a/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java +++ b/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java @@ -1,5 +1,7 @@ package chess.view.DDDrender.world; +import java.io.IOException; + import org.joml.Vector3f; import chess.view.DDDrender.DDDModel; @@ -36,4 +38,9 @@ public class ModelEntity extends Entity { this.rotation = rotation; } + @Override + public void close() throws IOException { + this.model.close(); + } + } diff --git a/app/src/main/java/chess/view/DDDrender/world/World.java b/app/src/main/java/chess/view/DDDrender/world/World.java index bcfa75e..b1cf530 100644 --- a/app/src/main/java/chess/view/DDDrender/world/World.java +++ b/app/src/main/java/chess/view/DDDrender/world/World.java @@ -1,5 +1,7 @@ package chess.view.DDDrender.world; +import java.io.Closeable; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -7,7 +9,7 @@ import chess.model.Coordinate; import chess.model.Move; import chess.model.Piece; -public class World { +public class World implements Closeable{ /** Renderable entity list */ private final List entites; @@ -55,4 +57,11 @@ public class World { return entites; } + @Override + public void close() throws IOException { + for (Entity entity : entites) { + entity.close(); + } + } + } From ffd77d97778487729721a31d4d1d2b1eb21470c0 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Wed, 30 Apr 2025 19:16:27 +0200 Subject: [PATCH 27/40] world: add ejectPiece --- app/src/main/java/chess/view/DDDrender/world/World.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/chess/view/DDDrender/world/World.java b/app/src/main/java/chess/view/DDDrender/world/World.java index b1cf530..17db900 100644 --- a/app/src/main/java/chess/view/DDDrender/world/World.java +++ b/app/src/main/java/chess/view/DDDrender/world/World.java @@ -45,6 +45,13 @@ public class World implements Closeable{ setPieceCoords(null, move.getStart()); } + public void ejectPiece(Coordinate coordinate) { + PieceEntity entity = getPiece(coordinate); + assert entity != null; + this.entites.remove(entity); + setPieceCoords(null, coordinate); + } + public PieceEntity getPiece(Coordinate coordinate) { return pieces[coordinate.toIndex()]; } From 83d573778914f123c441e0effc205007f0dca46f Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Tue, 6 May 2025 09:55:48 +0200 Subject: [PATCH 28/40] You spin me right 'round, baby, right 'round --- app/src/main/java/chess/view/DDDrender/DDDView.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 599f05e..297b7ae 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -25,11 +25,13 @@ public class DDDView extends GameAdaptator { private final Window window; private final World world; private BoardEntity boardEntity; + private final Camera camera; public DDDView(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; this.world = new World(); - this.window = new Window(new Renderer(), this.world, new Camera()); + this.camera = new Camera(); + this.window = new Window(new Renderer(), this.world, this.camera); } // Invoked when a cell is clicked @@ -106,11 +108,10 @@ public class DDDView extends GameAdaptator { } public void run() { - // this.window.addRegularTask((delta) -> { - // final float angle = 1f; - // final Camera cam = this.world.getCamera(); - // cam.setRotateAngle(cam.getRotateAngle() + angle * delta); - // }); + this.window.addRegularTask((delta) -> { + final float angle = 1f; + this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta); + }); this.window.run(); // free OpenGL resources From 046f68093794d19e71172a3b7b0be890461279f7 Mon Sep 17 00:00:00 2001 From: Janet-Doe Date: Wed, 14 May 2025 10:10:14 +0200 Subject: [PATCH 29/40] onclick functional --- .../java/chess/view/DDDrender/DDDView.java | 122 ++++++++++++++++-- 1 file changed, 114 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 297b7ae..a4cd4f5 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -1,11 +1,15 @@ package chess.view.DDDrender; import java.io.IOException; +import java.util.List; import org.joml.Vector2f; import org.joml.Vector3f; +import chess.controller.Command; +import chess.controller.Command.CommandResult; import chess.controller.CommandExecutor; +import chess.controller.commands.GetAllowedMovesPieceCommand; import chess.controller.commands.GetPieceAtCommand; import chess.controller.event.GameAdaptator; import chess.model.Color; @@ -26,6 +30,7 @@ public class DDDView extends GameAdaptator { private final World world; private BoardEntity boardEntity; private final Camera camera; + private Coordinate click = null; public DDDView(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; @@ -34,28 +39,127 @@ public class DDDView extends GameAdaptator { this.window = new Window(new Renderer(), this.world, this.camera); } + private void cancelClick(){ + this.click=null; + } + + private CommandResult sendCommand(Command command) { + return this.commandExecutor.executeCommand(command); + } + // Invoked when a cell is clicked private void onCellClick(Coordinate coordinate) { - System.out.println("Cell clicked : " + coordinate); + if (this.click == null){ // case: first click + System.out.println("First click on " + coordinate); + previewMoves(coordinate); + this.click = coordinate; + return; + } + // case: second click + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(this.click); + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.contains(coordinate)){ // case: valid attempt at move + System.out.println("Move on " + coordinate); + cancelPreview(click); + //onMove(new Move(click, coordinate)); + cancelClick(); + return; + } + if (!(coordinate == click)) { // case: cancelling previous click + System.out.println("New click on " + coordinate); // cases: invalid move, selecting another piece + cancelPreview(click); + previewMoves(coordinate); + this.click = coordinate; + return; + } + System.out.println("Cancelling click."); + cancelPreview(click); + cancelClick(); + } + + // cas 1 : hover sans click => click = null + // cas 2 : click, attente => click = coo + // cas 3 : click, click sur le même => cancelClick + // cas 4 : click, click sur un autre pion à vérifier => preview nouveau pion, click = new_coo + // cas 5 : click, click sur une position éventuelle valide => move, cancelClick + // cas 6 : click, click sur une case non valide => move invalide, cancelClick + + + + private boolean previewMoves(Coordinate coordinate){ + this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); + Piece p = pieceAt(coordinate); + if (p == null) + return false; + this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) + return false; + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) + return false; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.setCellColor(destCoord, new Vector3f(1, 1, 0)); + } + return true; } // Invoked when a cell is hovered private void onCellEnter(Coordinate coordinate) { - // small test turning a cell red when hovered - this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); - Piece p = pieceAt(coordinate); - if (p == null) - return; - this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); + if (this.click == null){ + // small test turning a cell red when hovered + this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); + Piece p = pieceAt(coordinate); + if (p == null) + return; + this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) + return ; + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) + return ; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.setCellColor(destCoord, new Vector3f(1, 0, 0)); + } + } } // Invoked when a cell is not hovered anymore private void onCellExit(Coordinate coordinate) { + if (this.click == null){ + this.boardEntity.resetCellColor(coordinate); + Piece p = pieceAt(coordinate); + if (p == null) + return; + this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK); + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) + return ; + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) + return ; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.resetCellColor(destCoord); + } + } + } + + private void cancelPreview(Coordinate coordinate){ this.boardEntity.resetCellColor(coordinate); Piece p = pieceAt(coordinate); if (p == null) return; this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK); + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) + return ; + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) + return ; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.resetCellColor(destCoord); + } } private Piece pieceAt(Coordinate pos) { @@ -108,10 +212,12 @@ public class DDDView extends GameAdaptator { } public void run() { + /* this.window.addRegularTask((delta) -> { - final float angle = 1f; + final float angle = 1f; this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta); }); + */ this.window.run(); // free OpenGL resources From ce977c3b48cf945e37c7c93903a96281baa461a3 Mon Sep 17 00:00:00 2001 From: Janet-Doe Date: Wed, 14 May 2025 15:56:08 +0200 Subject: [PATCH 30/40] debug --- app/src/main/java/chess/controller/Command.java | 8 ++++---- .../controller/commands/GetAllowedMovesPieceCommand.java | 4 ++++ app/src/main/java/chess/model/Move.java | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/chess/controller/Command.java b/app/src/main/java/chess/controller/Command.java index 9396eb7..078c2da 100644 --- a/app/src/main/java/chess/controller/Command.java +++ b/app/src/main/java/chess/controller/Command.java @@ -7,14 +7,14 @@ public abstract class Command { public enum CommandResult { /** - * The command was successfull. Should update display and switch player turn. + * The command was successful. Should update display and switch player turn. */ Moved, - /** The command was successfull. Should not update anything */ + /** The command was successful. Should not update anything */ NotMoved, - /** The command was successfull. Should only update display */ + /** The command was successful. Should only update display */ ActionNeeded, - /** The command was not successfull */ + /** The command was not successful */ NotAllowed; } diff --git a/app/src/main/java/chess/controller/commands/GetAllowedMovesPieceCommand.java b/app/src/main/java/chess/controller/commands/GetAllowedMovesPieceCommand.java index a8054ed..d2c2e0f 100644 --- a/app/src/main/java/chess/controller/commands/GetAllowedMovesPieceCommand.java +++ b/app/src/main/java/chess/controller/commands/GetAllowedMovesPieceCommand.java @@ -38,4 +38,8 @@ public class GetAllowedMovesPieceCommand extends Command { public List getDestinations() { return destinations; } + + public String toString(){ + return "From position " + start + " to " + destinations; + } } diff --git a/app/src/main/java/chess/model/Move.java b/app/src/main/java/chess/model/Move.java index 14edf02..8039964 100644 --- a/app/src/main/java/chess/model/Move.java +++ b/app/src/main/java/chess/model/Move.java @@ -60,4 +60,8 @@ public class Move { return false; } + public String toString(){ + return "Moved from " + getStart() + " to " + getFinish(); + } + } From 264391ba81a285d0ff687c0266ee720382ccd953 Mon Sep 17 00:00:00 2001 From: Janet-Doe Date: Wed, 14 May 2025 15:57:01 +0200 Subject: [PATCH 31/40] functional move in 3D view --- .../java/chess/view/DDDrender/DDDView.java | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index a4cd4f5..3588f62 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -3,6 +3,8 @@ package chess.view.DDDrender; import java.io.IOException; import java.util.List; +import chess.controller.commands.MoveCommand; +import chess.controller.event.GameListener; import org.joml.Vector2f; import org.joml.Vector3f; @@ -20,7 +22,7 @@ import chess.view.DDDrender.world.BoardEntity; import chess.view.DDDrender.world.PieceEntity; import chess.view.DDDrender.world.World; -public class DDDView extends GameAdaptator { +public class DDDView extends GameAdaptator implements GameListener { private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); @@ -42,7 +44,7 @@ public class DDDView extends GameAdaptator { private void cancelClick(){ this.click=null; } - + private void setClick(Coordinate coordinate) {this.click=coordinate;} private CommandResult sendCommand(Command command) { return this.commandExecutor.executeCommand(command); } @@ -50,58 +52,58 @@ public class DDDView extends GameAdaptator { // Invoked when a cell is clicked private void onCellClick(Coordinate coordinate) { if (this.click == null){ // case: first click - System.out.println("First click on " + coordinate); + setClick(coordinate); previewMoves(coordinate); - this.click = coordinate; + System.out.println("First click on " + coordinate); return; } // case: second click GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(this.click); - List allowedMoves = movesCommand.getDestinations(); - if (allowedMoves.contains(coordinate)){ // case: valid attempt at move - System.out.println("Move on " + coordinate); - cancelPreview(click); - //onMove(new Move(click, coordinate)); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move + System.out.println("Nothing to do here."); cancelClick(); return; } - if (!(coordinate == click)) { // case: cancelling previous click - System.out.println("New click on " + coordinate); // cases: invalid move, selecting another piece - cancelPreview(click); - previewMoves(coordinate); - this.click = coordinate; + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) { // case: no movement possible for piece + System.out.println("This piece cannot be moved at the moment."); + cancelClick(); return; } - System.out.println("Cancelling click."); - cancelPreview(click); + if (allowedMoves.contains(coordinate)){ // case: valid attempt to move + System.out.println("Move on " + coordinate); + cancelPreview(this.click); + Command.CommandResult result = sendCommand(new MoveCommand(new Move(click, coordinate))); + cancelClick(); + return; + } + if (!(coordinate == this.click)) { + System.out.println("New click on " + coordinate); // cases: invalid move, selecting another piece + cancelPreview(this.click); + previewMoves(coordinate); + setClick(coordinate); + return; + } + System.out.println("Cancelling click."); // case: cancelling previous click + cancelPreview(this.click); cancelClick(); } - // cas 1 : hover sans click => click = null - // cas 2 : click, attente => click = coo - // cas 3 : click, click sur le même => cancelClick - // cas 4 : click, click sur un autre pion à vérifier => preview nouveau pion, click = new_coo - // cas 5 : click, click sur une position éventuelle valide => move, cancelClick - // cas 6 : click, click sur une case non valide => move invalide, cancelClick - - - - private boolean previewMoves(Coordinate coordinate){ + private void previewMoves(Coordinate coordinate){ this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); Piece p = pieceAt(coordinate); if (p == null) - return false; + return; this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); if (sendCommand(movesCommand) == CommandResult.NotAllowed) - return false; + return; List allowedMoves = movesCommand.getDestinations(); if (allowedMoves.isEmpty()) - return false; + return; for (Coordinate destCoord : allowedMoves) { this.boardEntity.setCellColor(destCoord, new Vector3f(1, 1, 0)); } - return true; } // Invoked when a cell is hovered @@ -207,7 +209,12 @@ public class DDDView extends GameAdaptator { @Override public void onMove(Move move) { - // update world internal positions + if(move.getDeadPieceCoords() != null) { + this.world.ejectPiece(move.getDeadPieceCoords()); + } + Vector2f pieceBoardPos = DDDPlacement.coordinatesToVector(move.getFinish()); + Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); + this.world.getPiece(move.getStart()).setPosition(pieceWorldPos); this.world.movePiece(this.world.getPiece(move.getStart()), move); } From 39c7ebefe6d0b8e2e794820ab8a0c257f543a133 Mon Sep 17 00:00:00 2001 From: Janet-Doe Date: Wed, 14 May 2025 16:46:12 +0200 Subject: [PATCH 32/40] fix coloring bug on double click --- .../java/chess/view/DDDrender/DDDView.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 3588f62..36f31be 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -52,6 +52,16 @@ public class DDDView extends GameAdaptator implements GameListener { // Invoked when a cell is clicked private void onCellClick(Coordinate coordinate) { if (this.click == null){ // case: first click + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move + System.out.println("Nothing to do here."); + return; + } + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) { // case: no movement possible for piece + System.out.println("This piece cannot be moved at the moment."); + return; + } setClick(coordinate); previewMoves(coordinate); System.out.println("First click on " + coordinate); @@ -60,12 +70,14 @@ public class DDDView extends GameAdaptator implements GameListener { // case: second click GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(this.click); if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move + cancelPreview(this.click); System.out.println("Nothing to do here."); cancelClick(); return; } List allowedMoves = movesCommand.getDestinations(); if (allowedMoves.isEmpty()) { // case: no movement possible for piece + cancelPreview(this.click); System.out.println("This piece cannot be moved at the moment."); cancelClick(); return; @@ -85,19 +97,20 @@ public class DDDView extends GameAdaptator implements GameListener { return; } System.out.println("Cancelling click."); // case: cancelling previous click - cancelPreview(this.click); cancelClick(); } private void previewMoves(Coordinate coordinate){ - this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); Piece p = pieceAt(coordinate); - if (p == null) + if (p == null) { return; - this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); + } GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); - if (sendCommand(movesCommand) == CommandResult.NotAllowed) + if (sendCommand(movesCommand) == CommandResult.NotAllowed) { return; + } + this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); + this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); List allowedMoves = movesCommand.getDestinations(); if (allowedMoves.isEmpty()) return; From dba8e8fb1e627c78972aa74ca355a041293dc097 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Fri, 16 May 2025 10:41:33 +0200 Subject: [PATCH 33/40] feat: add imgui --- .gitignore | 5 +- app/build.gradle | 8 +- .../java/chess/view/DDDrender/Window.java | 88 ++++++++++++++---- app/src/main/resources/fonts/comic.ttf | Bin 0 -> 128244 bytes 4 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 app/src/main/resources/fonts/comic.ttf diff --git a/.gitignore b/.gitignore index 0056fd7..684fed0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ build app/bin -.vscode \ No newline at end of file +.vscode + +*.wav +imgui.ini \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index dd7732b..1234bb8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,10 @@ repositories { mavenCentral() } +def os = "linux"; def lwjgl_version = "3.3.6" -def lwjgl_natives = "natives-linux" +def lwjgl_natives = "natives-$os" +def imgui_version = "1.87.0" dependencies { // Use JUnit Jupiter for testing. @@ -33,6 +35,10 @@ dependencies { implementation "org.lwjgl:lwjgl-opengl::$lwjgl_natives" implementation "org.lwjgl:lwjgl-glfw::$lwjgl_natives" implementation "org.lwjgl:lwjgl-assimp::$lwjgl_natives" + + implementation "io.github.spair:imgui-java-binding:$imgui_version" + implementation "io.github.spair:imgui-java-natives-$os:$imgui_version" + implementation "io.github.spair:imgui-java-app:$imgui_version" } application { diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 88e610e..ebbdb76 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -7,9 +7,14 @@ import org.lwjgl.opengl.*; import org.lwjgl.system.*; import chess.model.Coordinate; +import chess.view.AssetManager; import chess.view.DDDrender.world.Entity; import chess.view.DDDrender.world.World; import common.Signal1; +import imgui.ImFontConfig; +import imgui.ImGui; +import imgui.gl3.ImGuiImplGl3; +import imgui.glfw.ImGuiImplGlfw; import java.io.Closeable; import java.io.IOException; @@ -26,11 +31,14 @@ import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.system.MemoryStack.*; import static org.lwjgl.system.MemoryUtil.*; -public class Window implements Closeable{ +public class Window implements Closeable { // The window handle private long window; + private final ImGuiImplGl3 implGl3 = new ImGuiImplGl3(); + private final ImGuiImplGlfw implGlfw = new ImGuiImplGlfw(); + private Renderer renderer; private final Camera cam; private final World world; @@ -77,6 +85,37 @@ public class Window implements Closeable{ // Terminate GLFW and free the error callback glfwTerminate(); glfwSetErrorCallback(null).free(); + + implGl3.shutdown(); + implGlfw.shutdown(); + + ImGui.destroyContext(); + } + + private void setCallbacks() { + glfwSetMouseButtonCallback(this.window, (window, button, action, mods) -> { + if (button == GLFW_MOUSE_BUTTON_1 && action == GLFW_PRESS) { + if (this.lastCell.isValid()) + this.OnCellClick.emit(this.lastCell); + } + }); + } + + private void initImGui() { + ImGui.setCurrentContext(ImGui.createContext()); + + implGl3.init("#version 330"); + implGlfw.init(window, true); + + ImFontConfig config = new ImFontConfig(); + config.setFontDataOwnedByAtlas(false); + try { + ImGui.getIO().getFonts().addFontFromMemoryTTF(AssetManager.getResource("fonts/comic.ttf").readAllBytes(), + 50.0f, config); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } private void init() { @@ -114,10 +153,21 @@ public class Window implements Closeable{ window, (vidmode.width() - pWidth.get(0)) / 2, (vidmode.height() - pHeight.get(0)) / 2); + + glfwSetWindowSize(window, vidmode.width(), vidmode.height()); } // the stack frame is popped automatically // Make the OpenGL context current glfwMakeContextCurrent(window); + + GL.createCapabilities(); + + setCallbacks(); + + initImGui(); + + renderer.Init(); + // Enable v-sync glfwSwapInterval(1); @@ -152,37 +202,31 @@ public class Window implements Closeable{ double x[] = new double[1]; double y[] = new double[1]; glfwGetCursorPos(this.window, x, y); - Vector2f cursorPos = this.cam.getCursorWorldFloorPos(new Vector2f((float) x[0], (float) y[0]), windowWidth, windowHeight); + Vector2f cursorPos = this.cam.getCursorWorldFloorPos(new Vector2f((float) x[0], (float) y[0]), windowWidth, + windowHeight); Coordinate selectedCell = DDDPlacement.vectorToCoordinates(cursorPos); if (this.lastCell == null) { this.lastCell = selectedCell; if (selectedCell.isValid()) this.OnCellEnter.emit(selectedCell); - } - else if (!this.lastCell.equals(selectedCell)) { + } else if (!this.lastCell.equals(selectedCell)) { if (this.lastCell.isValid()) this.OnCellExit.emit(this.lastCell); if (selectedCell.isValid()) this.OnCellEnter.emit(selectedCell); this.lastCell = selectedCell; } - glfwSetMouseButtonCallback(this.window, (window, button, action, mods) -> { - if (button == GLFW_MOUSE_BUTTON_1 && action == GLFW_PRESS) { - if (this.lastCell.isValid()) - this.OnCellClick.emit(this.lastCell); - } - }); + } + + private void newFrame() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer + + implGlfw.newFrame(); + implGl3.newFrame(); + ImGui.newFrame(); } private void loop() { - // This line is critical for LWJGL's interoperation with GLFW's - // OpenGL context, or any context that is managed externally. - // LWJGL detects the context that is current in the current thread, - // creates the GLCapabilities instance and makes the OpenGL - // bindings available for use. - GL.createCapabilities(); - - renderer.Init(); // Set the clear color glClearColor(0.4f, 0.4f, 0.6f, 1.0f); @@ -204,11 +248,17 @@ public class Window implements Closeable{ // Run the rendering loop until the user has attempted to close // the window or has pressed the ESCAPE key. while (!glfwWindowShouldClose(window)) { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer + + newFrame(); double currentTime = glfwGetTime(); float deltaTime = (float) (currentTime - lastTime); render(deltaTime, (float) width[0] / (float) height[0]); + + // ImGui.showDemoWindow(); + ImGui.render(); + implGl3.renderDrawData(ImGui.getDrawData()); + lastTime = glfwGetTime(); glfwSwapBuffers(window); // swap the color buffers diff --git a/app/src/main/resources/fonts/comic.ttf b/app/src/main/resources/fonts/comic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..da55369bd89f78c1f77e47d1267367f120bde7ae GIT binary patch literal 128244 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 z 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 Date: Fri, 16 May 2025 10:44:02 +0200 Subject: [PATCH 34/40] fix null error --- app/src/main/java/chess/view/DDDrender/world/World.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/world/World.java b/app/src/main/java/chess/view/DDDrender/world/World.java index 17db900..bd67f94 100644 --- a/app/src/main/java/chess/view/DDDrender/world/World.java +++ b/app/src/main/java/chess/view/DDDrender/world/World.java @@ -47,8 +47,8 @@ public class World implements Closeable{ public void ejectPiece(Coordinate coordinate) { PieceEntity entity = getPiece(coordinate); - assert entity != null; - this.entites.remove(entity); + if (entity != null) + this.entites.remove(entity); setPieceCoords(null, coordinate); } From 94870e65c2840de2d6af0d77290c32d857003f5e Mon Sep 17 00:00:00 2001 From: l Date: Fri, 16 May 2025 13:35:38 +0000 Subject: [PATCH 35/40] imgui (#10) Co-authored-by: Persson-dev Reviewed-on: https://git.ale-pri.com/Crabs/3DChess/pulls/10 --- .../java/chess/view/DDDrender/Camera.java | 2 +- .../java/chess/view/DDDrender/DDDView.java | 10 ++-- .../java/chess/view/DDDrender/Renderer.java | 39 ++++++++++++++-- .../java/chess/view/DDDrender/Window.java | 46 +++++++++++-------- .../view/DDDrender/shader/BoardShader.java | 2 +- .../view/DDDrender/shader/PieceShader.java | 2 +- .../view/DDDrender/world/BoardEntity.java | 6 +++ .../chess/view/DDDrender/world/Entity.java | 4 ++ .../view/DDDrender/world/ModelEntity.java | 17 ++++++- 9 files changed, 95 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/Camera.java b/app/src/main/java/chess/view/DDDrender/Camera.java index 03f5375..53a57db 100644 --- a/app/src/main/java/chess/view/DDDrender/Camera.java +++ b/app/src/main/java/chess/view/DDDrender/Camera.java @@ -10,7 +10,7 @@ public class Camera { public static final float zNear = 0.01f; public static final float zFar = 1000.0f; - private static final Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f); + private static final Vector3f up = new Vector3f(0.0f, -1.0f, 0.0f); private static final Vector3f center = new Vector3f(0.0f, 0.0f, 0.0f); private final float distance = 1.5f; diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 36f31be..0fd0a68 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -232,12 +232,10 @@ public class DDDView extends GameAdaptator implements GameListener { } public void run() { - /* - this.window.addRegularTask((delta) -> { - final float angle = 1f; - this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta); - }); - */ + // this.window.addRegularTask((delta) -> { + // final float angle = 1f; + // this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta); + // }); this.window.run(); // free OpenGL resources diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index c8781b7..eb66ad0 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -1,9 +1,16 @@ package chess.view.DDDrender; +import static org.lwjgl.opengl.GL11.GL_DEPTH_COMPONENT; +import static org.lwjgl.opengl.GL11.GL_RGB; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL11.glBindTexture; +import static org.lwjgl.opengl.GL11.glTexImage2D; import java.io.Closeable; import java.io.IOException; +import java.nio.IntBuffer; import org.joml.Matrix4f; import org.joml.Vector3f; @@ -14,7 +21,7 @@ import chess.view.DDDrender.shader.BoardShader; import chess.view.DDDrender.shader.PieceShader; import chess.view.DDDrender.shader.ShaderProgram; -public class Renderer implements Closeable{ +public class Renderer implements Closeable { private BoardShader boardShader; private PieceShader pieceShader; @@ -26,6 +33,32 @@ public class Renderer implements Closeable{ public void Init() { boardShader.LoadShader(); pieceShader.LoadShader(); + + // The frame buffer + int fbo = GL30.glGenFramebuffers(); + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo); + + // The texture + int renderTexture = GL30.glGenTextures(); + glBindTexture(GL_TEXTURE_2D, renderTexture); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 800, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + + GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + + // The depth buffer + int depthBuffer = GL30.glGenRenderbuffers(); + GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthBuffer); + GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 800, 800); + GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, + depthBuffer); + + // Set "renderedTexture" as our colour attachement #0 + GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTexture, 0); + // Set the list of draw buffers. + int[] drawBuffers = { GL30.GL_COLOR_ATTACHMENT0 }; + GL30.glDrawBuffers(drawBuffers); } public void Update(Camera cam) { @@ -35,10 +68,10 @@ public class Renderer implements Closeable{ this.pieceShader.SetCamMatrix(cam); } - public void Render(DDDModel model, Vector3f color, Vector3f position, float rotation) { + public void Render(DDDModel model, Vector3f color, Matrix4f transform) { this.pieceShader.Start(); this.pieceShader.setModelColor(color); - this.pieceShader.setModelTransform(new Matrix4f().translate(position).rotate(rotation, new Vector3f(0, 1, 0))); + this.pieceShader.setModelTransform(transform); Render(model); } diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index ebbdb76..839042c 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -13,6 +13,7 @@ import chess.view.DDDrender.world.World; import common.Signal1; import imgui.ImFontConfig; import imgui.ImGui; +import imgui.ImVec2; import imgui.gl3.ImGuiImplGl3; import imgui.glfw.ImGuiImplGlfw; @@ -92,15 +93,6 @@ public class Window implements Closeable { ImGui.destroyContext(); } - private void setCallbacks() { - glfwSetMouseButtonCallback(this.window, (window, button, action, mods) -> { - if (button == GLFW_MOUSE_BUTTON_1 && action == GLFW_PRESS) { - if (this.lastCell.isValid()) - this.OnCellClick.emit(this.lastCell); - } - }); - } - private void initImGui() { ImGui.setCurrentContext(ImGui.createContext()); @@ -162,8 +154,6 @@ public class Window implements Closeable { GL.createCapabilities(); - setCallbacks(); - initImGui(); renderer.Init(); @@ -176,9 +166,13 @@ public class Window implements Closeable { } private void render(float delta, float aspectRatio) { - cam.setAspectRatio(aspectRatio); + cam.setAspectRatio(1.0f); + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 1); + glViewport(0, 0, 800, 800); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer renderer.Update(cam); renderWorld(); + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0); } private void renderWorld() { @@ -198,11 +192,8 @@ public class Window implements Closeable { } } - private void checkCursor(int windowWidth, int windowHeight) { - double x[] = new double[1]; - double y[] = new double[1]; - glfwGetCursorPos(this.window, x, y); - Vector2f cursorPos = this.cam.getCursorWorldFloorPos(new Vector2f((float) x[0], (float) y[0]), windowWidth, + private void checkCursor(float cursorPosX, float cursorPosY, int windowWidth, int windowHeight) { + Vector2f cursorPos = this.cam.getCursorWorldFloorPos(new Vector2f(cursorPosX, cursorPosY), windowWidth, windowHeight); Coordinate selectedCell = DDDPlacement.vectorToCoordinates(cursorPos); if (this.lastCell == null) { @@ -216,6 +207,11 @@ public class Window implements Closeable { this.OnCellEnter.emit(selectedCell); this.lastCell = selectedCell; } + + if (ImGui.getIO().getMouseClicked(0)) { + if (this.lastCell != null && this.lastCell.isValid()) + this.OnCellClick.emit(this.lastCell); + } } private void newFrame() { @@ -226,6 +222,18 @@ public class Window implements Closeable { ImGui.newFrame(); } + private void renderWindow() { + ImGui.showDemoWindow(); + + ImGui.begin("Hello"); + ImGui.text("FPS : " + ImGui.getIO().getFramerate()); + ImVec2 mousePos = ImGui.getIO().getMousePos(); + ImVec2 framePos = ImGui.getCursorScreenPos(); + checkCursor(mousePos.x - framePos.x, 800 - (mousePos.y - framePos.y), 800, 800); + ImGui.image(1, new ImVec2(800, 800)); + ImGui.end(); + } + private void loop() { // Set the clear color @@ -255,7 +263,7 @@ public class Window implements Closeable { float deltaTime = (float) (currentTime - lastTime); render(deltaTime, (float) width[0] / (float) height[0]); - // ImGui.showDemoWindow(); + renderWindow(); ImGui.render(); implGl3.renderDrawData(ImGui.getDrawData()); @@ -267,8 +275,6 @@ public class Window implements Closeable { // invoked during this call. glfwPollEvents(); - checkCursor(width[0], height[0]); - executeTasks(deltaTime); glfwGetWindowSize(window, width, height); diff --git a/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java index 4c676f5..b0f0033 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java @@ -46,7 +46,7 @@ public class BoardShader extends ShaderProgram { in vec3 toCameraVector; in vec3 surfaceNormal; - out vec4 out_color; + layout(location = 0) out vec4 out_color; void main(void){ const float shineDamper = 10.0; diff --git a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java index 48bee93..754c60a 100644 --- a/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java +++ b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java @@ -48,7 +48,7 @@ public class PieceShader extends ShaderProgram { uniform vec3 modelColor; - out vec4 out_color; + layout(location = 0) out vec4 out_color; void main(void){ const float shineDamper = 10.0; diff --git a/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java b/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java index a89d502..031f84e 100644 --- a/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java +++ b/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java @@ -2,6 +2,7 @@ package chess.view.DDDrender.world; import java.io.IOException; +import org.joml.Matrix4f; import org.joml.Vector3f; import chess.model.Coordinate; @@ -45,4 +46,9 @@ public class BoardEntity extends Entity { vao.close(); } + @Override + public Matrix4f getTransform() { + return null; + } + } diff --git a/app/src/main/java/chess/view/DDDrender/world/Entity.java b/app/src/main/java/chess/view/DDDrender/world/Entity.java index d686902..dcfcfab 100644 --- a/app/src/main/java/chess/view/DDDrender/world/Entity.java +++ b/app/src/main/java/chess/view/DDDrender/world/Entity.java @@ -2,10 +2,14 @@ package chess.view.DDDrender.world; import java.io.Closeable; +import org.joml.Matrix4f; + import chess.view.DDDrender.Renderer; public abstract class Entity implements Closeable{ public abstract void render(Renderer renderer); + public abstract Matrix4f getTransform(); + } diff --git a/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java b/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java index 56b7767..2a79cd2 100644 --- a/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java +++ b/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java @@ -2,6 +2,7 @@ package chess.view.DDDrender.world; import java.io.IOException; +import org.joml.Matrix4f; import org.joml.Vector3f; import chess.view.DDDrender.DDDModel; @@ -14,20 +15,24 @@ public class ModelEntity extends Entity { protected float rotation; protected DDDModel model; + private Matrix4f transform; + public ModelEntity(DDDModel model, Vector3f position, Vector3f color, float rotation) { this.position = position; this.color = color; this.rotation = rotation; this.model = model; + updateTransform(); } @Override public void render(Renderer renderer) { - renderer.Render(model, color, position, rotation); + renderer.Render(model, color, transform); } public void setPosition(Vector3f position) { this.position = position; + updateTransform(); } public void setColor(Vector3f color) { @@ -36,6 +41,7 @@ public class ModelEntity extends Entity { public void setRotation(float rotation) { this.rotation = rotation; + updateTransform(); } @Override @@ -43,4 +49,13 @@ public class ModelEntity extends Entity { this.model.close(); } + private void updateTransform() { + this.transform = new Matrix4f().translate(this.position).rotate(this.rotation, new Vector3f(0, 1, 0)); + } + + @Override + public Matrix4f getTransform() { + return transform; + } + } From 6cec5d9e31f40f3c225d79dd7bef681c5a7c6198 Mon Sep 17 00:00:00 2001 From: "fl.du.pr Grens" Date: Fri, 16 May 2025 16:20:48 +0200 Subject: [PATCH 36/40] feat: spinning turn --- .../java/chess/view/DDDrender/DDDView.java | 39 +++++++++++++++++-- .../java/chess/view/DDDrender/Window.java | 11 +++++- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 0fd0a68..607f804 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -2,9 +2,11 @@ package chess.view.DDDrender; import java.io.IOException; import java.util.List; +import java.util.function.Consumer; import chess.controller.commands.MoveCommand; import chess.controller.event.GameListener; +import imgui.ImGui; import org.joml.Vector2f; import org.joml.Vector3f; @@ -34,6 +36,9 @@ public class DDDView extends GameAdaptator implements GameListener { private final Camera camera; private Coordinate click = null; + private static final float animationTime = 1.5f; // in seconds + private static final int animationTurns = 1; + public DDDView(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; this.world = new World(); @@ -195,9 +200,19 @@ public class DDDView extends GameAdaptator implements GameListener { this.window.OnCellClick.connect(this::onCellClick); this.window.OnCellEnter.connect(this::onCellEnter); this.window.OnCellExit.connect(this::onCellExit); + this.window.OnImGuiTopRender.connect(this::onHeaderRender); + this.window.OnImGuiBottomRender.connect(this::onFooterRender); }); } + private void onHeaderRender() { + ImGui.text("FPS : " + ImGui.getIO().getFramerate()); + } + + private void onFooterRender() { + ImGui.button("Coucou"); + } + private void initBoard() throws IOException { for (int i = 0; i < Coordinate.VALUE_MAX; i++) { for (int j = 0; j < Coordinate.VALUE_MAX; j++) { @@ -229,13 +244,29 @@ public class DDDView extends GameAdaptator implements GameListener { Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); this.world.getPiece(move.getStart()).setPosition(pieceWorldPos); this.world.movePiece(this.world.getPiece(move.getStart()), move); + cameraRotate(); + } + + private void cameraTick(float delta) { + int oddAnimationTurn = (2 * (animationTurns-1)) + 1; + final float angle = (float) Math.PI; + this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta * oddAnimationTurn / animationTime); + } + + public void cameraRotate() { + float end = this.camera.getRotateAngle() + (float) Math.PI; + Consumer rotationConsumer = this::cameraTick; + this.window.addRegularTask(rotationConsumer); + try { + Thread.sleep((long) (animationTime * 1000)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + this.window.removeRegularTask(rotationConsumer); + this.camera.setRotateAngle(end); } public void run() { - // this.window.addRegularTask((delta) -> { - // final float angle = 1f; - // this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta); - // }); this.window.run(); // free OpenGL resources diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 839042c..4e92982 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -1,5 +1,6 @@ package chess.view.DDDrender; +import common.Signal0; import org.joml.Vector2f; import org.lwjgl.*; import org.lwjgl.glfw.*; @@ -53,6 +54,9 @@ public class Window implements Closeable { public final Signal1 OnCellEnter = new Signal1<>(); public final Signal1 OnCellExit = new Signal1<>(); + public final Signal0 OnImGuiTopRender = new Signal0(); + public final Signal0 OnImGuiBottomRender = new Signal0(); + public Window(Renderer renderer, World world, Camera camera) { this.renderer = new Renderer(); this.cam = camera; @@ -65,6 +69,8 @@ public class Window implements Closeable { this.regularTasks.add(task); } + public void removeRegularTask(Consumer task) {this.regularTasks.remove(task);} + public synchronized void scheduleTask(Runnable runnable) { this.tasks.add(runnable); } @@ -225,12 +231,13 @@ public class Window implements Closeable { private void renderWindow() { ImGui.showDemoWindow(); - ImGui.begin("Hello"); - ImGui.text("FPS : " + ImGui.getIO().getFramerate()); + ImGui.begin("Main Window"); + this.OnImGuiTopRender.emit(); ImVec2 mousePos = ImGui.getIO().getMousePos(); ImVec2 framePos = ImGui.getCursorScreenPos(); checkCursor(mousePos.x - framePos.x, 800 - (mousePos.y - framePos.y), 800, 800); ImGui.image(1, new ImVec2(800, 800)); + this.OnImGuiBottomRender.emit(); ImGui.end(); } From 459e45802836366ea60cbe88ec9d7434679a85d2 Mon Sep 17 00:00:00 2001 From: "fl.du.pr Grens" Date: Fri, 16 May 2025 17:53:01 +0200 Subject: [PATCH 37/40] feat: popups --- .../java/chess/view/DDDrender/DDDView.java | 593 +++++++++++------- 1 file changed, 366 insertions(+), 227 deletions(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 607f804..8a80341 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -3,18 +3,18 @@ package chess.view.DDDrender; import java.io.IOException; import java.util.List; import java.util.function.Consumer; +import java.util.function.Function; -import chess.controller.commands.MoveCommand; +import chess.controller.commands.*; import chess.controller.event.GameListener; import imgui.ImGui; +import imgui.type.ImBoolean; import org.joml.Vector2f; import org.joml.Vector3f; import chess.controller.Command; import chess.controller.Command.CommandResult; import chess.controller.CommandExecutor; -import chess.controller.commands.GetAllowedMovesPieceCommand; -import chess.controller.commands.GetPieceAtCommand; import chess.controller.event.GameAdaptator; import chess.model.Color; import chess.model.Coordinate; @@ -26,256 +26,395 @@ import chess.view.DDDrender.world.World; public class DDDView extends GameAdaptator implements GameListener { - private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); - private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); + private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); + private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); - private final CommandExecutor commandExecutor; - private final Window window; - private final World world; - private BoardEntity boardEntity; - private final Camera camera; - private Coordinate click = null; + private final CommandExecutor commandExecutor; + private final Window window; + private final World world; + private BoardEntity boardEntity; + private final Camera camera; + private Coordinate click = null; - private static final float animationTime = 1.5f; // in seconds - private static final int animationTurns = 1; + private static final float animationTime = 1.5f; // in seconds + private static final int animationTurns = 1; - public DDDView(CommandExecutor commandExecutor) { - this.commandExecutor = commandExecutor; - this.world = new World(); - this.camera = new Camera(); - this.window = new Window(new Renderer(), this.world, this.camera); - } + private float moveProgress = 0.0f; - private void cancelClick(){ - this.click=null; - } - private void setClick(Coordinate coordinate) {this.click=coordinate;} - private CommandResult sendCommand(Command command) { - return this.commandExecutor.executeCommand(command); - } + private String waitingPopup = null; + private final ImBoolean popupOpened = new ImBoolean(false); - // Invoked when a cell is clicked - private void onCellClick(Coordinate coordinate) { - if (this.click == null){ // case: first click - GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); - if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move - System.out.println("Nothing to do here."); - return; - } - List allowedMoves = movesCommand.getDestinations(); - if (allowedMoves.isEmpty()) { // case: no movement possible for piece - System.out.println("This piece cannot be moved at the moment."); - return; - } - setClick(coordinate); - previewMoves(coordinate); - System.out.println("First click on " + coordinate); - return; - } - // case: second click - GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(this.click); - if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move - cancelPreview(this.click); - System.out.println("Nothing to do here."); - cancelClick(); - return; - } - List allowedMoves = movesCommand.getDestinations(); - if (allowedMoves.isEmpty()) { // case: no movement possible for piece - cancelPreview(this.click); - System.out.println("This piece cannot be moved at the moment."); - cancelClick(); - return; - } - if (allowedMoves.contains(coordinate)){ // case: valid attempt to move - System.out.println("Move on " + coordinate); - cancelPreview(this.click); - Command.CommandResult result = sendCommand(new MoveCommand(new Move(click, coordinate))); - cancelClick(); - return; - } - if (!(coordinate == this.click)) { - System.out.println("New click on " + coordinate); // cases: invalid move, selecting another piece - cancelPreview(this.click); - previewMoves(coordinate); - setClick(coordinate); - return; - } - System.out.println("Cancelling click."); // case: cancelling previous click - cancelClick(); - } + public DDDView(CommandExecutor commandExecutor) { + this.commandExecutor = commandExecutor; + this.world = new World(); + this.camera = new Camera(); + this.window = new Window(new Renderer(), this.world, this.camera); + } - private void previewMoves(Coordinate coordinate){ - Piece p = pieceAt(coordinate); - if (p == null) { - return; - } - GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); - if (sendCommand(movesCommand) == CommandResult.NotAllowed) { - return; - } - this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); - this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); - List allowedMoves = movesCommand.getDestinations(); - if (allowedMoves.isEmpty()) - return; - for (Coordinate destCoord : allowedMoves) { - this.boardEntity.setCellColor(destCoord, new Vector3f(1, 1, 0)); - } - } + private void cancelClick() { + this.click = null; + } - // Invoked when a cell is hovered - private void onCellEnter(Coordinate coordinate) { - if (this.click == null){ - // small test turning a cell red when hovered - this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); - Piece p = pieceAt(coordinate); - if (p == null) - return; - this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); - GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); - if (sendCommand(movesCommand) == CommandResult.NotAllowed) - return ; - List allowedMoves = movesCommand.getDestinations(); - if (allowedMoves.isEmpty()) - return ; - for (Coordinate destCoord : allowedMoves) { - this.boardEntity.setCellColor(destCoord, new Vector3f(1, 0, 0)); - } - } - } + private void setClick(Coordinate coordinate) { + this.click = coordinate; + } - // Invoked when a cell is not hovered anymore - private void onCellExit(Coordinate coordinate) { - if (this.click == null){ - this.boardEntity.resetCellColor(coordinate); - Piece p = pieceAt(coordinate); - if (p == null) - return; - this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK); - GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); - if (sendCommand(movesCommand) == CommandResult.NotAllowed) - return ; - List allowedMoves = movesCommand.getDestinations(); - if (allowedMoves.isEmpty()) - return ; - for (Coordinate destCoord : allowedMoves) { - this.boardEntity.resetCellColor(destCoord); - } - } - } + private CommandResult sendCommand(Command command) { + return this.commandExecutor.executeCommand(command); + } - private void cancelPreview(Coordinate coordinate){ - this.boardEntity.resetCellColor(coordinate); - Piece p = pieceAt(coordinate); - if (p == null) - return; - this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK); - GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); - if (sendCommand(movesCommand) == CommandResult.NotAllowed) - return ; - List allowedMoves = movesCommand.getDestinations(); - if (allowedMoves.isEmpty()) - return ; - for (Coordinate destCoord : allowedMoves) { - this.boardEntity.resetCellColor(destCoord); - } - } + // Invoked when a cell is clicked + private void onCellClick(Coordinate coordinate) { + if (this.click == null) { // case: first click + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move + System.out.println("Nothing to do here."); + return; + } + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) { // case: no movement possible for piece + System.out.println("This piece cannot be moved at the moment."); + return; + } + setClick(coordinate); + previewMoves(coordinate); + System.out.println("First click on " + coordinate); + return; + } + // case: second click + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(this.click); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) { // case: invalid piece to move + cancelPreview(this.click); + System.out.println("Nothing to do here."); + cancelClick(); + return; + } + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) { // case: no movement possible for piece + cancelPreview(this.click); + System.out.println("This piece cannot be moved at the moment."); + cancelClick(); + return; + } + if (allowedMoves.contains(coordinate)) { // case: valid attempt to move + System.out.println("Move on " + coordinate); + cancelPreview(this.click); + Command.CommandResult result = sendCommand(new MoveCommand(new Move(click, coordinate))); + cancelClick(); + return; + } + if (!(coordinate == this.click)) { + System.out.println("New click on " + coordinate); // cases: invalid move, selecting another piece + cancelPreview(this.click); + previewMoves(coordinate); + setClick(coordinate); + return; + } + System.out.println("Cancelling click."); // case: cancelling previous click + cancelClick(); + } - private Piece pieceAt(Coordinate pos) { - GetPieceAtCommand cmd = new GetPieceAtCommand(pos); - this.commandExecutor.executeCommand(cmd); - return cmd.getPiece(); - } + private void previewMoves(Coordinate coordinate) { + Piece p = pieceAt(coordinate); + if (p == null) { + return; + } + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) { + return; + } + this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); + this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) + return; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.setCellColor(destCoord, new Vector3f(1, 1, 0)); + } + } - @Override - public void onGameStart() { - this.window.scheduleTask(() -> { - try { - initBoard(); - } catch (IOException e) { - e.printStackTrace(); - } - // start listening to mouse events - this.window.OnCellClick.connect(this::onCellClick); - this.window.OnCellEnter.connect(this::onCellEnter); - this.window.OnCellExit.connect(this::onCellExit); - this.window.OnImGuiTopRender.connect(this::onHeaderRender); - this.window.OnImGuiBottomRender.connect(this::onFooterRender); - }); - } + // Invoked when a cell is hovered + private void onCellEnter(Coordinate coordinate) { + if (this.click == null) { + // small test turning a cell red when hovered + this.boardEntity.setCellColor(coordinate, new Vector3f(1, 0, 0)); + Piece p = pieceAt(coordinate); + if (p == null) + return; + this.world.getPiece(coordinate).setColor(new Vector3f(1, 0, 0)); + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) + return; + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) + return; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.setCellColor(destCoord, new Vector3f(1, 0, 0)); + } + } + } - private void onHeaderRender() { - ImGui.text("FPS : " + ImGui.getIO().getFramerate()); - } + // Invoked when a cell is not hovered anymore + private void onCellExit(Coordinate coordinate) { + if (this.click == null) { + this.boardEntity.resetCellColor(coordinate); + Piece p = pieceAt(coordinate); + if (p == null) + return; + PieceEntity pEntity = this.world.getPiece(coordinate); + if (pEntity == null) + return; - private void onFooterRender() { - ImGui.button("Coucou"); - } + pEntity.setColor(p.getColor() == Color.White ? WHITE : BLACK); + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) + return; + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) + return; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.resetCellColor(destCoord); + } + } + } - private void initBoard() throws IOException { - for (int i = 0; i < Coordinate.VALUE_MAX; i++) { - for (int j = 0; j < Coordinate.VALUE_MAX; j++) { - Coordinate pos = new Coordinate(i, j); - Piece piece = pieceAt(pos); - if (piece == null) - continue; + private void cancelPreview(Coordinate coordinate) { + this.boardEntity.resetCellColor(coordinate); + Piece p = pieceAt(coordinate); + if (p == null) + return; + this.world.getPiece(coordinate).setColor(p.getColor() == Color.White ? WHITE : BLACK); + GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(coordinate); + if (sendCommand(movesCommand) == CommandResult.NotAllowed) + return; + List allowedMoves = movesCommand.getDestinations(); + if (allowedMoves.isEmpty()) + return; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.resetCellColor(destCoord); + } + } - Vector2f pieceBoardPos = DDDPlacement.coordinatesToVector(pos); - Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); + private Piece pieceAt(Coordinate pos) { + GetPieceAtCommand cmd = new GetPieceAtCommand(pos); + this.commandExecutor.executeCommand(cmd); + return cmd.getPiece(); + } - PieceEntity entity = new PieceEntity(piece, pieceWorldPos, - piece.getColor() == Color.White ? WHITE : BLACK, - piece.getColor() == Color.White ? 0.0f : (float) Math.PI); + @Override + public void onGameStart() { + this.window.scheduleTask(() -> { + try { + initBoard(); + } catch (IOException e) { + e.printStackTrace(); + } + // start listening to mouse events + this.window.OnCellClick.connect(this::onCellClick); + this.window.OnCellEnter.connect(this::onCellEnter); + this.window.OnCellExit.connect(this::onCellExit); + this.window.OnImGuiTopRender.connect(this::onHeaderRender); + this.window.OnImGuiBottomRender.connect(this::onFooterRender); + }); + } - this.world.addPiece(entity, pos); - } - } - this.boardEntity = new BoardEntity(); - this.world.addEntity(this.boardEntity); - } + private void onHeaderRender() { + ImGui.text("FPS : " + ImGui.getIO().getFramerate()); + } - @Override - public void onMove(Move move) { - if(move.getDeadPieceCoords() != null) { - this.world.ejectPiece(move.getDeadPieceCoords()); - } - Vector2f pieceBoardPos = DDDPlacement.coordinatesToVector(move.getFinish()); - Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); - this.world.getPiece(move.getStart()).setPosition(pieceWorldPos); - this.world.movePiece(this.world.getPiece(move.getStart()), move); - cameraRotate(); - } + private void onFooterRender() { + if (ImGui.button("Roque")) { + sendCommand(new CastlingCommand(false)); + } + ImGui.sameLine(); + if (ImGui.button("Grand Roque")) { + sendCommand(new CastlingCommand(true)); + } + ImGui.sameLine(); + if (ImGui.button("Annuler le coup précédent")) { + sendCommand(new UndoCommand()); + } + openPopup(); + renderPopups(); + } - private void cameraTick(float delta) { - int oddAnimationTurn = (2 * (animationTurns-1)) + 1; - final float angle = (float) Math.PI; - this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta * oddAnimationTurn / animationTime); - } + private void openPopup() { + if (waitingPopup != null) { + ImGui.openPopup(waitingPopup); + waitingPopup = null; + } + } - public void cameraRotate() { - float end = this.camera.getRotateAngle() + (float) Math.PI; - Consumer rotationConsumer = this::cameraTick; - this.window.addRegularTask(rotationConsumer); + private void initBoard() throws IOException { + for (int i = 0; i < Coordinate.VALUE_MAX; i++) { + for (int j = 0; j < Coordinate.VALUE_MAX; j++) { + Coordinate pos = new Coordinate(i, j); + Piece piece = pieceAt(pos); + if (piece == null) + continue; + + Vector2f pieceBoardPos = DDDPlacement.coordinatesToVector(pos); + Vector3f pieceWorldPos = new Vector3f(pieceBoardPos.x(), 0, pieceBoardPos.y()); + + PieceEntity entity = new PieceEntity(piece, pieceWorldPos, + piece.getColor() == Color.White ? WHITE : BLACK, + piece.getColor() == Color.White ? 0.0f : (float) Math.PI); + + this.world.addPiece(entity, pos); + } + } + this.boardEntity = new BoardEntity(); + this.world.addEntity(this.boardEntity); + } + + /** + * @param begin begin + * @param middle control point + * @param end end + * @param t between 0 and 1 + * @return the point + */ + private Vector3f bezierCurve(Vector3f begin, Vector3f middle, Vector3f end, float t) { + return begin.mul((1.0f - t) * (1.0f - t)).add(middle.mul(2.0f * t * (1.0f - t))).add(end.mul(t * t)); + } + + private Function lagrangeInterpolation(Vector3f begin, Vector3f middle, Vector3f end) { + return (t) -> { + return t+1.0f; + }; + } + + private void pieceTick(float progress, PieceEntity piece, Move move) { + float height = 1; // how much the piece is raised + Vector2f pieceStartBoard = DDDPlacement.coordinatesToVector(move.getStart()); + Vector2f pieceDestinationBoard = DDDPlacement.coordinatesToVector(move.getFinish()); + Vector3f start = new Vector3f(pieceStartBoard.x(), 0, pieceStartBoard.y()); + Vector3f top = new Vector3f(pieceDestinationBoard.x() - pieceStartBoard.x(), height, pieceDestinationBoard.y() - pieceStartBoard.y()); + Vector3f end = new Vector3f(pieceDestinationBoard.x(), 0, pieceDestinationBoard.y()); + + piece.setPosition(bezierCurve(start, top, end, progress)); + } + + private void move3DPiece(Move move) { + Vector2f pieceDestBoardPos = DDDPlacement.coordinatesToVector(move.getFinish()); + Vector3f pieceDestWorldPos = new Vector3f(pieceDestBoardPos.x(), 0, pieceDestBoardPos.y()); + + final PieceEntity pEntity = this.world.getPiece(move.getStart()); + final Move pMove = move; + + this.moveProgress = 0.0f; + + Consumer moveConsumer = (delta) -> { + this.moveProgress += delta; + pieceTick(this.moveProgress, pEntity, pMove); + }; + + this.window.addRegularTask(moveConsumer); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + this.window.removeRegularTask(moveConsumer); + + if (move.getDeadPieceCoords() != null) { + this.world.ejectPiece(move.getDeadPieceCoords()); + } + pEntity.setPosition(pieceDestWorldPos); + + this.world.movePiece(pEntity, move); + } + + @Override + public void onMove(Move move) { + move3DPiece(move); + cameraRotate(); + } + + private void cameraTick(float delta) { + int oddAnimationTurn = (2 * (animationTurns - 1)) + 1; + final float angle = (float) Math.PI; + this.camera.setRotateAngle(this.camera.getRotateAngle() + angle * delta * oddAnimationTurn / animationTime); + } + + private void cameraRotate() { + float end = this.camera.getRotateAngle() + (float) Math.PI; + Consumer rotationConsumer = this::cameraTick; + this.window.addRegularTask(rotationConsumer); try { Thread.sleep((long) (animationTime * 1000)); } catch (InterruptedException e) { throw new RuntimeException(e); } - this.window.removeRegularTask(rotationConsumer); - this.camera.setRotateAngle(end); - } + this.window.removeRegularTask(rotationConsumer); + this.camera.setRotateAngle(end); + } - public void run() { - this.window.run(); + public void run() { + this.window.run(); - // free OpenGL resources - try { - this.window.close(); - this.world.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } + // free OpenGL resources + try { + this.window.close(); + this.world.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + private void renderPopup(String title, String text) { + if(ImGui.beginPopupModal(title, popupOpened)){ + ImGui.text(text); + ImGui.endPopup(); + } + } + + private void renderPopups() { + renderPopup("Check","Your king is in check"); + renderPopup("Checkmate", "Checkmate, it's a win!"); + renderPopup("Promotion", "Please promote your pawn."); + renderPopup("Pat", "It's a pat!"); + renderPopup("Tie", "It's a tie!"); + renderPopup("White surrender", "The white player has surrendered!"); + renderPopup("Black surrender", "The black player has surrendered!"); + renderPopup("White victory", "The white player has won !"); + renderPopup("Black victory", "The black player has won !"); + renderPopup("End", "End of the game, thank you for playing!"); + } + + private void openPopup(String title) { + this.popupOpened.set(true); + this.waitingPopup = title; + } + + @Override + public void onKingInCheck(){ + openPopup("Check"); + } + + @Override + public void onDraw() { + openPopup("Tie"); + } + + @Override + public void onKingInMat() { + openPopup("Checkmate"); + } + + @Override + public void onGameEnd() { + openPopup("End"); + } + + @Override + public void onPatSituation() { + openPopup("Pat"); + } + + @Override + public void onSurrender(Color color) { + openPopup(color == Color.White ? "White surrender": "Black surrender"); + openPopup(color == Color.White ? "Black victory" : "White victory"); + } } From 96945d949af29f810d9857a56389831b6e8112dc Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 17 May 2025 10:22:07 +0200 Subject: [PATCH 38/40] remove redundant interface --- app/src/main/java/chess/view/DDDrender/DDDView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index 8a80341..faf37ca 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -24,7 +24,7 @@ import chess.view.DDDrender.world.BoardEntity; import chess.view.DDDrender.world.PieceEntity; import chess.view.DDDrender.world.World; -public class DDDView extends GameAdaptator implements GameListener { +public class DDDView extends GameAdaptator { private static final Vector3f BLACK = new Vector3f(0.3f, 0.3f, 0.3f); private static final Vector3f WHITE = new Vector3f(1.0f, 1.0f, 1.0f); From 0a1582cf07639f5c92d25466cd49a5d0ebfd8348 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 17 May 2025 12:37:57 +0200 Subject: [PATCH 39/40] round fps --- app/src/main/java/chess/view/DDDrender/DDDView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/chess/view/DDDrender/DDDView.java b/app/src/main/java/chess/view/DDDrender/DDDView.java index faf37ca..ad5c09d 100644 --- a/app/src/main/java/chess/view/DDDrender/DDDView.java +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -219,7 +219,7 @@ public class DDDView extends GameAdaptator { } private void onHeaderRender() { - ImGui.text("FPS : " + ImGui.getIO().getFramerate()); + ImGui.text("FPS : " + (int) ImGui.getIO().getFramerate()); } private void onFooterRender() { From c80805fc668794dca5eb6cff0776d2b9c53781cf Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 17 May 2025 12:38:29 +0200 Subject: [PATCH 40/40] refactor: fbo --- .../java/chess/view/DDDrender/Renderer.java | 41 ++----- .../java/chess/view/DDDrender/Window.java | 64 ++++++----- .../view/DDDrender/opengl/FrameBuffer.java | 106 ++++++++++++++++++ 3 files changed, 153 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/chess/view/DDDrender/opengl/FrameBuffer.java diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index eb66ad0..a7ee8ac 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -1,21 +1,15 @@ package chess.view.DDDrender; -import static org.lwjgl.opengl.GL11.GL_DEPTH_COMPONENT; -import static org.lwjgl.opengl.GL11.GL_RGB; -import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; -import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; -import static org.lwjgl.opengl.GL11.glBindTexture; -import static org.lwjgl.opengl.GL11.glTexImage2D; import java.io.Closeable; import java.io.IOException; -import java.nio.IntBuffer; import org.joml.Matrix4f; import org.joml.Vector3f; import org.lwjgl.opengl.GL30; +import chess.view.DDDrender.opengl.FrameBuffer; import chess.view.DDDrender.opengl.VertexArray; import chess.view.DDDrender.shader.BoardShader; import chess.view.DDDrender.shader.PieceShader; @@ -24,41 +18,18 @@ import chess.view.DDDrender.shader.ShaderProgram; public class Renderer implements Closeable { private BoardShader boardShader; private PieceShader pieceShader; + private FrameBuffer frameBuffer; public Renderer() { this.boardShader = new BoardShader(); this.pieceShader = new PieceShader(); } - public void Init() { + public void Init(float windowWidth, float widowHeight) { boardShader.LoadShader(); pieceShader.LoadShader(); - // The frame buffer - int fbo = GL30.glGenFramebuffers(); - GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo); - - // The texture - int renderTexture = GL30.glGenTextures(); - glBindTexture(GL_TEXTURE_2D, renderTexture); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 800, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); - - GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - - // The depth buffer - int depthBuffer = GL30.glGenRenderbuffers(); - GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthBuffer); - GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 800, 800); - GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, - depthBuffer); - - // Set "renderedTexture" as our colour attachement #0 - GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTexture, 0); - // Set the list of draw buffers. - int[] drawBuffers = { GL30.GL_COLOR_ATTACHMENT0 }; - GL30.glDrawBuffers(drawBuffers); + this.frameBuffer = new FrameBuffer((int) windowWidth, (int) (widowHeight * 8.0f / 10.0f)); } public void Update(Camera cam) { @@ -89,6 +60,10 @@ public class Renderer implements Closeable { vertexArray.Unbind(); } + public FrameBuffer getFrameBuffer() { + return frameBuffer; + } + public BoardShader getBoardShader() { return boardShader; } diff --git a/app/src/main/java/chess/view/DDDrender/Window.java b/app/src/main/java/chess/view/DDDrender/Window.java index 4e92982..2cac876 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -9,14 +9,18 @@ import org.lwjgl.system.*; import chess.model.Coordinate; import chess.view.AssetManager; +import chess.view.DDDrender.opengl.FrameBuffer; import chess.view.DDDrender.world.Entity; import chess.view.DDDrender.world.World; import common.Signal1; import imgui.ImFontConfig; import imgui.ImGui; import imgui.ImVec2; +import imgui.flag.ImGuiWindowFlags; import imgui.gl3.ImGuiImplGl3; import imgui.glfw.ImGuiImplGlfw; +import imgui.type.ImBoolean; +import imgui.type.ImInt; import java.io.Closeable; import java.io.IOException; @@ -57,6 +61,9 @@ public class Window implements Closeable { public final Signal0 OnImGuiTopRender = new Signal0(); public final Signal0 OnImGuiBottomRender = new Signal0(); + private ImInt detailLevel = new ImInt(10); + private ImBoolean pixelatedFrame = new ImBoolean(true); + public Window(Renderer renderer, World world, Camera camera) { this.renderer = new Renderer(); this.cam = camera; @@ -153,17 +160,17 @@ public class Window implements Closeable { (vidmode.height() - pHeight.get(0)) / 2); glfwSetWindowSize(window, vidmode.width(), vidmode.height()); + + // Make the OpenGL context current + glfwMakeContextCurrent(window); + + GL.createCapabilities(); + + initImGui(); + + renderer.Init(vidmode.width(), vidmode.height()); } // the stack frame is popped automatically - // Make the OpenGL context current - glfwMakeContextCurrent(window); - - GL.createCapabilities(); - - initImGui(); - - renderer.Init(); - // Enable v-sync glfwSwapInterval(1); @@ -172,13 +179,14 @@ public class Window implements Closeable { } private void render(float delta, float aspectRatio) { - cam.setAspectRatio(1.0f); - GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 1); - glViewport(0, 0, 800, 800); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer + final FrameBuffer frameBuffer = this.renderer.getFrameBuffer(); + + cam.setAspectRatio(frameBuffer.getAspectRatio()); + frameBuffer.Bind(); + frameBuffer.Clear(); renderer.Update(cam); renderWorld(); - GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0); + frameBuffer.Unbind(); } private void renderWorld() { @@ -231,13 +239,25 @@ public class Window implements Closeable { private void renderWindow() { ImGui.showDemoWindow(); - ImGui.begin("Main Window"); + final FrameBuffer frameBuffer = this.renderer.getFrameBuffer(); + final int frameWidth = (int) ImGui.getIO().getDisplaySizeX(); + final int frameHeight = (int) (ImGui.getIO().getDisplaySizeY() * 8.0f / 10.0f); + + ImGui.setNextWindowSize(ImGui.getIO().getDisplaySize()); + ImGui.setNextWindowPos(new ImVec2()); + ImGui.begin("Main Window", ImGuiWindowFlags.NoDecoration); this.OnImGuiTopRender.emit(); ImVec2 mousePos = ImGui.getIO().getMousePos(); ImVec2 framePos = ImGui.getCursorScreenPos(); - checkCursor(mousePos.x - framePos.x, 800 - (mousePos.y - framePos.y), 800, 800); - ImGui.image(1, new ImVec2(800, 800)); + checkCursor(mousePos.x - framePos.x, frameHeight - (mousePos.y - framePos.y), frameWidth, frameHeight); + ImGui.image(frameBuffer.getRenderTexture(), new ImVec2(frameWidth, frameHeight)); this.OnImGuiBottomRender.emit(); + if(ImGui.sliderInt("Niveau de détail", detailLevel.getData(), 1, 10)){ + frameBuffer.Resize((int) ((float) frameWidth * detailLevel.get() / 10.0f), (int) ((float) frameHeight * detailLevel.get() / 10.0f)); + } + if (ImGui.checkbox("Minecraft", pixelatedFrame)) { + frameBuffer.SetPixelScaling(pixelatedFrame.get()); + } ImGui.end(); } @@ -254,8 +274,6 @@ public class Window implements Closeable { glColor4f(1.0f, 0.0f, 0.0f, 1.0f); - double lastTime = glfwGetTime(); - int width[] = new int[1]; int height[] = new int[1]; glfwGetWindowSize(window, width, height); @@ -266,23 +284,19 @@ public class Window implements Closeable { newFrame(); - double currentTime = glfwGetTime(); - float deltaTime = (float) (currentTime - lastTime); - render(deltaTime, (float) width[0] / (float) height[0]); + render(ImGui.getIO().getDeltaTime(), (float) width[0] / (float) height[0]); renderWindow(); ImGui.render(); implGl3.renderDrawData(ImGui.getDrawData()); - lastTime = glfwGetTime(); - glfwSwapBuffers(window); // swap the color buffers // Poll for window events. The key callback above will only be // invoked during this call. glfwPollEvents(); - executeTasks(deltaTime); + executeTasks(ImGui.getIO().getDeltaTime()); glfwGetWindowSize(window, width, height); glViewport(0, 0, width[0], height[0]); diff --git a/app/src/main/java/chess/view/DDDrender/opengl/FrameBuffer.java b/app/src/main/java/chess/view/DDDrender/opengl/FrameBuffer.java new file mode 100644 index 0000000..d145f3f --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/opengl/FrameBuffer.java @@ -0,0 +1,106 @@ +package chess.view.DDDrender.opengl; + +import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; +import static org.lwjgl.opengl.GL11.GL_DEPTH_COMPONENT; +import static org.lwjgl.opengl.GL11.GL_RGB; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; +import static org.lwjgl.opengl.GL11.glBindTexture; +import static org.lwjgl.opengl.GL11.glClear; +import static org.lwjgl.opengl.GL11.glTexImage2D; +import static org.lwjgl.opengl.GL11.glViewport; + +import org.lwjgl.opengl.GL30; + +public class FrameBuffer { + + private int fbo; + private int renderTexture; + private int depthBuffer; + + private int width; + private int height; + + public FrameBuffer(int width, int height) { + this.width = width; + this.height = height; + + // The frame buffer + this.fbo = GL30.glGenFramebuffers(); + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo); + + // The texture + this.renderTexture = GL30.glGenTextures(); + glBindTexture(GL_TEXTURE_2D, renderTexture); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + + GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + + // The depth buffer + this.depthBuffer = GL30.glGenRenderbuffers(); + GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthBuffer); + GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height); + GL30.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, + depthBuffer); + + // Set "renderedTexture" as our colour attachement #0 + GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTexture, 0); + // Set the list of draw buffers. + int[] drawBuffers = { GL30.GL_COLOR_ATTACHMENT0 }; + GL30.glDrawBuffers(drawBuffers); + } + + public void Bind() { + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, this.fbo); + glViewport(0, 0, this.width, this.height); + } + + public void Unbind() { + GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0); + } + + public void Clear() { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer + } + + public void Resize(int width, int height) { + this.width = width; + this.height = height; + + GL30.glBindTexture(GL_TEXTURE_2D, this.renderTexture); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, this.width, this.height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + + GL30.glBindRenderbuffer(GL30.GL_RENDERBUFFER, this.depthBuffer); + GL30.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL_DEPTH_COMPONENT, this.width, this.height); + } + + public void SetPixelScaling(boolean pixelated) { + glBindTexture(GL_TEXTURE_2D, renderTexture); + + int method = pixelated ? GL30.GL_NEAREST : GL30.GL_LINEAR; + + GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, method); + GL30.glTexParameteri(GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, method); + } + + public float getAspectRatio() { + return (float) width / (float) height; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getRenderTexture() { + return renderTexture; + } + +}