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/.gitignore b/.gitignore index f3001a4..684fed0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,5 @@ app/bin .vscode -audio/*.wav -app/audio/*.wav \ No newline at end of file +*.wav +imgui.ini \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index d36f72b..1234bb8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,21 +16,29 @@ 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. - testImplementation "org.junit.jupiter:junit-jupiter:5.11.4" + testImplementation "org.junit.jupiter:junit-jupiter:5.9.1" 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" + + 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 { @@ -39,24 +47,16 @@ application { applicationName = "3DChess" } -run { - standardInput = System.in -} - - -// Apply a specific Java toolchain to ease working on different environments. -java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } -} - jar { manifest { attributes 'Main-Class': application.mainClass } } +run { + standardInput = System.in +} + tasks.named('test') { // Use JUnit Platform for unit tests. useJUnitPlatform() diff --git a/app/src/main/java/chess/OpenGLMain.java b/app/src/main/java/chess/OpenGLMain.java index 3929ae5..b3e6195 100644 --- a/app/src/main/java/chess/OpenGLMain.java +++ b/app/src/main/java/chess/OpenGLMain.java @@ -1,9 +1,21 @@ 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(); + commandExecutor.close(); } } diff --git a/app/src/main/java/chess/ai/actions/AIAction.java b/app/src/main/java/chess/ai/actions/AIAction.java index 9241a35..f7ac577 100644 --- a/app/src/main/java/chess/ai/actions/AIAction.java +++ b/app/src/main/java/chess/ai/actions/AIAction.java @@ -1,11 +1,10 @@ package chess.ai.actions; -import chess.controller.Command; import chess.controller.CommandExecutor; -import chess.controller.Command.CommandResult; +import chess.controller.CommandSender; import chess.controller.commands.UndoCommand; -public abstract class AIAction { +public abstract class AIAction implements CommandSender { private final CommandExecutor commandExecutor; @@ -13,8 +12,9 @@ public abstract class AIAction { this.commandExecutor = commandExecutor; } - protected CommandResult sendCommand(Command cmd, CommandExecutor commandExecutor) { - return commandExecutor.executeCommand(cmd); + @Override + public CommandExecutor getCommandExecutor() { + return this.commandExecutor; } public void undoAction(CommandExecutor commandExecutor) { diff --git a/app/src/main/java/chess/ai/minimax/GameSimulation.java b/app/src/main/java/chess/ai/minimax/GameSimulation.java index ef9cef1..9d02d5a 100644 --- a/app/src/main/java/chess/ai/minimax/GameSimulation.java +++ b/app/src/main/java/chess/ai/minimax/GameSimulation.java @@ -4,15 +4,9 @@ import java.util.List; import chess.ai.actions.AIAction; import chess.ai.actions.AIActions; -import chess.controller.Command; -import chess.controller.Command.CommandResult; import chess.controller.CommandExecutor; -import chess.controller.commands.CastlingCommand; -import chess.controller.commands.MoveCommand; -import chess.controller.commands.NewGameCommand; -import chess.controller.commands.PromoteCommand; +import chess.controller.CommandSender; import chess.controller.commands.PromoteCommand.PromoteType; -import chess.controller.commands.UndoCommand; import chess.controller.event.EmptyGameDispatcher; import chess.controller.event.GameAdapter; import chess.model.ChessBoard; @@ -21,7 +15,7 @@ import chess.model.Game; import chess.model.Move; import chess.model.PermissiveGame; -public class GameSimulation extends GameAdapter { +public class GameSimulation extends GameAdapter implements CommandSender { private final CommandExecutor simulation; private final Game gameSimulation; @@ -31,48 +25,33 @@ public class GameSimulation extends GameAdapter { this.simulation = new CommandExecutor(gameSimulation, new EmptyGameDispatcher()); } - protected CommandResult sendCommand(Command command) { - CommandResult result = this.simulation.executeCommand(command); - if (result == CommandResult.NotAllowed) { - System.out.println("eeeeee"); - } - return result; - } - - public void tryMove(Move move) { - sendCommand(new MoveCommand(move)); - if (this.gameSimulation.getBoard().pawnShouldBePromoted()) - sendCommand(new PromoteCommand(PromoteType.Queen)); - } - - public void undoMove() { - sendCommand(new UndoCommand()); - } - @Override public void onPawnPromoted(PromoteType promotion) { - sendCommand(new PromoteCommand(promotion)); + sendPawnPromotion(promotion); } @Override public void onCastling(boolean bigCastling) { - sendCommand(new CastlingCommand(bigCastling)); + if (bigCastling) + sendBigCastling(); + else + sendCastling(); } @Override public void onMove(Move move, boolean captured) { - sendCommand(new MoveCommand(move)); + sendMove(move); } @Override public void onGameStart() { - sendCommand(new NewGameCommand()); + sendStartGame(); } @Override public void onPlayerTurn(Color color, boolean undone) { if (undone) - sendCommand(new UndoCommand()); + sendUndo(); } public CommandExecutor getCommandExecutor() { diff --git a/app/src/main/java/chess/controller/CommandSender.java b/app/src/main/java/chess/controller/CommandSender.java new file mode 100644 index 0000000..dcc2c54 --- /dev/null +++ b/app/src/main/java/chess/controller/CommandSender.java @@ -0,0 +1,102 @@ +package chess.controller; + +import java.util.List; + +import chess.controller.Command.CommandResult; +import chess.controller.commands.CastlingCommand; +import chess.controller.commands.GetAllowedCastlingsCommand; +import chess.controller.commands.GetAllowedMovesPieceCommand; +import chess.controller.commands.GetPieceAtCommand; +import chess.controller.commands.GetPlayerMovesCommand; +import chess.controller.commands.MoveCommand; +import chess.controller.commands.NewGameCommand; +import chess.controller.commands.PromoteCommand; +import chess.controller.commands.SurrenderCommand; +import chess.controller.commands.UndoCommand; +import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult; +import chess.controller.commands.PromoteCommand.PromoteType; +import chess.model.Color; +import chess.model.Coordinate; +import chess.model.Move; +import chess.model.Piece; + +public interface CommandSender { + + CommandExecutor getCommandExecutor(); + + default CommandResult sendCommand(Command cmd, CommandExecutor commandExecutor) { + CommandResult result = commandExecutor.executeCommand(cmd); + if (result == CommandResult.NotAllowed) { + // maybe do something + } + return result; + } + + default CommandResult sendCommand(Command cmd) { + return sendCommand(cmd, getCommandExecutor()); + } + + // inputs + + default CastlingResult getAllowedCastlings() { + GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand(); + sendCommand(cmd); + return cmd.getCastlingResult(); + } + + default Piece getPieceAt(int x, int y) { + return getPieceAt(new Coordinate(x, y)); + } + + default Piece getPieceAt(Coordinate coordinate) { + GetPieceAtCommand cmd = new GetPieceAtCommand(coordinate); + sendCommand(cmd); + return cmd.getPiece(); + } + + default boolean isCellEmpty(int x, int y) { + return getPieceAt(x, y) == null; + } + + default List getPlayerMoves() { + GetPlayerMovesCommand cmd = new GetPlayerMovesCommand(); + sendCommand(cmd); + return cmd.getMoves(); + } + + default List getPieceAllowedMoves(Coordinate piecePos) { + GetAllowedMovesPieceCommand cmd = new GetAllowedMovesPieceCommand(piecePos); + sendCommand(cmd); + return cmd.getDestinations(); + } + + // outputs + + default CommandResult sendCastling() { + return sendCommand(new CastlingCommand(false)); + } + + default CommandResult sendBigCastling() { + return sendCommand(new CastlingCommand(false)); + } + + default CommandResult sendMove(Move move) { + return sendCommand(new MoveCommand(move)); + } + + default CommandResult sendStartGame() { + return sendCommand(new NewGameCommand()); + } + + default CommandResult sendPawnPromotion(PromoteType promote) { + return sendCommand(new PromoteCommand(promote)); + } + + default CommandResult sendSurrender(Color player) { + return sendCommand(new SurrenderCommand(player)); + } + + default CommandResult sendUndo() { + return sendCommand(new UndoCommand()); + } +} diff --git a/app/src/main/java/chess/view/DDDrender/Camera.java b/app/src/main/java/chess/view/DDDrender/Camera.java index 8efdbeb..e26cfe0 100644 --- a/app/src/main/java/chess/view/DDDrender/Camera.java +++ b/app/src/main/java/chess/view/DDDrender/Camera.java @@ -1,23 +1,29 @@ 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; - // 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 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(0, 2.0f, 0); - setRotation(0.0f, -3.14150f / 2.0f); + this.pos = new Vector3f(0.0f, camHeight, 0.0f); + setRotateAngle(0.0f); } public void move(float x, float y) { @@ -25,104 +31,56 @@ 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; } - public void setX(float x) { - this.pos.x = x; + public void setAspectRatio(float aspectRatio) { + this.aspectRatio = aspectRatio; } - public void setY(float y) { - this.pos.y = y; + public Matrix4f getPerspectiveMatrix() { + return new Matrix4f().perspective((float) (Math.toRadians(fov)), aspectRatio, zNear, zFar); } - public void setZ(float z) { - this.pos.z = z; + public Matrix4f getViewMatrix() { + return new Matrix4f().lookAt(pos, center, up); } - public void setYaw(float yaw) { - this.yaw = yaw; - } + 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); - public void setPitch(float pitch) { - this.pitch = pitch; - } + Vector4f rayClip = new Vector4f(relativeX, relativeY, -1.0f, 1.0f); - public void reset() { - resetPosition(); - resetRotation(); - } + Vector4f rayEye = getPerspectiveMatrix().invert().transform(rayClip); - public void resetPosition() { - pos = new Vector3f(0.0f, 0.0f, 0.0f); - } + rayEye = new Vector4f(rayEye.x, rayEye.y, -1.0f, 0.0f); - public void resetRotation() { - yaw = 0.0f; - pitch = 0.0f; - } + Vector4f rayWorld = getViewMatrix().invert().transform(rayEye); - public void moveForward(float distance) { - pos.x += distance * (float) Math.cos(yaw); - pos.y += distance * (float) Math.sin(yaw); - } + float lambda = -this.pos.y / rayWorld.y; - 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 Matrix4f getMatrix() { - Vector3f forward = new Vector3f( - (float) (Math.cos(yaw) * Math.cos(pitch)), - (float) (Math.sin(pitch)), - (float) (Math.sin(yaw) * Math.cos(pitch))); - - return new Matrix4f() - .perspective((float) (Math.toRadians(fov)), aspect, zNear, zFar) - .lookAt(pos, forward, new Vector3f(0.0f, 1.0f, 0.0f)); + 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/DDDModel.java b/app/src/main/java/chess/view/DDDrender/DDDModel.java new file mode 100644 index 0000000..a2aec9f --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/DDDModel.java @@ -0,0 +1,27 @@ +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 implements Closeable { + private final List vaos; + + public DDDModel(List vaos) { + this.vaos = vaos; + } + + public List getVaos() { + return vaos; + } + + @Override + public void close() throws IOException { + for (VertexArray vertexArray : vaos) { + vertexArray.close(); + } + } + +} 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..ce0cc19 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/DDDPlacement.java @@ -0,0 +1,21 @@ +package chess.view.DDDrender; + +import org.joml.Vector2f; + +import chess.model.Coordinate; + +class DDDPlacement { + public static Vector2f coordinatesToVector(Coordinate coo) { + return coordinatesToVector(coo.getX(), coo.getY()); + } + + 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 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); + } +} \ 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 new file mode 100644 index 0000000..96cf3f9 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/DDDView.java @@ -0,0 +1,401 @@ +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.*; +import imgui.ImGui; +import imgui.type.ImBoolean; +import org.joml.Vector2f; +import org.joml.Vector3f; + +import chess.controller.Command.CommandResult; +import chess.controller.CommandExecutor; +import chess.controller.CommandSender; +import chess.controller.event.GameAdapter; +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; +import chess.view.DDDrender.world.World; + +public class DDDView extends GameAdapter implements CommandSender { + + 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 RED = new Vector3f(1.0f, 0.0f, 0.0f); + private static final Vector3f YELLOW = new Vector3f(1.0f, 1.0f, 0.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 static final float animationTime = 1.5f; // in seconds + private static final int animationTurns = 1; + + private float moveProgress = 0.0f; + + private String waitingPopup = null; + private final ImBoolean popupOpened = new ImBoolean(false); + + 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); + } + + @Override + public CommandExecutor getCommandExecutor() { + return this.commandExecutor; + } + + private void cancelClick() { + this.click = null; + } + + private void setClick(Coordinate coordinate) { + this.click = coordinate; + } + + // Invoked when a cell is clicked + private void onCellClick(Coordinate coordinate) { + if (this.click == null) { // case: first click + List allowedMoves = getPieceAllowedMoves(coordinate); + 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); + sendMove(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 void previewMoves(Coordinate coordinate) { + List allowedMoves = getPieceAllowedMoves(coordinate); + if (allowedMoves.isEmpty()) + return; + this.boardEntity.setCellColor(coordinate, RED); + this.world.getPiece(coordinate).setColor(RED); + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.setCellColor(destCoord, YELLOW); + } + } + + // 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, RED); + Piece p = pieceAt(coordinate); + if (p == null) + return; + this.world.getPiece(coordinate).setColor(RED); + List allowedMoves = getPieceAllowedMoves(coordinate); + if (allowedMoves.isEmpty()) + return; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.setCellColor(destCoord, RED); + } + } + } + + // 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; + + pEntity.setColor(p.getColor() == Color.White ? WHITE : BLACK); + List allowedMoves = getPieceAllowedMoves(coordinate); + 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); + List allowedMoves = getPieceAllowedMoves(coordinate); + if (allowedMoves.isEmpty()) + return; + for (Coordinate destCoord : allowedMoves) { + this.boardEntity.resetCellColor(destCoord); + } + } + + private Piece pieceAt(Coordinate pos) { + GetPieceAtCommand cmd = new GetPieceAtCommand(pos); + this.commandExecutor.executeCommand(cmd); + return cmd.getPiece(); + } + + @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); + }); + } + + private void onHeaderRender() { + ImGui.text("FPS : " + (int) ImGui.getIO().getFramerate()); + } + + private void onFooterRender() { + if (ImGui.button("Roque")) { + sendCastling(); + } + ImGui.sameLine(); + if (ImGui.button("Grand Roque")) { + sendBigCastling(); + } + ImGui.sameLine(); + if (ImGui.button("Annuler le coup précédent")) { + sendUndo(); + } + openPopup(); + renderPopups(); + } + + private void openPopup() { + if (waitingPopup != null) { + ImGui.openPopup(waitingPopup); + waitingPopup = null; + } + } + + 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, boolean captured) { + 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); + } + + public void run() { + this.window.run(); + + // 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"); + } +} diff --git a/app/src/main/java/chess/view/DDDrender/Renderer.java b/app/src/main/java/chess/view/DDDrender/Renderer.java index 2033785..a7ee8ac 100644 --- a/app/src/main/java/chess/view/DDDrender/Renderer.java +++ b/app/src/main/java/chess/view/DDDrender/Renderer.java @@ -2,123 +2,75 @@ 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; +import chess.view.DDDrender.opengl.FrameBuffer; +import chess.view.DDDrender.opengl.VertexArray; 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 VertexArray vao; - - 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 class Renderer implements Closeable { + private BoardShader boardShader; + private PieceShader pieceShader; + private FrameBuffer frameBuffer; public Renderer() { - this.shader = new BoardShader(); + this.boardShader = new BoardShader(); + this.pieceShader = new PieceShader(); } - public void Init() { - shader.LoadShader(); - InitBoard(); + public void Init(float windowWidth, float widowHeight) { + boardShader.LoadShader(); + pieceShader.LoadShader(); + + this.frameBuffer = new FrameBuffer((int) windowWidth, (int) (widowHeight * 8.0f / 10.0f)); } - 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; + public void Update(Camera cam) { + this.boardShader.Start(); + this.boardShader.SetCamMatrix(cam); + this.pieceShader.Start(); + this.pieceShader.SetCamMatrix(cam); + } - float trueX = 2 * x - 1; - float trueZ = 2 * z - 1; - float trueDX = 2 * dx - 1; - float trueDZ = 2 * dz - 1; + public void Render(DDDModel model, Vector3f color, Matrix4f transform) { + this.pieceShader.Start(); + this.pieceShader.setModelColor(color); + this.pieceShader.setModelTransform(transform); + Render(model); + } - 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; - } + public void Render(DDDModel model) { + for (int i = 0; i < model.getVaos().size(); i++) { + VertexArray vao = model.getVaos().get(i); + RenderVao(this.pieceShader, vao); } - 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 Render(Camera cam) { - this.shader.Start(); - this.shader.SetCamMatrix(cam.getMatrix()); - RenderVao(vao); - } - - public void RenderVao(VertexArray vertexArray) { - this.shader.Start(); + public void RenderVao(ShaderProgram shader, VertexArray vertexArray) { + shader.Start(); vertexArray.Bind(); GL30.glDrawElements(GL30.GL_TRIANGLES, vertexArray.GetVertexCount(), GL_UNSIGNED_INT, 0); vertexArray.Unbind(); } + + public FrameBuffer getFrameBuffer() { + return frameBuffer; + } + + 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 e2b8ff7..edd18a4 100644 --- a/app/src/main/java/chess/view/DDDrender/Window.java +++ b/app/src/main/java/chess/view/DDDrender/Window.java @@ -1,11 +1,35 @@ package chess.view.DDDrender; +import common.AssetManager; +import common.Signal0; +import org.joml.Vector2f; import org.lwjgl.*; import org.lwjgl.glfw.*; import org.lwjgl.opengl.*; import org.lwjgl.system.*; +import chess.model.Coordinate; +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; 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.*; @@ -13,21 +37,53 @@ 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; - private Renderer renderer; - private Camera cam; + private final ImGuiImplGl3 implGl3 = new ImGuiImplGl3(); + private final ImGuiImplGlfw implGlfw = new ImGuiImplGlfw(); - public Window() { + private Renderer renderer; + private final Camera cam; + private final World world; + + 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 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 = new Camera(); + this.cam = camera; + this.tasks = new ConcurrentLinkedDeque<>(); + this.world = world; + this.regularTasks = new ArrayList<>(); } - public static void main(String[] args) { - new Window().run(); + public void addRegularTask(Consumer task) { + this.regularTasks.add(task); + } + + public void removeRegularTask(Consumer task) {this.regularTasks.remove(task);} + + public synchronized void scheduleTask(Runnable runnable) { + this.tasks.add(runnable); + } + + public synchronized Runnable getNextTask() { + return this.tasks.poll(); } public void run() { @@ -43,6 +99,28 @@ public class Window { // Terminate GLFW and free the error callback glfwTerminate(); glfwSetErrorCallback(null).free(); + + implGl3.shutdown(); + implGlfw.shutdown(); + + ImGui.destroyContext(); + } + + 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() { @@ -80,10 +158,19 @@ public class Window { window, (vidmode.width() - pWidth.get(0)) / 2, (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); // Enable v-sync glfwSwapInterval(1); @@ -91,32 +178,117 @@ public class Window { glfwShowWindow(window); } - private void render() { - cam.rotate(0.01f, 0.01f); - renderer.Render(cam); + private void render(float delta, float aspectRatio) { + final FrameBuffer frameBuffer = this.renderer.getFrameBuffer(); + + cam.setAspectRatio(frameBuffer.getAspectRatio()); + frameBuffer.Bind(); + frameBuffer.Clear(); + renderer.Update(cam); + renderWorld(); + frameBuffer.Unbind(); + } + + private void renderWorld() { + for (Entity entity : this.world.getEntites()) { + entity.render(this.renderer); + } + } + + private void executeTasks(float delta) { + Runnable task = getNextTask(); + while (task != null) { + task.run(); + task = getNextTask(); + } + for (Consumer consumer : regularTasks) { + consumer.accept(delta); + } + } + + 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) { + 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; + } + + if (ImGui.getIO().getMouseClicked(0)) { + if (this.lastCell != null && 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 renderWindow() { + ImGui.showDemoWindow(); + + 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, 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(); } 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.0f, 0.0f, 0.0f, 0.0f); + glClearColor(0.4f, 0.4f, 0.6f, 1.0f); + + glEnable(GL_DEPTH_TEST); + + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + glFrontFace(GL_CW); glColor4f(1.0f, 0.0f, 0.0f, 1.0f); + 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(); + newFrame(); + + render(ImGui.getIO().getDeltaTime(), (float) width[0] / (float) height[0]); + + renderWindow(); + ImGui.render(); + implGl3.renderDrawData(ImGui.getDrawData()); glfwSwapBuffers(window); // swap the color buffers @@ -124,16 +296,17 @@ public class Window { // invoked during this call. glfwPollEvents(); - try (MemoryStack stack = stackPush()) { - IntBuffer pWidth = stack.mallocInt(1); // int* - IntBuffer pHeight = stack.mallocInt(1); // int* + executeTasks(ImGui.getIO().getDeltaTime()); - // Get the window size passed to glfwCreateWindow - glfwGetWindowSize(window, pWidth, pHeight); + glfwGetWindowSize(window, width, height); + glViewport(0, 0, width[0], height[0]); - glViewport(0, 0, pWidth.get(), pHeight.get()); - } // the stack frame is popped automatically } } + @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/loader/BoardModelLoader.java b/app/src/main/java/chess/view/DDDrender/loader/BoardModelLoader.java new file mode 100644 index 0000000..af5f027 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/loader/BoardModelLoader.java @@ -0,0 +1,98 @@ +package chess.view.DDDrender.loader; + +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 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++) { + 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 = (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; + 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 + 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; + } + 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/loader/ModelLoader.java b/app/src/main/java/chess/view/DDDrender/loader/ModelLoader.java new file mode 100644 index 0000000..24e2d82 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/loader/ModelLoader.java @@ -0,0 +1,130 @@ +package chess.view.DDDrender.loader; + +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 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.DDDrender.DDDModel; +import chess.view.DDDrender.opengl.ElementBuffer; +import chess.view.DDDrender.opengl.VertexArray; +import chess.view.DDDrender.opengl.VertexBuffer; +import common.AssetManager; + +public class ModelLoader { + + private static final int VERTEX_SIZE = 3; + private static final int VERTEX_POSITION_INDEX = 0; + private static final int VERTEX_NORMAL_INDEX = 1; + + 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 normals = new ArrayList<>(); + + List indicies = new ArrayList<>(); + + Buffer faces = mesh.mFaces(); + int faceNumber = mesh.mNumFaces(); + + for (int i = 0; i < faceNumber; i++) { + IntBuffer faceIndicies = faces.get(i).mIndices(); + 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()); + } + + VertexBuffer positionVBO = new VertexBuffer(toFloatArray(positions), VERTEX_SIZE); + positionVBO.AddVertexAttribPointer(VERTEX_POSITION_INDEX, VERTEX_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(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 DDDModel 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_GlobalScale + | Assimp.aiProcess_ValidateDataStructure, + ""); + + if (scene == null) + System.err.println(Assimp.aiGetErrorString()); + + List vertecies = new ArrayList<>(); + + processNode(scene.mRootNode(), scene, vertecies); + + MemoryUtil.memFree(data); + + return new DDDModel(vertecies); + + } + +} diff --git a/app/src/main/java/chess/view/DDDrender/loader/Piece3DModel.java b/app/src/main/java/chess/view/DDDrender/loader/Piece3DModel.java new file mode 100644 index 0000000..7b9c21f --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/loader/Piece3DModel.java @@ -0,0 +1,75 @@ +package chess.view.DDDrender.loader; + +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; +import chess.view.DDDrender.DDDModel; + +public class Piece3DModel 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) + ".fbx"; + 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 "king"; + } + + @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/opengl/ElementBuffer.java b/app/src/main/java/chess/view/DDDrender/opengl/ElementBuffer.java new file mode 100644 index 0000000..6d18cb1 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/opengl/ElementBuffer.java @@ -0,0 +1,37 @@ +package chess.view.DDDrender.opengl; + +import java.io.Closeable; +import java.io.IOException; + +import org.lwjgl.opengl.GL30; + +public class ElementBuffer implements Closeable { + private final int id; + private final int indiciesCount; + + public ElementBuffer(int[] indicies) { + this.indiciesCount = indicies.length; + this.id = GL30.glGenBuffers(); + Bind(); + GL30.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, indicies.length * 4, GL30.GL_STATIC_DRAW); + GL30.glBufferSubData(GL30.GL_ELEMENT_ARRAY_BUFFER, 0, indicies); + Unbind(); + } + + public void Bind() { + GL30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.id); + } + + public void Unbind() { + GL30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, 0); + } + + 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/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; + } + +} diff --git a/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java new file mode 100644 index 0000000..bf9fb45 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexArray.java @@ -0,0 +1,59 @@ +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 implements Closeable { + + private final int id; + private final ElementBuffer elementBuffer; + private final List vertexBuffers; + + public VertexArray(ElementBuffer elementBuffer) { + this.id = GL30.glGenVertexArrays(); + this.elementBuffer = elementBuffer; + this.vertexBuffers = new ArrayList(); + Bind(); + BindElementArrayBuffer(); + Unbind(); + } + + public int GetVertexCount() { + return this.elementBuffer.GetIndiciesCount(); + } + + public void BindVertexBuffer(VertexBuffer buffer) { + buffer.Bind(); + buffer.BindVertexAttribs(); + this.vertexBuffers.add(buffer); + } + + public void Bind() { + GL30.glBindVertexArray(this.id); + } + + public void Unbind() { + GL30.glBindVertexArray(0); + } + + private void BindElementArrayBuffer() { + this.elementBuffer.Bind(); + } + + 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/VertexAttribPointer.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexAttribPointer.java new file mode 100644 index 0000000..dd4fc01 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexAttribPointer.java @@ -0,0 +1,5 @@ +package chess.view.DDDrender.opengl; + +public record VertexAttribPointer(int index, int size, int offset) { + +} diff --git a/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java b/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java new file mode 100644 index 0000000..191c4d3 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/opengl/VertexBuffer.java @@ -0,0 +1,58 @@ +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 implements Closeable { + private final int id; + private final int dataStride; + private final List vertexAttribs; + + public VertexBuffer(float[] data, int stride) { + this.id = GL30.glGenBuffers(); + this.dataStride = stride; + this.vertexAttribs = new ArrayList(); + Bind(); + GL30.glBufferData(GL30.GL_ARRAY_BUFFER, data.length * 4, GL30.GL_STATIC_DRAW); + GL30.glBufferSubData(GL30.GL_ARRAY_BUFFER, 0, data); + Unbind(); + } + + public void UpdateData(int offset, float[] data) { + Bind(); + GL30.glBufferSubData(GL30.GL_ARRAY_BUFFER, offset, data); + Unbind(); + } + + public void Bind() { + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.id); + } + + public void Unbind() { + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, 0); + } + + public void AddVertexAttribPointer(int index, int coordinateSize, int offset) { + VertexAttribPointer pointer = new VertexAttribPointer(index, coordinateSize, offset); + this.vertexAttribs.add(pointer); + } + + public void BindVertexAttribs() { + for (VertexAttribPointer vertexAttribPointer : vertexAttribs) { + GL30.glEnableVertexAttribArray(vertexAttribPointer.index()); + GL30.glVertexAttribPointer(vertexAttribPointer.index(), vertexAttribPointer.size(), GL_FLOAT, false, + 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/BoardShader.java b/app/src/main/java/chess/view/DDDrender/shader/BoardShader.java index cc3fdbe..b0f0033 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,31 @@ 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 = vec3(0, 10, 0); flat out vec3 pass_color; + out vec3 toLightVector; + out vec3 toCameraVector; + out vec3 surfaceNormal; + void main(void){ - gl_Position = camMatrix * vec4(position, 1.0); + 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; } + """; private static String fragmentShader = """ @@ -25,14 +42,44 @@ public class BoardShader extends ShaderProgram { flat in vec3 pass_color; - out vec4 out_color; + in vec3 toLightVector; + in vec3 toCameraVector; + in vec3 surfaceNormal; + + layout(location = 0) 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.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); + + vec3 lightDirection = -unitLightVector; + vec3 reflectedLightDirection = reflect(lightDirection, unitNormal); + + float diffuse = max(0.2, dot(unitNormal, unitLightVector)); + + 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; + } + """; - private int location_CamMatrix = 0; + private int location_ProjectionMatrix = 0; + private int location_ViewMatrix = 0; public BoardShader() { @@ -44,10 +91,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 new file mode 100644 index 0000000..754c60a --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/shader/PieceShader.java @@ -0,0 +1,116 @@ +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 = """ + #version 330 + + layout(location = 0) in vec3 position; + layout(location = 1) in vec3 normal; + + uniform mat4 projectionMatrix; + uniform mat4 viewMatrix; + uniform mat4 modelTransform; + uniform vec3 lightPosition = vec3(0, 10, 0); + + out vec3 toLightVector; + out vec3 toCameraVector; + out vec3 surfaceNormal; + + void main(void){ + + vec4 modelPos = modelTransform * vec4(position, 1.0); + vec4 globalNormal = modelTransform * vec4(normal, 1.0); + + 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; + + layout(location = 0) 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); + + vec3 lightDirection = -unitLightVector; + vec3 reflectedLightDirection = reflect(lightDirection, unitNormal); + + float diffuse = max(0.2, dot(unitNormal, unitLightVector)); + + 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; + private int location_ViewMatrix = 0; + private int location_ModelTransform = 0; + private int location_ModelColor = 0; + + public PieceShader() { + + } + + public void LoadShader() { + super.LoadProgram(vertexShader, fragmentShader); + } + + @Override + protected void GetAllUniformLocation() { + location_ProjectionMatrix = GetUniformLocation("projectionMatrix"); + location_ViewMatrix = GetUniformLocation("viewMatrix"); + location_ModelTransform = GetUniformLocation("modelTransform"); + location_ModelColor = GetUniformLocation("modelColor"); + } + + public void SetCamMatrix(Camera camera) { + LoadMat4(location_ProjectionMatrix, camera.getPerspectiveMatrix()); + LoadMat4(location_ViewMatrix, camera.getViewMatrix()); + } + + public void setModelTransform(Matrix4f mat) { + LoadMat4(location_ModelTransform, mat); + } + + public void setModelColor(Vector3f color) { + LoadVector(location_ModelColor, color); + } +} 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..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; @@ -56,8 +58,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 +70,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; } @@ -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 new file mode 100644 index 0000000..031f84e --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/BoardEntity.java @@ -0,0 +1,54 @@ +package chess.view.DDDrender.world; + +import java.io.IOException; + +import org.joml.Matrix4f; +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); + } + + @Override + public void close() throws IOException { + 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 new file mode 100644 index 0000000..dcfcfab --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/Entity.java @@ -0,0 +1,15 @@ +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 new file mode 100644 index 0000000..2a79cd2 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/ModelEntity.java @@ -0,0 +1,61 @@ +package chess.view.DDDrender.world; + +import java.io.IOException; + +import org.joml.Matrix4f; +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; + + 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, transform); + } + + public void setPosition(Vector3f position) { + this.position = position; + updateTransform(); + } + + public void setColor(Vector3f color) { + this.color = color; + } + + public void setRotation(float rotation) { + this.rotation = rotation; + updateTransform(); + } + + @Override + public void close() throws IOException { + 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; + } + +} 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..7192134 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/PieceEntity.java @@ -0,0 +1,25 @@ +package chess.view.DDDrender.world; + +import java.io.IOException; + +import org.joml.Vector3f; + +import chess.model.Piece; +import chess.view.DDDrender.loader.Piece3DModel; + +public class PieceEntity extends ModelEntity { + + private static final Piece3DModel modelLoader = new Piece3DModel(); + + private final Piece piece; + + public PieceEntity(Piece piece, Vector3f position, Vector3f color, float rotation) throws IOException { + super(modelLoader.getModel(piece), position, color, rotation); + this.piece = piece; + } + + public Piece getPiece() { + return piece; + } + +} 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..bd67f94 --- /dev/null +++ b/app/src/main/java/chess/view/DDDrender/world/World.java @@ -0,0 +1,74 @@ +package chess.view.DDDrender.world; + +import java.io.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import chess.model.Coordinate; +import chess.model.Move; +import chess.model.Piece; + +public class World implements Closeable{ + /** Renderable entity list */ + private final List entites; + + /** 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) { + for (Entity entity : entites) { + if (entity instanceof PieceEntity p) { + if (p.getPiece() == piece) + return p; + } + } + return null; + } + + public void addPiece(PieceEntity entity, Coordinate coordinate) { + addEntity(entity); + setPieceCoords(entity, coordinate); + } + + private void setPieceCoords(PieceEntity entity, Coordinate coordinate) { + pieces[coordinate.toIndex()] = entity; + } + + public void movePiece(PieceEntity entity, Move move) { + setPieceCoords(entity, move.getFinish()); + setPieceCoords(null, move.getStart()); + } + + public void ejectPiece(Coordinate coordinate) { + PieceEntity entity = getPiece(coordinate); + if (entity != null) + this.entites.remove(entity); + setPieceCoords(null, coordinate); + } + + public PieceEntity getPiece(Coordinate coordinate) { + return pieces[coordinate.toIndex()]; + } + + public void addEntity(Entity entity) { + this.entites.add(entity); + } + + public List getEntites() { + return entites; + } + + @Override + public void close() throws IOException { + for (Entity entity : entites) { + entity.close(); + } + } + +} diff --git a/app/src/main/java/chess/view/consolerender/Console.java b/app/src/main/java/chess/view/consolerender/Console.java index 101eee5..03a7359 100644 --- a/app/src/main/java/chess/view/consolerender/Console.java +++ b/app/src/main/java/chess/view/consolerender/Console.java @@ -2,9 +2,10 @@ package chess.view.consolerender; import chess.controller.Command; import chess.controller.CommandExecutor; +import chess.controller.CommandSender; import chess.controller.commands.*; import chess.controller.commands.PromoteCommand.PromoteType; -import chess.controller.event.GameListener; +import chess.controller.event.GameAdapter; import chess.model.Color; import chess.model.Coordinate; import chess.model.Move; @@ -16,7 +17,7 @@ import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -public class Console implements GameListener { +public class Console extends GameAdapter implements CommandSender { private final Scanner scanner = new Scanner(System.in); private final CommandExecutor commandExecutor; private final ConsolePieceName consolePieceName = new ConsolePieceName(); @@ -33,20 +34,6 @@ public class Console implements GameListener { this(commandExecutor, true); } - private Piece pieceAt(int x, int y) { - return pieceAt(new Coordinate(x, y)); - } - - private Command.CommandResult sendCommand(Command command) { - return this.commandExecutor.executeCommand(command); - } - - private Piece pieceAt(Coordinate coordinate) { - GetPieceAtCommand command = new GetPieceAtCommand(coordinate); - sendCommand(command); - return command.getPiece(); - } - public Coordinate stringToCoordinate(String coordinates) throws Exception { char xPos = coordinates.charAt(0); char yPos = coordinates.charAt(1); @@ -95,7 +82,7 @@ public class Console implements GameListener { } private boolean playerPickedSurrender(Color color) { - sendCommand(new SurrenderCommand(color)); + sendSurrender(color); return true; } @@ -109,7 +96,7 @@ public class Console implements GameListener { Coordinate start = stringToCoordinate(answer); System.out.println("New position: "); Coordinate end = stringToCoordinate(scanner.nextLine()); - Command.CommandResult result = sendCommand(new MoveCommand(new Move(start, end))); + Command.CommandResult result = sendMove(new Move(start, end)); return switch (Objects.requireNonNull(result)) { case Command.CommandResult.Moved, Command.CommandResult.ActionNeeded -> true; @@ -126,12 +113,7 @@ public class Console implements GameListener { try { System.out.println("Piece to examine: "); Coordinate piece = stringToCoordinate(scanner.nextLine()); - GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(piece); - if (sendCommand(movesCommand) == Command.CommandResult.NotAllowed) { - System.out.println("Not allowed."); - return false; - } - List allowedMoves = movesCommand.getDestinations(); + List allowedMoves = getPieceAllowedMoves(piece); if (allowedMoves.isEmpty()) { System.out.println("No moves allowed for this piece."); return false; @@ -201,7 +183,7 @@ public class Console implements GameListener { default -> throw new Exception(); }; valid = true; - sendCommand(new PromoteCommand(newPiece)); + sendPawnPromotion(newPiece); } catch (Exception e) { System.out.println("Invalid input!"); } @@ -218,7 +200,7 @@ public class Console implements GameListener { for (int i = 0; i < Coordinate.VALUE_MAX; i++) { string.append(8 - i).append(" "); for (int j = 0; j < Coordinate.VALUE_MAX; j++) { - Piece p = pieceAt(j, i); + Piece p = getPieceAt(new Coordinate(j, i)); if ((i + j) % 2 == 0) { string.append(Colors.LIGHT_GRAY_BACKGROUND); } else { @@ -243,7 +225,7 @@ public class Console implements GameListener { string.append(8 - i).append(" "); for (int j = 0; j < Coordinate.VALUE_MAX; j++) { Coordinate currentCell = new Coordinate(j, i); - Piece p = pieceAt(j, i); + Piece p = getPieceAt(new Coordinate(j, i)); if (moves.contains(currentCell)) { string.append(Colors.YELLOW_BACKGROUND); } else { @@ -269,10 +251,6 @@ public class Console implements GameListener { System.out.println(string); } - @Override - public void onMove(Move move, boolean captured) { - } - @Override public void onMoveNotAllowed(Move move) { System.out.println("Move not allowed."); @@ -284,9 +262,7 @@ public class Console implements GameListener { } private boolean onAskedCastling() { - GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand(); - sendCommand(cmd); - return switch (cmd.getCastlingResult()) { + return switch (getAllowedCastlings()) { case Small -> onSmallCastling(); case Big -> onBigCastling(); case Both -> onBothCastling(); @@ -334,10 +310,9 @@ public class Console implements GameListener { } @Override - public void onCastling(boolean bigCastling) {} - - @Override - public void onPawnPromoted(PromoteType promotion) {} + public CommandExecutor getCommandExecutor() { + return this.commandExecutor; + } } diff --git a/app/src/main/java/chess/view/simplerender/Window.java b/app/src/main/java/chess/view/simplerender/Window.java index 8d0695f..fb6cf87 100644 --- a/app/src/main/java/chess/view/simplerender/Window.java +++ b/app/src/main/java/chess/view/simplerender/Window.java @@ -17,24 +17,15 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; -import chess.controller.Command; -import chess.controller.Command.CommandResult; import chess.controller.CommandExecutor; -import chess.controller.commands.CastlingCommand; -import chess.controller.commands.GetAllowedCastlingsCommand; -import chess.controller.commands.GetAllowedMovesPieceCommand; -import chess.controller.commands.GetPieceAtCommand; -import chess.controller.commands.MoveCommand; -import chess.controller.commands.PromoteCommand; +import chess.controller.CommandSender; import chess.controller.commands.PromoteCommand.PromoteType; -import chess.controller.commands.UndoCommand; import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult; import chess.controller.event.GameListener; import chess.model.Coordinate; import chess.model.Move; -import chess.model.Piece; -public class Window extends JFrame implements GameListener { +public class Window extends JFrame implements GameListener, CommandSender { private final CommandExecutor commandExecutor; @@ -65,25 +56,22 @@ public class Window extends JFrame implements GameListener { }); } - private CommandResult sendCommand(Command command) { - return this.commandExecutor.executeCommand(command); - } - private Color getCellColor(int x, int y) { return ((x + y) % 2 == 1) ? Color.DARK_GRAY : Color.LIGHT_GRAY; } + @SuppressWarnings("unused") private void buildButtons(JPanel bottom) { castlingButton.addActionListener((event) -> { - sendCommand(new CastlingCommand(false)); + sendCastling(); }); - + bigCastlingButton.addActionListener((event) -> { - sendCommand(new CastlingCommand(true)); + sendBigCastling(); }); undoButton.addActionListener((event) -> { - sendCommand(new UndoCommand()); + sendUndo(); }); bottom.add(castlingButton); @@ -127,23 +115,13 @@ public class Window extends JFrame implements GameListener { updateBoard(); } - private boolean isCellEmpty(int x, int y) { - return pieceAt(x, y) == null; - } - - private Piece pieceAt(int x, int y) { - GetPieceAtCommand command = new GetPieceAtCommand(new Coordinate(x, y)); - sendCommand(command); - return command.getPiece(); - } - private void updateBoard() { PieceIcon pieceIcon = new PieceIcon(); for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { JLabel cell = this.cells[x][y]; try { - cell.setIcon(pieceIcon.getIcon(pieceAt(x, y))); + cell.setIcon(pieceIcon.getIcon(getPieceAt(x, y))); } catch (IOException e) { e.printStackTrace(); } @@ -152,11 +130,7 @@ public class Window extends JFrame implements GameListener { } private boolean previewMoves(int x, int y) { - GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(new Coordinate(x, y)); - if (sendCommand(movesCommand) == CommandResult.NotAllowed) - return false; - - List allowedMoves = movesCommand.getDestinations(); + List allowedMoves = getPieceAllowedMoves(new Coordinate(x, y)); if (allowedMoves.isEmpty()) return false; @@ -198,18 +172,17 @@ public class Window extends JFrame implements GameListener { } if (!this.lastClick.equals(new Coordinate(x, y))) { Move move = new Move(lastClick, new Coordinate(x, y)); - sendCommand(new MoveCommand(move)); + sendMove(move); } this.lastClick = null; } private void updateButtons() { - GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand(); - sendCommand(cmd); + CastlingResult castlings = getAllowedCastlings(); this.castlingButton.setEnabled( - cmd.getCastlingResult() == CastlingResult.Small || cmd.getCastlingResult() == CastlingResult.Both); + castlings == CastlingResult.Small || castlings == CastlingResult.Both); this.bigCastlingButton.setEnabled( - cmd.getCastlingResult() == CastlingResult.Big || cmd.getCastlingResult() == CastlingResult.Both); + castlings == CastlingResult.Big || castlings == CastlingResult.Both); } @Override @@ -296,7 +269,7 @@ public class Window extends JFrame implements GameListener { } if (choosedType != null) - sendCommand(new PromoteCommand(choosedType)); + sendPawnPromotion(choosedType); }); } @@ -324,4 +297,9 @@ public class Window extends JFrame implements GameListener { @Override public void onPawnPromoted(PromoteType promotion) {} + @Override + public CommandExecutor getCommandExecutor() { + return this.commandExecutor; + } + } 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 diff --git a/app/src/main/resources/fonts/comic.ttf b/app/src/main/resources/fonts/comic.ttf new file mode 100644 index 0000000..da55369 Binary files /dev/null and b/app/src/main/resources/fonts/comic.ttf differ diff --git a/app/src/test/java/chess/PgnTest.java b/app/src/test/java/chess/PgnTest.java index 246d10a..fab6960 100644 --- a/app/src/test/java/chess/PgnTest.java +++ b/app/src/test/java/chess/PgnTest.java @@ -30,6 +30,7 @@ public class PgnTest { commandExecutor.addListener(new GameAdapter() { @Override public void onGameEnd() { + // does not work with this for some reason ... synchronized (game) { game.notifyAll(); } @@ -85,17 +86,17 @@ public class PgnTest { commandExecutor.close(); - synchronized (game) { - game.notifyAll(); + synchronized (this) { + notifyAll(); } }); commandExecutor.executeCommand(new NewGameCommand()); - synchronized (game) { + synchronized (this) { try { - game.wait(); + wait(); } catch (InterruptedException e) { e.printStackTrace(); }