dev #12

Merged
Persson-dev merged 44 commits from dev into main 2025-05-17 15:05:24 +00:00
45 changed files with 1950 additions and 363 deletions

1
.gitattributes vendored
View File

@@ -6,3 +6,4 @@
# These are Windows script files and should use crlf # These are Windows script files and should use crlf
*.bat text eol=crlf *.bat text eol=crlf
*.glb filter=lfs diff=lfs merge=lfs -text *.glb filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text

4
.gitignore vendored
View File

@@ -8,5 +8,5 @@ app/bin
.vscode .vscode
audio/*.wav *.wav
app/audio/*.wav imgui.ini

View File

@@ -16,21 +16,29 @@ repositories {
mavenCentral() mavenCentral()
} }
def os = "linux";
def lwjgl_version = "3.3.6" def lwjgl_version = "3.3.6"
def lwjgl_natives = "natives-linux" def lwjgl_natives = "natives-$os"
def imgui_version = "1.87.0"
dependencies { dependencies {
// Use JUnit Jupiter for testing. // 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:$lwjgl_version"
implementation "org.lwjgl:lwjgl-opengl:$lwjgl_version" implementation "org.lwjgl:lwjgl-opengl:$lwjgl_version"
implementation "org.lwjgl:lwjgl-glfw:$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.joml:joml:1.10.8"
implementation "org.lwjgl:lwjgl::$lwjgl_natives" implementation "org.lwjgl:lwjgl::$lwjgl_natives"
implementation "org.lwjgl:lwjgl-opengl::$lwjgl_natives" implementation "org.lwjgl:lwjgl-opengl::$lwjgl_natives"
implementation "org.lwjgl:lwjgl-glfw::$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 { application {
@@ -39,24 +47,16 @@ application {
applicationName = "3DChess" applicationName = "3DChess"
} }
run {
standardInput = System.in
}
// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
jar { jar {
manifest { manifest {
attributes 'Main-Class': application.mainClass attributes 'Main-Class': application.mainClass
} }
} }
run {
standardInput = System.in
}
tasks.named('test') { tasks.named('test') {
// Use JUnit Platform for unit tests. // Use JUnit Platform for unit tests.
useJUnitPlatform() useJUnitPlatform()

View File

@@ -1,9 +1,21 @@
package chess; 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 class OpenGLMain {
public static void main(String[] args) { 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();
} }
} }

View File

@@ -1,11 +1,10 @@
package chess.ai.actions; package chess.ai.actions;
import chess.controller.Command;
import chess.controller.CommandExecutor; import chess.controller.CommandExecutor;
import chess.controller.Command.CommandResult; import chess.controller.CommandSender;
import chess.controller.commands.UndoCommand; import chess.controller.commands.UndoCommand;
public abstract class AIAction { public abstract class AIAction implements CommandSender {
private final CommandExecutor commandExecutor; private final CommandExecutor commandExecutor;
@@ -13,8 +12,9 @@ public abstract class AIAction {
this.commandExecutor = commandExecutor; this.commandExecutor = commandExecutor;
} }
protected CommandResult sendCommand(Command cmd, CommandExecutor commandExecutor) { @Override
return commandExecutor.executeCommand(cmd); public CommandExecutor getCommandExecutor() {
return this.commandExecutor;
} }
public void undoAction(CommandExecutor commandExecutor) { public void undoAction(CommandExecutor commandExecutor) {

View File

@@ -4,15 +4,9 @@ import java.util.List;
import chess.ai.actions.AIAction; import chess.ai.actions.AIAction;
import chess.ai.actions.AIActions; import chess.ai.actions.AIActions;
import chess.controller.Command;
import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor; import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand; import chess.controller.CommandSender;
import chess.controller.commands.MoveCommand;
import chess.controller.commands.NewGameCommand;
import chess.controller.commands.PromoteCommand;
import chess.controller.commands.PromoteCommand.PromoteType; import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.commands.UndoCommand;
import chess.controller.event.EmptyGameDispatcher; import chess.controller.event.EmptyGameDispatcher;
import chess.controller.event.GameAdapter; import chess.controller.event.GameAdapter;
import chess.model.ChessBoard; import chess.model.ChessBoard;
@@ -21,7 +15,7 @@ import chess.model.Game;
import chess.model.Move; import chess.model.Move;
import chess.model.PermissiveGame; import chess.model.PermissiveGame;
public class GameSimulation extends GameAdapter { public class GameSimulation extends GameAdapter implements CommandSender {
private final CommandExecutor simulation; private final CommandExecutor simulation;
private final Game gameSimulation; private final Game gameSimulation;
@@ -31,48 +25,33 @@ public class GameSimulation extends GameAdapter {
this.simulation = new CommandExecutor(gameSimulation, new EmptyGameDispatcher()); 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 @Override
public void onPawnPromoted(PromoteType promotion) { public void onPawnPromoted(PromoteType promotion) {
sendCommand(new PromoteCommand(promotion)); sendPawnPromotion(promotion);
} }
@Override @Override
public void onCastling(boolean bigCastling) { public void onCastling(boolean bigCastling) {
sendCommand(new CastlingCommand(bigCastling)); if (bigCastling)
sendBigCastling();
else
sendCastling();
} }
@Override @Override
public void onMove(Move move, boolean captured) { public void onMove(Move move, boolean captured) {
sendCommand(new MoveCommand(move)); sendMove(move);
} }
@Override @Override
public void onGameStart() { public void onGameStart() {
sendCommand(new NewGameCommand()); sendStartGame();
} }
@Override @Override
public void onPlayerTurn(Color color, boolean undone) { public void onPlayerTurn(Color color, boolean undone) {
if (undone) if (undone)
sendCommand(new UndoCommand()); sendUndo();
} }
public CommandExecutor getCommandExecutor() { public CommandExecutor getCommandExecutor() {

View File

@@ -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<Move> getPlayerMoves() {
GetPlayerMovesCommand cmd = new GetPlayerMovesCommand();
sendCommand(cmd);
return cmd.getMoves();
}
default List<Coordinate> 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());
}
}

View File

@@ -1,23 +1,29 @@
package chess.view.DDDrender; package chess.view.DDDrender;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector2f;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.joml.Vector4f;
public class Camera { public class Camera {
public static final float fov = 70.0f; 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 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 Vector3f pos;
private float yaw = 0.0f;
private float pitch = 0.0f;
public Camera() { public Camera() {
this.pos = new Vector3f(0, 2.0f, 0); this.pos = new Vector3f(0.0f, camHeight, 0.0f);
setRotation(0.0f, -3.14150f / 2.0f); setRotateAngle(0.0f);
} }
public void move(float x, float y) { public void move(float x, float y) {
@@ -25,104 +31,56 @@ public class Camera {
this.pos.y += y; this.pos.y += y;
} }
public void rotate(float yaw, float pitch) { public void setRotateAngle(float angle) {
this.yaw += yaw; this.angle = angle;
this.pitch += pitch; 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() { public Vector3f getPos() {
return pos; return pos;
} }
public float getYaw() {
return yaw;
}
public float getPitch() {
return pitch;
}
public float getFov() { public float getFov() {
return fov; return fov;
} }
public void setX(float x) { public void setAspectRatio(float aspectRatio) {
this.pos.x = x; this.aspectRatio = aspectRatio;
} }
public void setY(float y) { public Matrix4f getPerspectiveMatrix() {
this.pos.y = y; return new Matrix4f().perspective((float) (Math.toRadians(fov)), aspectRatio, zNear, zFar);
} }
public void setZ(float z) { public Matrix4f getViewMatrix() {
this.pos.z = z; return new Matrix4f().lookAt(pos, center, up);
} }
public void setYaw(float yaw) { public Vector2f getCursorWorldFloorPos(Vector2f screenPos, int windowWidth, int windowHeight) {
this.yaw = yaw; 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) { Vector4f rayClip = new Vector4f(relativeX, relativeY, -1.0f, 1.0f);
this.pitch = pitch;
}
public void reset() { Vector4f rayEye = getPerspectiveMatrix().invert().transform(rayClip);
resetPosition();
resetRotation();
}
public void resetPosition() { rayEye = new Vector4f(rayEye.x, rayEye.y, -1.0f, 0.0f);
pos = new Vector3f(0.0f, 0.0f, 0.0f);
}
public void resetRotation() { Vector4f rayWorld = getViewMatrix().invert().transform(rayEye);
yaw = 0.0f;
pitch = 0.0f;
}
public void moveForward(float distance) { float lambda = -this.pos.y / rayWorld.y;
pos.x += distance * (float) Math.cos(yaw);
pos.y += distance * (float) Math.sin(yaw);
}
public void moveRight(float distance) { return new Vector2f(lambda * rayWorld.x + this.pos.x, lambda *
pos.x += distance * (float) Math.cos(yaw); rayWorld.z + this.pos.z);
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));
} }
} }

View File

@@ -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<VertexArray> vaos;
public DDDModel(List<VertexArray> vaos) {
this.vaos = vaos;
}
public List<VertexArray> getVaos() {
return vaos;
}
@Override
public void close() throws IOException {
for (VertexArray vertexArray : vaos) {
vertexArray.close();
}
}
}

View File

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

View File

@@ -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<Coordinate> 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<Coordinate> 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<Coordinate> 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<Coordinate> 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<Coordinate> 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<Coordinate> 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<Float, Float> 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<Float> 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<Float> 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");
}
}

View File

@@ -2,123 +2,75 @@ package chess.view.DDDrender;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; 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.joml.Vector3f;
import org.lwjgl.opengl.GL30; 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.BoardShader;
import chess.view.DDDrender.shader.PieceShader;
import chess.view.DDDrender.shader.ShaderProgram;
public class Renderer { public class Renderer implements Closeable {
private BoardShader shader; private BoardShader boardShader;
private VertexArray vao; private PieceShader pieceShader;
private FrameBuffer frameBuffer;
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() { public Renderer() {
this.shader = new BoardShader(); this.boardShader = new BoardShader();
this.pieceShader = new PieceShader();
} }
public void Init() { public void Init(float windowWidth, float widowHeight) {
shader.LoadShader(); boardShader.LoadShader();
InitBoard(); pieceShader.LoadShader();
this.frameBuffer = new FrameBuffer((int) windowWidth, (int) (widowHeight * 8.0f / 10.0f));
} }
private float[] GetBoardPositions() { public void Update(Camera cam) {
float[] positions = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3]; this.boardShader.Start();
for (int i = 0; i < BOARD_WIDTH; i++) { this.boardShader.SetCamMatrix(cam);
for (int j = 0; j < BOARD_HEIGHT; j++) { this.pieceShader.Start();
float x = i / (float) BOARD_WIDTH; this.pieceShader.SetCamMatrix(cam);
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() { public void Render(DDDModel model, Vector3f color, Matrix4f transform) {
float[] colors = new float[BOARD_SIZE * SQUARE_VERTEX_COUNT * 3]; this.pieceShader.Start();
for (int i = 0; i < BOARD_WIDTH; i++) { this.pieceShader.setModelColor(color);
for (int j = 0; j < BOARD_HEIGHT; j++) { this.pieceShader.setModelTransform(transform);
Vector3f color; Render(model);
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() { public void Render(DDDModel model) {
int[] indices = new int[BOARD_SIZE * 6]; for (int i = 0; i < model.getVaos().size(); i++) {
for (int i = 0; i < BOARD_SIZE; i++) { VertexArray vao = model.getVaos().get(i);
indices[i * 6] = i * 4; RenderVao(this.pieceShader, vao);
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() { public void RenderVao(ShaderProgram shader, VertexArray vertexArray) {
ElementBuffer eBuffer = new ElementBuffer(GetBoardIndicies()); shader.Start();
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();
vertexArray.Bind(); vertexArray.Bind();
GL30.glDrawElements(GL30.GL_TRIANGLES, vertexArray.GetVertexCount(), GL_UNSIGNED_INT, 0); GL30.glDrawElements(GL30.GL_TRIANGLES, vertexArray.GetVertexCount(), GL_UNSIGNED_INT, 0);
vertexArray.Unbind(); vertexArray.Unbind();
} }
public FrameBuffer getFrameBuffer() {
return frameBuffer;
}
public BoardShader getBoardShader() {
return boardShader;
}
@Override
public void close() throws IOException {
this.boardShader.close();
this.pieceShader.close();
}
} }

View File

@@ -1,11 +1,35 @@
package chess.view.DDDrender; package chess.view.DDDrender;
import common.AssetManager;
import common.Signal0;
import org.joml.Vector2f;
import org.lwjgl.*; import org.lwjgl.*;
import org.lwjgl.glfw.*; import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*; import org.lwjgl.opengl.*;
import org.lwjgl.system.*; 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.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.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*; 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.MemoryStack.*;
import static org.lwjgl.system.MemoryUtil.*; import static org.lwjgl.system.MemoryUtil.*;
public class Window { public class Window implements Closeable {
// The window handle // The window handle
private long window; private long window;
private Renderer renderer; private final ImGuiImplGl3 implGl3 = new ImGuiImplGl3();
private Camera cam; private final ImGuiImplGlfw implGlfw = new ImGuiImplGlfw();
public Window() { private Renderer renderer;
private final Camera cam;
private final World world;
private final Queue<Runnable> tasks;
private final List<Consumer<Float>> regularTasks;
private Coordinate lastCell = null;
public final Signal1<Coordinate> OnCellClick = new Signal1<>();
public final Signal1<Coordinate> OnCellEnter = new Signal1<>();
public final Signal1<Coordinate> 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.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) { public void addRegularTask(Consumer<Float> task) {
new Window().run(); this.regularTasks.add(task);
}
public void removeRegularTask(Consumer<Float> 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() { public void run() {
@@ -43,6 +99,28 @@ public class Window {
// Terminate GLFW and free the error callback // Terminate GLFW and free the error callback
glfwTerminate(); glfwTerminate();
glfwSetErrorCallback(null).free(); 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() { private void init() {
@@ -80,10 +158,19 @@ public class Window {
window, window,
(vidmode.width() - pWidth.get(0)) / 2, (vidmode.width() - pWidth.get(0)) / 2,
(vidmode.height() - pHeight.get(0)) / 2); (vidmode.height() - pHeight.get(0)) / 2);
} // the stack frame is popped automatically
glfwSetWindowSize(window, vidmode.width(), vidmode.height());
// Make the OpenGL context current // Make the OpenGL context current
glfwMakeContextCurrent(window); glfwMakeContextCurrent(window);
GL.createCapabilities();
initImGui();
renderer.Init(vidmode.width(), vidmode.height());
} // the stack frame is popped automatically
// Enable v-sync // Enable v-sync
glfwSwapInterval(1); glfwSwapInterval(1);
@@ -91,32 +178,117 @@ public class Window {
glfwShowWindow(window); glfwShowWindow(window);
} }
private void render() { private void render(float delta, float aspectRatio) {
cam.rotate(0.01f, 0.01f); final FrameBuffer frameBuffer = this.renderer.getFrameBuffer();
renderer.Render(cam);
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<Float> 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() { 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 // 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); 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 // Run the rendering loop until the user has attempted to close
// the window or has pressed the ESCAPE key. // the window or has pressed the ESCAPE key.
while (!glfwWindowShouldClose(window)) { 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 glfwSwapBuffers(window); // swap the color buffers
@@ -124,16 +296,17 @@ public class Window {
// invoked during this call. // invoked during this call.
glfwPollEvents(); glfwPollEvents();
try (MemoryStack stack = stackPush()) { executeTasks(ImGui.getIO().getDeltaTime());
IntBuffer pWidth = stack.mallocInt(1); // int*
IntBuffer pHeight = stack.mallocInt(1); // int*
// Get the window size passed to glfwCreateWindow glfwGetWindowSize(window, width, height);
glfwGetWindowSize(window, pWidth, pHeight); 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();
}
} }

View File

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

View File

@@ -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<Float> 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<Integer> 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<Float> positions = new ArrayList<>();
List<Float> normals = new ArrayList<>();
List<Integer> 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<VertexArray> 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<VertexArray> vertecies = new ArrayList<>();
processNode(scene.mRootNode(), scene, vertecies);
MemoryUtil.memFree(data);
return new DDDModel(vertecies);
}
}

View File

@@ -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<String> {
private static final String basePath = "3d/";
private static final Map<String, DDDModel> 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";
}
}

View File

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

View File

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

View File

@@ -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<VertexBuffer> vertexBuffers;
public VertexArray(ElementBuffer elementBuffer) {
this.id = GL30.glGenVertexArrays();
this.elementBuffer = elementBuffer;
this.vertexBuffers = new ArrayList<VertexBuffer>();
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<VertexBuffer> getVertexBuffers() {
return vertexBuffers;
}
@Override
public void close() throws IOException {
GL30.glDeleteBuffers(this.id);
for (VertexBuffer vertexBuffer : vertexBuffers) {
vertexBuffer.close();
}
elementBuffer.close();
}
}

View File

@@ -0,0 +1,5 @@
package chess.view.DDDrender.opengl;
public record VertexAttribPointer(int index, int size, int offset) {
}

View File

@@ -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<VertexAttribPointer> vertexAttribs;
public VertexBuffer(float[] data, int stride) {
this.id = GL30.glGenBuffers();
this.dataStride = stride;
this.vertexAttribs = new ArrayList<VertexAttribPointer>();
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);
}
}

View File

@@ -1,6 +1,6 @@
package chess.view.DDDrender.shader; package chess.view.DDDrender.shader;
import org.joml.Matrix4f; import chess.view.DDDrender.Camera;
public class BoardShader extends ShaderProgram { public class BoardShader extends ShaderProgram {
@@ -10,14 +10,31 @@ public class BoardShader extends ShaderProgram {
layout(location = 0) in vec3 position; layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color; 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; flat out vec3 pass_color;
out vec3 toLightVector;
out vec3 toCameraVector;
out vec3 surfaceNormal;
void main(void){ 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; pass_color = color;
} }
"""; """;
private static String fragmentShader = """ private static String fragmentShader = """
@@ -25,14 +42,44 @@ public class BoardShader extends ShaderProgram {
flat in vec3 pass_color; 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){ 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() { public BoardShader() {
@@ -44,10 +91,12 @@ public class BoardShader extends ShaderProgram {
@Override @Override
protected void GetAllUniformLocation() { protected void GetAllUniformLocation() {
location_CamMatrix = GetUniformLocation("camMatrix"); location_ProjectionMatrix = GetUniformLocation("projectionMatrix");
location_ViewMatrix = GetUniformLocation("viewMatrix");
} }
public void SetCamMatrix(Matrix4f mat) { public void SetCamMatrix(Camera camera) {
LoadMat4(location_CamMatrix, mat); LoadMat4(location_ProjectionMatrix, camera.getPerspectiveMatrix());
LoadMat4(location_ViewMatrix, camera.getViewMatrix());
} }
} }

View File

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

View File

@@ -1,5 +1,7 @@
package chess.view.DDDrender.shader; package chess.view.DDDrender.shader;
import java.io.Closeable;
import java.io.IOException;
import java.nio.FloatBuffer; import java.nio.FloatBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
@@ -9,7 +11,7 @@ import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
public abstract class ShaderProgram { public abstract class ShaderProgram implements Closeable {
private int programId; private int programId;
private int vertexShaderId; private int vertexShaderId;
private int fragmentShaderId; private int fragmentShaderId;
@@ -56,8 +58,8 @@ public abstract class ShaderProgram {
if (compileSuccesful.get() != 1) { if (compileSuccesful.get() != 1) {
System.out.println("Shader did not compile !"); System.out.println("Shader did not compile !");
System.err.println(GL30.glGetShaderInfoLog(shaderId));
return -1; return -1;
} }
return shaderId; return shaderId;
@@ -68,7 +70,7 @@ public abstract class ShaderProgram {
protected int GetUniformLocation(String uniformName) { protected int GetUniformLocation(String uniformName) {
int location = GL30.glGetUniformLocation(programId, uniformName); int location = GL30.glGetUniformLocation(programId, uniformName);
if (location == -1) { if (location == -1) {
System.out.println("Uniform value not found !"); System.out.println("Uniform value \"" + uniformName + "\" not found !");
} }
return location; return location;
} }
@@ -91,4 +93,11 @@ public abstract class ShaderProgram {
GL30.glUniformMatrix4fv(location, false, buffer); GL30.glUniformMatrix4fv(location, false, buffer);
} }
} }
@Override
public void close() throws IOException {
GL30.glDeleteShader(vertexShaderId);
GL30.glDeleteShader(fragmentShaderId);
GL30.glDeleteProgram(programId);
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Entity> 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<Entity> getEntites() {
return entites;
}
@Override
public void close() throws IOException {
for (Entity entity : entites) {
entity.close();
}
}
}

View File

@@ -2,9 +2,10 @@ package chess.view.consolerender;
import chess.controller.Command; import chess.controller.Command;
import chess.controller.CommandExecutor; import chess.controller.CommandExecutor;
import chess.controller.CommandSender;
import chess.controller.commands.*; import chess.controller.commands.*;
import chess.controller.commands.PromoteCommand.PromoteType; import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.event.GameListener; import chess.controller.event.GameAdapter;
import chess.model.Color; import chess.model.Color;
import chess.model.Coordinate; import chess.model.Coordinate;
import chess.model.Move; import chess.model.Move;
@@ -16,7 +17,7 @@ import java.util.Scanner;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; 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 Scanner scanner = new Scanner(System.in);
private final CommandExecutor commandExecutor; private final CommandExecutor commandExecutor;
private final ConsolePieceName consolePieceName = new ConsolePieceName(); private final ConsolePieceName consolePieceName = new ConsolePieceName();
@@ -33,20 +34,6 @@ public class Console implements GameListener {
this(commandExecutor, true); 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 { public Coordinate stringToCoordinate(String coordinates) throws Exception {
char xPos = coordinates.charAt(0); char xPos = coordinates.charAt(0);
char yPos = coordinates.charAt(1); char yPos = coordinates.charAt(1);
@@ -95,7 +82,7 @@ public class Console implements GameListener {
} }
private boolean playerPickedSurrender(Color color) { private boolean playerPickedSurrender(Color color) {
sendCommand(new SurrenderCommand(color)); sendSurrender(color);
return true; return true;
} }
@@ -109,7 +96,7 @@ public class Console implements GameListener {
Coordinate start = stringToCoordinate(answer); Coordinate start = stringToCoordinate(answer);
System.out.println("New position: "); System.out.println("New position: ");
Coordinate end = stringToCoordinate(scanner.nextLine()); 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)) { return switch (Objects.requireNonNull(result)) {
case Command.CommandResult.Moved, Command.CommandResult.ActionNeeded -> true; case Command.CommandResult.Moved, Command.CommandResult.ActionNeeded -> true;
@@ -126,12 +113,7 @@ public class Console implements GameListener {
try { try {
System.out.println("Piece to examine: "); System.out.println("Piece to examine: ");
Coordinate piece = stringToCoordinate(scanner.nextLine()); Coordinate piece = stringToCoordinate(scanner.nextLine());
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(piece); List<Coordinate> allowedMoves = getPieceAllowedMoves(piece);
if (sendCommand(movesCommand) == Command.CommandResult.NotAllowed) {
System.out.println("Not allowed.");
return false;
}
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty()) { if (allowedMoves.isEmpty()) {
System.out.println("No moves allowed for this piece."); System.out.println("No moves allowed for this piece.");
return false; return false;
@@ -201,7 +183,7 @@ public class Console implements GameListener {
default -> throw new Exception(); default -> throw new Exception();
}; };
valid = true; valid = true;
sendCommand(new PromoteCommand(newPiece)); sendPawnPromotion(newPiece);
} catch (Exception e) { } catch (Exception e) {
System.out.println("Invalid input!"); System.out.println("Invalid input!");
} }
@@ -218,7 +200,7 @@ public class Console implements GameListener {
for (int i = 0; i < Coordinate.VALUE_MAX; i++) { for (int i = 0; i < Coordinate.VALUE_MAX; i++) {
string.append(8 - i).append(" "); string.append(8 - i).append(" ");
for (int j = 0; j < Coordinate.VALUE_MAX; j++) { 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) { if ((i + j) % 2 == 0) {
string.append(Colors.LIGHT_GRAY_BACKGROUND); string.append(Colors.LIGHT_GRAY_BACKGROUND);
} else { } else {
@@ -243,7 +225,7 @@ public class Console implements GameListener {
string.append(8 - i).append(" "); string.append(8 - i).append(" ");
for (int j = 0; j < Coordinate.VALUE_MAX; j++) { for (int j = 0; j < Coordinate.VALUE_MAX; j++) {
Coordinate currentCell = new Coordinate(j, i); Coordinate currentCell = new Coordinate(j, i);
Piece p = pieceAt(j, i); Piece p = getPieceAt(new Coordinate(j, i));
if (moves.contains(currentCell)) { if (moves.contains(currentCell)) {
string.append(Colors.YELLOW_BACKGROUND); string.append(Colors.YELLOW_BACKGROUND);
} else { } else {
@@ -269,10 +251,6 @@ public class Console implements GameListener {
System.out.println(string); System.out.println(string);
} }
@Override
public void onMove(Move move, boolean captured) {
}
@Override @Override
public void onMoveNotAllowed(Move move) { public void onMoveNotAllowed(Move move) {
System.out.println("Move not allowed."); System.out.println("Move not allowed.");
@@ -284,9 +262,7 @@ public class Console implements GameListener {
} }
private boolean onAskedCastling() { private boolean onAskedCastling() {
GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand(); return switch (getAllowedCastlings()) {
sendCommand(cmd);
return switch (cmd.getCastlingResult()) {
case Small -> onSmallCastling(); case Small -> onSmallCastling();
case Big -> onBigCastling(); case Big -> onBigCastling();
case Both -> onBothCastling(); case Both -> onBothCastling();
@@ -334,10 +310,9 @@ public class Console implements GameListener {
} }
@Override @Override
public void onCastling(boolean bigCastling) {} public CommandExecutor getCommandExecutor() {
return this.commandExecutor;
@Override }
public void onPawnPromoted(PromoteType promotion) {}
} }

View File

@@ -17,24 +17,15 @@ import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import chess.controller.Command;
import chess.controller.Command.CommandResult;
import chess.controller.CommandExecutor; import chess.controller.CommandExecutor;
import chess.controller.commands.CastlingCommand; import chess.controller.CommandSender;
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.commands.PromoteCommand.PromoteType; import chess.controller.commands.PromoteCommand.PromoteType;
import chess.controller.commands.UndoCommand;
import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult; import chess.controller.commands.GetAllowedCastlingsCommand.CastlingResult;
import chess.controller.event.GameListener; import chess.controller.event.GameListener;
import chess.model.Coordinate; import chess.model.Coordinate;
import chess.model.Move; 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; 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) { private Color getCellColor(int x, int y) {
return ((x + y) % 2 == 1) ? Color.DARK_GRAY : Color.LIGHT_GRAY; return ((x + y) % 2 == 1) ? Color.DARK_GRAY : Color.LIGHT_GRAY;
} }
@SuppressWarnings("unused")
private void buildButtons(JPanel bottom) { private void buildButtons(JPanel bottom) {
castlingButton.addActionListener((event) -> { castlingButton.addActionListener((event) -> {
sendCommand(new CastlingCommand(false)); sendCastling();
}); });
bigCastlingButton.addActionListener((event) -> { bigCastlingButton.addActionListener((event) -> {
sendCommand(new CastlingCommand(true)); sendBigCastling();
}); });
undoButton.addActionListener((event) -> { undoButton.addActionListener((event) -> {
sendCommand(new UndoCommand()); sendUndo();
}); });
bottom.add(castlingButton); bottom.add(castlingButton);
@@ -127,23 +115,13 @@ public class Window extends JFrame implements GameListener {
updateBoard(); 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() { private void updateBoard() {
PieceIcon pieceIcon = new PieceIcon(); PieceIcon pieceIcon = new PieceIcon();
for (int y = 0; y < 8; y++) { for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) { for (int x = 0; x < 8; x++) {
JLabel cell = this.cells[x][y]; JLabel cell = this.cells[x][y];
try { try {
cell.setIcon(pieceIcon.getIcon(pieceAt(x, y))); cell.setIcon(pieceIcon.getIcon(getPieceAt(x, y)));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -152,11 +130,7 @@ public class Window extends JFrame implements GameListener {
} }
private boolean previewMoves(int x, int y) { private boolean previewMoves(int x, int y) {
GetAllowedMovesPieceCommand movesCommand = new GetAllowedMovesPieceCommand(new Coordinate(x, y)); List<Coordinate> allowedMoves = getPieceAllowedMoves(new Coordinate(x, y));
if (sendCommand(movesCommand) == CommandResult.NotAllowed)
return false;
List<Coordinate> allowedMoves = movesCommand.getDestinations();
if (allowedMoves.isEmpty()) if (allowedMoves.isEmpty())
return false; return false;
@@ -198,18 +172,17 @@ public class Window extends JFrame implements GameListener {
} }
if (!this.lastClick.equals(new Coordinate(x, y))) { if (!this.lastClick.equals(new Coordinate(x, y))) {
Move move = new Move(lastClick, new Coordinate(x, y)); Move move = new Move(lastClick, new Coordinate(x, y));
sendCommand(new MoveCommand(move)); sendMove(move);
} }
this.lastClick = null; this.lastClick = null;
} }
private void updateButtons() { private void updateButtons() {
GetAllowedCastlingsCommand cmd = new GetAllowedCastlingsCommand(); CastlingResult castlings = getAllowedCastlings();
sendCommand(cmd);
this.castlingButton.setEnabled( this.castlingButton.setEnabled(
cmd.getCastlingResult() == CastlingResult.Small || cmd.getCastlingResult() == CastlingResult.Both); castlings == CastlingResult.Small || castlings == CastlingResult.Both);
this.bigCastlingButton.setEnabled( this.bigCastlingButton.setEnabled(
cmd.getCastlingResult() == CastlingResult.Big || cmd.getCastlingResult() == CastlingResult.Both); castlings == CastlingResult.Big || castlings == CastlingResult.Both);
} }
@Override @Override
@@ -296,7 +269,7 @@ public class Window extends JFrame implements GameListener {
} }
if (choosedType != null) if (choosedType != null)
sendCommand(new PromoteCommand(choosedType)); sendPawnPromotion(choosedType);
}); });
} }
@@ -324,4 +297,9 @@ public class Window extends JFrame implements GameListener {
@Override @Override
public void onPawnPromoted(PromoteType promotion) {} public void onPawnPromoted(PromoteType promotion) {}
@Override
public CommandExecutor getCommandExecutor() {
return this.commandExecutor;
}
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -30,6 +30,7 @@ public class PgnTest {
commandExecutor.addListener(new GameAdapter() { commandExecutor.addListener(new GameAdapter() {
@Override @Override
public void onGameEnd() { public void onGameEnd() {
// does not work with this for some reason ...
synchronized (game) { synchronized (game) {
game.notifyAll(); game.notifyAll();
} }
@@ -85,17 +86,17 @@ public class PgnTest {
commandExecutor.close(); commandExecutor.close();
synchronized (game) { synchronized (this) {
game.notifyAll(); notifyAll();
} }
}); });
commandExecutor.executeCommand(new NewGameCommand()); commandExecutor.executeCommand(new NewGameCommand());
synchronized (game) { synchronized (this) {
try { try {
game.wait(); wait();
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }