Added block breaking stuff.
This commit is contained in:
parent
c9b325da3a
commit
2ebf5ad1bf
|
@ -1,15 +1,20 @@
|
||||||
package nl.andrewl.aos2_client;
|
package nl.andrewl.aos2_client;
|
||||||
|
|
||||||
|
import nl.andrewl.aos2_client.control.InputHandler;
|
||||||
import nl.andrewl.aos2_client.control.PlayerInputKeyCallback;
|
import nl.andrewl.aos2_client.control.PlayerInputKeyCallback;
|
||||||
|
import nl.andrewl.aos2_client.control.PlayerInputMouseClickCallback;
|
||||||
import nl.andrewl.aos2_client.control.PlayerViewCursorCallback;
|
import nl.andrewl.aos2_client.control.PlayerViewCursorCallback;
|
||||||
import nl.andrewl.aos2_client.render.GameRenderer;
|
import nl.andrewl.aos2_client.render.GameRenderer;
|
||||||
import nl.andrewl.aos_core.model.Chunk;
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
import nl.andrewl.aos_core.model.ColorPalette;
|
import nl.andrewl.aos_core.model.ColorPalette;
|
||||||
import nl.andrewl.aos_core.model.World;
|
import nl.andrewl.aos_core.model.World;
|
||||||
import nl.andrewl.aos_core.net.ChunkDataMessage;
|
import nl.andrewl.aos_core.net.ChunkDataMessage;
|
||||||
|
import nl.andrewl.aos_core.net.ChunkHashMessage;
|
||||||
import nl.andrewl.aos_core.net.WorldInfoMessage;
|
import nl.andrewl.aos_core.net.WorldInfoMessage;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ChunkUpdateMessage;
|
||||||
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
import org.joml.Vector3i;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -25,6 +30,7 @@ public class Client implements Runnable {
|
||||||
private final String username;
|
private final String username;
|
||||||
|
|
||||||
private final CommunicationHandler communicationHandler;
|
private final CommunicationHandler communicationHandler;
|
||||||
|
private final InputHandler inputHandler;
|
||||||
private final GameRenderer gameRenderer;
|
private final GameRenderer gameRenderer;
|
||||||
|
|
||||||
private int clientId;
|
private int clientId;
|
||||||
|
@ -35,6 +41,7 @@ public class Client implements Runnable {
|
||||||
this.serverPort = serverPort;
|
this.serverPort = serverPort;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.communicationHandler = new CommunicationHandler(this);
|
this.communicationHandler = new CommunicationHandler(this);
|
||||||
|
this.inputHandler = new InputHandler(communicationHandler);
|
||||||
this.world = new ClientWorld();
|
this.world = new ClientWorld();
|
||||||
this.gameRenderer = new GameRenderer(world);
|
this.gameRenderer = new GameRenderer(world);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +59,8 @@ public class Client implements Runnable {
|
||||||
|
|
||||||
gameRenderer.setupWindow(
|
gameRenderer.setupWindow(
|
||||||
new PlayerViewCursorCallback(gameRenderer.getCamera(), communicationHandler),
|
new PlayerViewCursorCallback(gameRenderer.getCamera(), communicationHandler),
|
||||||
new PlayerInputKeyCallback(communicationHandler)
|
new PlayerInputKeyCallback(inputHandler),
|
||||||
|
new PlayerInputMouseClickCallback(inputHandler)
|
||||||
);
|
);
|
||||||
|
|
||||||
long lastFrameAt = System.currentTimeMillis();
|
long lastFrameAt = System.currentTimeMillis();
|
||||||
|
@ -82,11 +90,22 @@ public class Client implements Runnable {
|
||||||
if (msg instanceof ChunkDataMessage chunkDataMessage) {
|
if (msg instanceof ChunkDataMessage chunkDataMessage) {
|
||||||
Chunk chunk = chunkDataMessage.toChunk();
|
Chunk chunk = chunkDataMessage.toChunk();
|
||||||
world.addChunk(chunk);
|
world.addChunk(chunk);
|
||||||
gameRenderer.getChunkRenderer().addChunkMesh(chunk);
|
gameRenderer.getChunkRenderer().queueChunkMesh(chunk);
|
||||||
|
}
|
||||||
|
if (msg instanceof ChunkUpdateMessage u) {
|
||||||
|
Vector3i chunkPos = new Vector3i(u.cx(), u.cy(), u.cz());
|
||||||
|
Chunk chunk = world.getChunkAt(chunkPos);
|
||||||
|
System.out.println(u);
|
||||||
|
if (chunk != null) {
|
||||||
|
chunk.setBlockAt(u.lx(), u.ly(), u.lz(), u.newBlock());
|
||||||
|
gameRenderer.getChunkRenderer().queueChunkMesh(chunk);
|
||||||
|
} else {
|
||||||
|
communicationHandler.sendMessage(new ChunkHashMessage(u.cx(), u.cy(), u.cz(), -1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (msg instanceof PlayerUpdateMessage playerUpdate) {
|
if (msg instanceof PlayerUpdateMessage playerUpdate) {
|
||||||
if (playerUpdate.clientId() == clientId) {
|
if (playerUpdate.clientId() == clientId) {
|
||||||
float eyeHeight = playerUpdate.crouching() ? 1.1f : 1.7f;
|
float eyeHeight = playerUpdate.crouching() ? 1.3f : 1.7f;
|
||||||
gameRenderer.getCamera().setPosition(playerUpdate.px(), playerUpdate.py() + eyeHeight, playerUpdate.pz());
|
gameRenderer.getCamera().setPosition(playerUpdate.px(), playerUpdate.py() + eyeHeight, playerUpdate.pz());
|
||||||
gameRenderer.getCamera().setVelocity(playerUpdate.vx(), playerUpdate.vy(), playerUpdate.vz());
|
gameRenderer.getCamera().setVelocity(playerUpdate.vx(), playerUpdate.vy(), playerUpdate.vz());
|
||||||
// TODO: Unload far away chunks and request close chunks we don't have.
|
// TODO: Unload far away chunks and request close chunks we don't have.
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package nl.andrewl.aos2_client.control;
|
||||||
|
|
||||||
|
import nl.andrewl.aos2_client.CommunicationHandler;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
||||||
|
|
||||||
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class which manages the player's input, and sending it to the server.
|
||||||
|
*/
|
||||||
|
public class InputHandler {
|
||||||
|
private final CommunicationHandler comm;
|
||||||
|
|
||||||
|
private ClientInputState lastInputState = null;
|
||||||
|
|
||||||
|
public InputHandler(CommunicationHandler comm) {
|
||||||
|
this.comm = comm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateInputState(long window) {
|
||||||
|
// TODO: Allow customized keybindings.
|
||||||
|
ClientInputState currentInputState = new ClientInputState(
|
||||||
|
comm.getClientId(),
|
||||||
|
glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS,
|
||||||
|
glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS,
|
||||||
|
glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS
|
||||||
|
);
|
||||||
|
if (!currentInputState.equals(lastInputState)) {
|
||||||
|
comm.sendDatagramPacket(currentInputState);
|
||||||
|
lastInputState = currentInputState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,14 @@
|
||||||
package nl.andrewl.aos2_client.control;
|
package nl.andrewl.aos2_client.control;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.CommunicationHandler;
|
|
||||||
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
|
||||||
import org.lwjgl.glfw.GLFWKeyCallbackI;
|
import org.lwjgl.glfw.GLFWKeyCallbackI;
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
|
|
||||||
public class PlayerInputKeyCallback implements GLFWKeyCallbackI {
|
public class PlayerInputKeyCallback implements GLFWKeyCallbackI {
|
||||||
private ClientInputState lastInputState = null;
|
private final InputHandler inputHandler;
|
||||||
private final CommunicationHandler comm;
|
|
||||||
|
|
||||||
public PlayerInputKeyCallback(CommunicationHandler comm) {
|
public PlayerInputKeyCallback(InputHandler inputHandler) {
|
||||||
this.comm = comm;
|
this.inputHandler = inputHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -19,20 +16,6 @@ public class PlayerInputKeyCallback implements GLFWKeyCallbackI {
|
||||||
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
|
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
|
||||||
glfwSetWindowShouldClose(window, true);
|
glfwSetWindowShouldClose(window, true);
|
||||||
}
|
}
|
||||||
|
inputHandler.updateInputState(window);
|
||||||
ClientInputState inputState = new ClientInputState(
|
|
||||||
comm.getClientId(),
|
|
||||||
glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS,
|
|
||||||
glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS,
|
|
||||||
glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS,
|
|
||||||
glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS,
|
|
||||||
glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS,
|
|
||||||
glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS,
|
|
||||||
glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS
|
|
||||||
);
|
|
||||||
if (!inputState.equals(lastInputState)) {
|
|
||||||
comm.sendDatagramPacket(inputState);
|
|
||||||
lastInputState = inputState;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package nl.andrewl.aos2_client.control;
|
||||||
|
|
||||||
|
import org.lwjgl.glfw.GLFWMouseButtonCallbackI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback that's called when the player clicks with their mouse.
|
||||||
|
*/
|
||||||
|
public class PlayerInputMouseClickCallback implements GLFWMouseButtonCallbackI {
|
||||||
|
private final InputHandler inputHandler;
|
||||||
|
|
||||||
|
public PlayerInputMouseClickCallback(InputHandler inputHandler) {
|
||||||
|
this.inputHandler = inputHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(long window, int button, int action, int mods) {
|
||||||
|
System.out.println("Click: " + button);
|
||||||
|
inputHandler.updateInputState(window);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,10 @@ import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.glfwGetCursorPos;
|
import static org.lwjgl.glfw.GLFW.glfwGetCursorPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback that's called when the player's cursor position updates. This means
|
||||||
|
* the player is looking around.
|
||||||
|
*/
|
||||||
public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI {
|
public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI {
|
||||||
/**
|
/**
|
||||||
* The number of milliseconds to wait before sending orientation updates,
|
* The number of milliseconds to wait before sending orientation updates,
|
||||||
|
|
|
@ -2,6 +2,8 @@ package nl.andrewl.aos2_client.render;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.model.Chunk;
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
import nl.andrewl.aos_core.model.World;
|
import nl.andrewl.aos_core.model.World;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL46.*;
|
import static org.lwjgl.opengl.GL46.*;
|
||||||
|
|
||||||
|
@ -9,6 +11,8 @@ import static org.lwjgl.opengl.GL46.*;
|
||||||
* Represents a 3d mesh for a chunk.
|
* Represents a 3d mesh for a chunk.
|
||||||
*/
|
*/
|
||||||
public class ChunkMesh {
|
public class ChunkMesh {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ChunkMesh.class);
|
||||||
|
|
||||||
private final int vboId;
|
private final int vboId;
|
||||||
private final int vaoId;
|
private final int vaoId;
|
||||||
private final int eboId;
|
private final int eboId;
|
||||||
|
@ -50,12 +54,11 @@ public class ChunkMesh {
|
||||||
double dur = (System.nanoTime() - start) / 1_000_000.0;
|
double dur = (System.nanoTime() - start) / 1_000_000.0;
|
||||||
this.indexCount = meshData.indexBuffer().limit();
|
this.indexCount = meshData.indexBuffer().limit();
|
||||||
// Print some debug information.
|
// Print some debug information.
|
||||||
System.out.printf(
|
log.debug(
|
||||||
"Generated mesh for chunk (%d, %d, %d) in %.3f ms. %d vertices, %d indices.%n",
|
"Generated mesh for chunk ({}, {}, {}) in {} ms. {} vertices and {} indices.",
|
||||||
chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z,
|
chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z,
|
||||||
dur,
|
dur,
|
||||||
meshData.vertexBuffer().limit() / 9,
|
meshData.vertexBuffer().limit() / 9, indexCount
|
||||||
indexCount
|
|
||||||
);
|
);
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class ChunkRenderer {
|
||||||
glUniform1i(chunkSizeUniform, Chunk.SIZE);
|
glUniform1i(chunkSizeUniform, Chunk.SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addChunkMesh(Chunk chunk) {
|
public void queueChunkMesh(Chunk chunk) {
|
||||||
meshGenerationQueue.add(chunk);
|
meshGenerationQueue.add(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,8 @@ public class ChunkRenderer {
|
||||||
while (!meshGenerationQueue.isEmpty()) {
|
while (!meshGenerationQueue.isEmpty()) {
|
||||||
Chunk chunk = meshGenerationQueue.remove();
|
Chunk chunk = meshGenerationQueue.remove();
|
||||||
ChunkMesh mesh = new ChunkMesh(chunk, world, chunkMeshGenerator);
|
ChunkMesh mesh = new ChunkMesh(chunk, world, chunkMeshGenerator);
|
||||||
|
ChunkMesh existingMesh = chunkMeshes.get(chunk.getPosition());
|
||||||
|
if (existingMesh != null) existingMesh.free();
|
||||||
chunkMeshes.put(chunk.getPosition(), mesh);
|
chunkMeshes.put(chunk.getPosition(), mesh);
|
||||||
}
|
}
|
||||||
shaderProgram.use();
|
shaderProgram.use();
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
package nl.andrewl.aos2_client.render;
|
package nl.andrewl.aos2_client.render;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.Camera;
|
import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos2_client.control.PlayerInputKeyCallback;
|
|
||||||
import nl.andrewl.aos2_client.control.PlayerViewCursorCallback;
|
|
||||||
import nl.andrewl.aos_core.model.Chunk;
|
|
||||||
import nl.andrewl.aos_core.model.World;
|
import nl.andrewl.aos_core.model.World;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.lwjgl.glfw.Callbacks;
|
import org.lwjgl.glfw.*;
|
||||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
|
||||||
import org.lwjgl.glfw.GLFWVidMode;
|
|
||||||
import org.lwjgl.opengl.GL;
|
import org.lwjgl.opengl.GL;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -48,7 +43,7 @@ public class GameRenderer {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setupWindow(PlayerViewCursorCallback viewCursorCallback, PlayerInputKeyCallback inputKeyCallback) {
|
public void setupWindow(GLFWCursorPosCallbackI viewCursorCallback, GLFWKeyCallbackI inputKeyCallback, GLFWMouseButtonCallbackI mouseButtonCallback) {
|
||||||
GLFWErrorCallback.createPrint(System.err).set();
|
GLFWErrorCallback.createPrint(System.err).set();
|
||||||
if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW.");
|
if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW.");
|
||||||
glfwDefaultWindowHints();
|
glfwDefaultWindowHints();
|
||||||
|
@ -65,6 +60,7 @@ public class GameRenderer {
|
||||||
// Setup callbacks.
|
// Setup callbacks.
|
||||||
glfwSetKeyCallback(windowHandle, inputKeyCallback);
|
glfwSetKeyCallback(windowHandle, inputKeyCallback);
|
||||||
glfwSetCursorPosCallback(windowHandle, viewCursorCallback);
|
glfwSetCursorPosCallback(windowHandle, viewCursorCallback);
|
||||||
|
glfwSetMouseButtonCallback(windowHandle, mouseButtonCallback);
|
||||||
glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||||
glfwSetInputMode(windowHandle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
glfwSetInputMode(windowHandle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||||
glfwSetCursorPos(windowHandle, 0, 0);
|
glfwSetCursorPos(windowHandle, 0, 0);
|
||||||
|
@ -118,13 +114,16 @@ public class GameRenderer {
|
||||||
updatePerspective();
|
updatePerspective();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getAspectRatio() {
|
||||||
|
return (float) screenWidth / (float) screenHeight;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the rendering perspective used to render the game. Note: only
|
* Updates the rendering perspective used to render the game. Note: only
|
||||||
* call this after calling {@link ChunkRenderer#setupShaderProgram()}.
|
* call this after calling {@link ChunkRenderer#setupShaderProgram()}.
|
||||||
*/
|
*/
|
||||||
private void updatePerspective() {
|
private void updatePerspective() {
|
||||||
float aspect = (float) screenWidth / (float) screenHeight;
|
perspectiveTransform.setPerspective(fov, getAspectRatio(), Z_NEAR, Z_FAR);
|
||||||
perspectiveTransform.setPerspective(fov, aspect, Z_NEAR, Z_FAR);
|
|
||||||
chunkRenderer.setPerspective(perspectiveTransform);
|
chunkRenderer.setPerspective(perspectiveTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
package nl.andrewl.aos2_client.render.font;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple data structure class holding information about a certain glyph in the
|
||||||
|
* font texture atlas. All sizes are for a font-size of 1.
|
||||||
|
*
|
||||||
|
* @author Karl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Character {
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
private double xTextureCoord;
|
||||||
|
private double yTextureCoord;
|
||||||
|
private double xMaxTextureCoord;
|
||||||
|
private double yMaxTextureCoord;
|
||||||
|
private double xOffset;
|
||||||
|
private double yOffset;
|
||||||
|
private double sizeX;
|
||||||
|
private double sizeY;
|
||||||
|
private double xAdvance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id
|
||||||
|
* - the ASCII value of the character.
|
||||||
|
* @param xTextureCoord
|
||||||
|
* - the x texture coordinate for the top left corner of the
|
||||||
|
* character in the texture atlas.
|
||||||
|
* @param yTextureCoord
|
||||||
|
* - the y texture coordinate for the top left corner of the
|
||||||
|
* character in the texture atlas.
|
||||||
|
* @param xTexSize
|
||||||
|
* - the width of the character in the texture atlas.
|
||||||
|
* @param yTexSize
|
||||||
|
* - the height of the character in the texture atlas.
|
||||||
|
* @param xOffset
|
||||||
|
* - the x distance from the curser to the left edge of the
|
||||||
|
* character's quad.
|
||||||
|
* @param yOffset
|
||||||
|
* - the y distance from the curser to the top edge of the
|
||||||
|
* character's quad.
|
||||||
|
* @param sizeX
|
||||||
|
* - the width of the character's quad in screen space.
|
||||||
|
* @param sizeY
|
||||||
|
* - the height of the character's quad in screen space.
|
||||||
|
* @param xAdvance
|
||||||
|
* - how far in pixels the cursor should advance after adding
|
||||||
|
* this character.
|
||||||
|
*/
|
||||||
|
protected Character(int id, double xTextureCoord, double yTextureCoord, double xTexSize, double yTexSize,
|
||||||
|
double xOffset, double yOffset, double sizeX, double sizeY, double xAdvance) {
|
||||||
|
this.id = id;
|
||||||
|
this.xTextureCoord = xTextureCoord;
|
||||||
|
this.yTextureCoord = yTextureCoord;
|
||||||
|
this.xOffset = xOffset;
|
||||||
|
this.yOffset = yOffset;
|
||||||
|
this.sizeX = sizeX;
|
||||||
|
this.sizeY = sizeY;
|
||||||
|
this.xMaxTextureCoord = xTexSize + xTextureCoord;
|
||||||
|
this.yMaxTextureCoord = yTexSize + yTextureCoord;
|
||||||
|
this.xAdvance = xAdvance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getxTextureCoord() {
|
||||||
|
return xTextureCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getyTextureCoord() {
|
||||||
|
return yTextureCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getXMaxTextureCoord() {
|
||||||
|
return xMaxTextureCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getYMaxTextureCoord() {
|
||||||
|
return yMaxTextureCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getxOffset() {
|
||||||
|
return xOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getyOffset() {
|
||||||
|
return yOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getSizeX() {
|
||||||
|
return sizeX;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getSizeY() {
|
||||||
|
return sizeY;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getxAdvance() {
|
||||||
|
return xAdvance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package nl.andrewl.aos2_client.render.font;
|
||||||
|
|
||||||
|
import nl.andrewl.aos2_client.render.ShaderProgram;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL46.*;
|
||||||
|
|
||||||
|
public class FontRenderer {
|
||||||
|
private final ShaderProgram shaderProgram;
|
||||||
|
|
||||||
|
public FontRenderer() {
|
||||||
|
shaderProgram = new ShaderProgram.Builder()
|
||||||
|
.withShader("/shader/text/vertex.glsl", GL_VERTEX_SHADER)
|
||||||
|
.withShader("/shader/text/fragment.glsl", GL_FRAGMENT_SHADER)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void free() {
|
||||||
|
shaderProgram.free();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package nl.andrewl.aos2_client.render.font;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a font. It holds the font's texture atlas as well as having the
|
||||||
|
* ability to create the quad vertices for any text using this font.
|
||||||
|
*
|
||||||
|
* @author Karl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class FontType {
|
||||||
|
|
||||||
|
private final int textureAtlas;
|
||||||
|
private final TextMeshCreator loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new font and loads up the data about each character from the
|
||||||
|
* font file.
|
||||||
|
*
|
||||||
|
* @param textureAtlas
|
||||||
|
* - the ID of the font atlas texture.
|
||||||
|
* @param fontFile
|
||||||
|
* - the font file containing information about each character in
|
||||||
|
* the texture atlas.
|
||||||
|
*/
|
||||||
|
public FontType(int textureAtlas, File fontFile, float aspectRatio) {
|
||||||
|
this.textureAtlas = textureAtlas;
|
||||||
|
this.loader = new TextMeshCreator(fontFile, aspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The font texture atlas.
|
||||||
|
*/
|
||||||
|
public int getTextureAtlas() {
|
||||||
|
return textureAtlas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes in an unloaded text and calculate all of the vertices for the quads
|
||||||
|
* on which this text will be rendered. The vertex positions and texture
|
||||||
|
* coords and calculated based on the information from the font file.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* - the unloaded text.
|
||||||
|
* @return Information about the vertices of all the quads.
|
||||||
|
*/
|
||||||
|
public TextMeshData loadText(GUIText text) {
|
||||||
|
return loader.createTextMesh(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
package nl.andrewl.aos2_client.render.font;
|
||||||
|
|
||||||
|
import org.joml.Vector2f;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.lwjgl.BufferUtils;
|
||||||
|
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL46.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a piece of text in the game.
|
||||||
|
*
|
||||||
|
* @author Karl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class GUIText {
|
||||||
|
|
||||||
|
private final String textString;
|
||||||
|
private final float fontSize;
|
||||||
|
|
||||||
|
private int textMeshVao;
|
||||||
|
private int textMeshVbo;
|
||||||
|
private int vertexCount;
|
||||||
|
private final Vector3f colour = new Vector3f(0f, 0f, 0f);
|
||||||
|
|
||||||
|
private final Vector2f position;
|
||||||
|
private final float lineMaxSize;
|
||||||
|
private int numberOfLines;
|
||||||
|
|
||||||
|
private final FontType font;
|
||||||
|
|
||||||
|
private boolean centerText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new text, loads the text's quads into a VAO, and adds the text
|
||||||
|
* to the screen.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* - the text.
|
||||||
|
* @param fontSize
|
||||||
|
* - the font size of the text, where a font size of 1 is the
|
||||||
|
* default size.
|
||||||
|
* @param font
|
||||||
|
* - the font that this text should use.
|
||||||
|
* @param position
|
||||||
|
* - the position on the screen where the top left corner of the
|
||||||
|
* text should be rendered. The top left corner of the screen is
|
||||||
|
* (0, 0) and the bottom right is (1, 1).
|
||||||
|
* @param maxLineLength
|
||||||
|
* - basically the width of the virtual page in terms of screen
|
||||||
|
* width (1 is full screen width, 0.5 is half the width of the
|
||||||
|
* screen, etc.) Text cannot go off the edge of the page, so if
|
||||||
|
* the text is longer than this length it will go onto the next
|
||||||
|
* line. When text is centered it is centered into the middle of
|
||||||
|
* the line, based on this line length value.
|
||||||
|
* @param centered
|
||||||
|
* - whether the text should be centered or not.
|
||||||
|
*/
|
||||||
|
public GUIText(String text, float fontSize, FontType font, Vector2f position, float maxLineLength,
|
||||||
|
boolean centered) {
|
||||||
|
this.textString = text;
|
||||||
|
this.fontSize = fontSize;
|
||||||
|
this.font = font;
|
||||||
|
this.position = position;
|
||||||
|
this.lineMaxSize = maxLineLength;
|
||||||
|
this.centerText = centered;
|
||||||
|
// load text
|
||||||
|
TextMeshData meshData = font.loadText(this);
|
||||||
|
textMeshVao = glGenVertexArrays();
|
||||||
|
textMeshVbo = glGenBuffers();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, textMeshVbo);
|
||||||
|
// FloatBuffer buffer1 = BufferUtils.createFloatBuffer(meshData)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the text from the screen.
|
||||||
|
*/
|
||||||
|
public void remove() {
|
||||||
|
// remove text
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The font used by this text.
|
||||||
|
*/
|
||||||
|
public FontType getFont() {
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the colour of the text.
|
||||||
|
*
|
||||||
|
* @param r
|
||||||
|
* - red value, between 0 and 1.
|
||||||
|
* @param g
|
||||||
|
* - green value, between 0 and 1.
|
||||||
|
* @param b
|
||||||
|
* - blue value, between 0 and 1.
|
||||||
|
*/
|
||||||
|
public void setColour(float r, float g, float b) {
|
||||||
|
colour.set(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the colour of the text.
|
||||||
|
*/
|
||||||
|
public Vector3f getColour() {
|
||||||
|
return colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of lines of text. This is determined when the text is
|
||||||
|
* loaded, based on the length of the text and the max line length
|
||||||
|
* that is set.
|
||||||
|
*/
|
||||||
|
public int getNumberOfLines() {
|
||||||
|
return numberOfLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The position of the top-left corner of the text in screen-space.
|
||||||
|
* (0, 0) is the top left corner of the screen, (1, 1) is the bottom
|
||||||
|
* right.
|
||||||
|
*/
|
||||||
|
public Vector2f getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the ID of the text's VAO, which contains all the vertex data for
|
||||||
|
* the quads on which the text will be rendered.
|
||||||
|
*/
|
||||||
|
public int getMesh() {
|
||||||
|
return textMeshVao;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the VAO and vertex count for this text.
|
||||||
|
*
|
||||||
|
* @param vao
|
||||||
|
* - the VAO containing all the vertex data for the quads on
|
||||||
|
* which the text will be rendered.
|
||||||
|
* @param verticesCount
|
||||||
|
* - the total number of vertices in all of the quads.
|
||||||
|
*/
|
||||||
|
public void setMeshInfo(int vao, int verticesCount) {
|
||||||
|
this.textMeshVao = vao;
|
||||||
|
this.vertexCount = verticesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The total number of vertices of all the text's quads.
|
||||||
|
*/
|
||||||
|
public int getVertexCount() {
|
||||||
|
return this.vertexCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the font size of the text (a font size of 1 is normal).
|
||||||
|
*/
|
||||||
|
protected float getFontSize() {
|
||||||
|
return fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number of lines that this text covers (method used only in
|
||||||
|
* loading).
|
||||||
|
*
|
||||||
|
* @param number
|
||||||
|
*/
|
||||||
|
protected void setNumberOfLines(int number) {
|
||||||
|
this.numberOfLines = number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the text should be centered.
|
||||||
|
*/
|
||||||
|
protected boolean isCentered() {
|
||||||
|
return centerText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The maximum length of a line of this text.
|
||||||
|
*/
|
||||||
|
protected float getMaxLineSize() {
|
||||||
|
return lineMaxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The string of text.
|
||||||
|
*/
|
||||||
|
protected String getTextString() {
|
||||||
|
return textString;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package nl.andrewl.aos2_client.render.font;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a line of text during the loading of a text.
|
||||||
|
*
|
||||||
|
* @author Karl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Line {
|
||||||
|
|
||||||
|
private final double maxLength;
|
||||||
|
private final double spaceSize;
|
||||||
|
|
||||||
|
private final List<Word> words = new ArrayList<Word>();
|
||||||
|
private double currentLineLength = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty line.
|
||||||
|
*
|
||||||
|
* @param spaceWidth
|
||||||
|
* - the screen-space width of a space character.
|
||||||
|
* @param fontSize
|
||||||
|
* - the size of font being used.
|
||||||
|
* @param maxLength
|
||||||
|
* - the screen-space maximum length of a line.
|
||||||
|
*/
|
||||||
|
protected Line(double spaceWidth, double fontSize, double maxLength) {
|
||||||
|
this.spaceSize = spaceWidth * fontSize;
|
||||||
|
this.maxLength = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to add a word to the line. If the line can fit the word in
|
||||||
|
* without reaching the maximum line length then the word is added and the
|
||||||
|
* line length increased.
|
||||||
|
*
|
||||||
|
* @param word
|
||||||
|
* - the word to try to add.
|
||||||
|
* @return {@code true} if the word has successfully been added to the line.
|
||||||
|
*/
|
||||||
|
protected boolean attemptToAddWord(Word word) {
|
||||||
|
double additionalLength = word.getWordWidth();
|
||||||
|
additionalLength += !words.isEmpty() ? spaceSize : 0;
|
||||||
|
if (currentLineLength + additionalLength <= maxLength) {
|
||||||
|
words.add(word);
|
||||||
|
currentLineLength += additionalLength;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The max length of the line.
|
||||||
|
*/
|
||||||
|
protected double getMaxLength() {
|
||||||
|
return maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The current screen-space length of the line.
|
||||||
|
*/
|
||||||
|
protected double getLineLength() {
|
||||||
|
return currentLineLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The list of words in the line.
|
||||||
|
*/
|
||||||
|
protected List<Word> getWords() {
|
||||||
|
return words;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,213 @@
|
||||||
|
package nl.andrewl.aos2_client.render.font;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides functionality for getting the values from a font file.
|
||||||
|
*
|
||||||
|
* @author Karl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class MetaFile {
|
||||||
|
|
||||||
|
private static final int PAD_TOP = 0;
|
||||||
|
private static final int PAD_LEFT = 1;
|
||||||
|
private static final int PAD_BOTTOM = 2;
|
||||||
|
private static final int PAD_RIGHT = 3;
|
||||||
|
|
||||||
|
private static final int DESIRED_PADDING = 3;
|
||||||
|
|
||||||
|
private static final String SPLITTER = " ";
|
||||||
|
private static final String NUMBER_SEPARATOR = ",";
|
||||||
|
|
||||||
|
private final double aspectRatio;
|
||||||
|
|
||||||
|
private double verticalPerPixelSize;
|
||||||
|
private double horizontalPerPixelSize;
|
||||||
|
private double spaceWidth;
|
||||||
|
private int[] padding;
|
||||||
|
private int paddingWidth;
|
||||||
|
private int paddingHeight;
|
||||||
|
|
||||||
|
private final Map<Integer, Character> metaData = new HashMap<>();
|
||||||
|
|
||||||
|
private BufferedReader reader;
|
||||||
|
private final Map<String, String> values = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a font file in preparation for reading.
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* - the font file.
|
||||||
|
*/
|
||||||
|
protected MetaFile(File file, float aspectRatio) {
|
||||||
|
this.aspectRatio = aspectRatio;
|
||||||
|
openFile(file);
|
||||||
|
loadPaddingData();
|
||||||
|
loadLineSizes();
|
||||||
|
int imageWidth = getValueOfVariable("scaleW");
|
||||||
|
loadCharacterData(imageWidth);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double getSpaceWidth() {
|
||||||
|
return spaceWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Character getCharacter(int ascii) {
|
||||||
|
return metaData.get(ascii);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read in the next line and store the variable values.
|
||||||
|
*
|
||||||
|
* @return {@code true} if the end of the file hasn't been reached.
|
||||||
|
*/
|
||||||
|
private boolean processNextLine() {
|
||||||
|
values.clear();
|
||||||
|
String line = null;
|
||||||
|
try {
|
||||||
|
line = reader.readLine();
|
||||||
|
} catch (IOException e1) {
|
||||||
|
}
|
||||||
|
if (line == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (String part : line.split(SPLITTER)) {
|
||||||
|
String[] valuePairs = part.split("=");
|
||||||
|
if (valuePairs.length == 2) {
|
||||||
|
values.put(valuePairs[0], valuePairs[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@code int} value of the variable with a certain name on the
|
||||||
|
* current line.
|
||||||
|
*
|
||||||
|
* @param variable
|
||||||
|
* - the name of the variable.
|
||||||
|
* @return The value of the variable.
|
||||||
|
*/
|
||||||
|
private int getValueOfVariable(String variable) {
|
||||||
|
return Integer.parseInt(values.get(variable));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the array of ints associated with a variable on the current line.
|
||||||
|
*
|
||||||
|
* @param variable
|
||||||
|
* - the name of the variable.
|
||||||
|
* @return The int array of values associated with the variable.
|
||||||
|
*/
|
||||||
|
private int[] getValuesOfVariable(String variable) {
|
||||||
|
String[] numbers = values.get(variable).split(NUMBER_SEPARATOR);
|
||||||
|
int[] actualValues = new int[numbers.length];
|
||||||
|
for (int i = 0; i < actualValues.length; i++) {
|
||||||
|
actualValues[i] = Integer.parseInt(numbers[i]);
|
||||||
|
}
|
||||||
|
return actualValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the font file after finishing reading.
|
||||||
|
*/
|
||||||
|
private void close() {
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the font file, ready for reading.
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* - the font file.
|
||||||
|
*/
|
||||||
|
private void openFile(File file) {
|
||||||
|
try {
|
||||||
|
reader = new BufferedReader(new FileReader(file));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.println("Couldn't read font meta file!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the data about how much padding is used around each character in
|
||||||
|
* the texture atlas.
|
||||||
|
*/
|
||||||
|
private void loadPaddingData() {
|
||||||
|
processNextLine();
|
||||||
|
this.padding = getValuesOfVariable("padding");
|
||||||
|
this.paddingWidth = padding[PAD_LEFT] + padding[PAD_RIGHT];
|
||||||
|
this.paddingHeight = padding[PAD_TOP] + padding[PAD_BOTTOM];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads information about the line height for this font in pixels, and uses
|
||||||
|
* this as a way to find the conversion rate between pixels in the texture
|
||||||
|
* atlas and screen-space.
|
||||||
|
*/
|
||||||
|
private void loadLineSizes() {
|
||||||
|
processNextLine();
|
||||||
|
int lineHeightPixels = getValueOfVariable("lineHeight") - paddingHeight;
|
||||||
|
verticalPerPixelSize = TextMeshCreator.LINE_HEIGHT / (double) lineHeightPixels;
|
||||||
|
horizontalPerPixelSize = verticalPerPixelSize / aspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads in data about each character and stores the data in the
|
||||||
|
* {@link Character} class.
|
||||||
|
*
|
||||||
|
* @param imageWidth
|
||||||
|
* - the width of the texture atlas in pixels.
|
||||||
|
*/
|
||||||
|
private void loadCharacterData(int imageWidth) {
|
||||||
|
processNextLine();
|
||||||
|
processNextLine();
|
||||||
|
while (processNextLine()) {
|
||||||
|
Character c = loadCharacter(imageWidth);
|
||||||
|
if (c != null) {
|
||||||
|
metaData.put(c.getId(), c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all the data about one character in the texture atlas and converts
|
||||||
|
* it all from 'pixels' to 'screen-space' before storing. The effects of
|
||||||
|
* padding are also removed from the data.
|
||||||
|
*
|
||||||
|
* @param imageSize
|
||||||
|
* - the size of the texture atlas in pixels.
|
||||||
|
* @return The data about the character.
|
||||||
|
*/
|
||||||
|
private Character loadCharacter(int imageSize) {
|
||||||
|
int id = getValueOfVariable("id");
|
||||||
|
if (id == TextMeshCreator.SPACE_ASCII) {
|
||||||
|
this.spaceWidth = (getValueOfVariable("xadvance") - paddingWidth) * horizontalPerPixelSize;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
double xTex = ((double) getValueOfVariable("x") + (padding[PAD_LEFT] - DESIRED_PADDING)) / imageSize;
|
||||||
|
double yTex = ((double) getValueOfVariable("y") + (padding[PAD_TOP] - DESIRED_PADDING)) / imageSize;
|
||||||
|
int width = getValueOfVariable("width") - (paddingWidth - (2 * DESIRED_PADDING));
|
||||||
|
int height = getValueOfVariable("height") - ((paddingHeight) - (2 * DESIRED_PADDING));
|
||||||
|
double quadWidth = width * horizontalPerPixelSize;
|
||||||
|
double quadHeight = height * verticalPerPixelSize;
|
||||||
|
double xTexSize = (double) width / imageSize;
|
||||||
|
double yTexSize = (double) height / imageSize;
|
||||||
|
double xOff = (getValueOfVariable("xoffset") + padding[PAD_LEFT] - DESIRED_PADDING) * horizontalPerPixelSize;
|
||||||
|
double yOff = (getValueOfVariable("yoffset") + (padding[PAD_TOP] - DESIRED_PADDING)) * verticalPerPixelSize;
|
||||||
|
double xAdvance = (getValueOfVariable("xadvance") - paddingWidth) * horizontalPerPixelSize;
|
||||||
|
return new Character(id, xTex, yTex, xTexSize, yTexSize, xOff, yOff, quadWidth, quadHeight, xAdvance);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package nl.andrewl.aos2_client.render.font;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TextMeshCreator {
|
||||||
|
|
||||||
|
protected static final double LINE_HEIGHT = 0.03f;
|
||||||
|
protected static final int SPACE_ASCII = 32;
|
||||||
|
|
||||||
|
private final MetaFile metaData;
|
||||||
|
|
||||||
|
protected TextMeshCreator(File metaFile, float aspectRatio) {
|
||||||
|
metaData = new MetaFile(metaFile, aspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TextMeshData createTextMesh(GUIText text) {
|
||||||
|
List<Line> lines = createStructure(text);
|
||||||
|
return createQuadVertices(text, lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Line> createStructure(GUIText text) {
|
||||||
|
char[] chars = text.getTextString().toCharArray();
|
||||||
|
List<Line> lines = new ArrayList<Line>();
|
||||||
|
Line currentLine = new Line(metaData.getSpaceWidth(), text.getFontSize(), text.getMaxLineSize());
|
||||||
|
Word currentWord = new Word(text.getFontSize());
|
||||||
|
for (char c : chars) {
|
||||||
|
if ((int) c == SPACE_ASCII) {
|
||||||
|
boolean added = currentLine.attemptToAddWord(currentWord);
|
||||||
|
if (!added) {
|
||||||
|
lines.add(currentLine);
|
||||||
|
currentLine = new Line(metaData.getSpaceWidth(), text.getFontSize(), text.getMaxLineSize());
|
||||||
|
currentLine.attemptToAddWord(currentWord);
|
||||||
|
}
|
||||||
|
currentWord = new Word(text.getFontSize());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Character character = metaData.getCharacter(c);
|
||||||
|
currentWord.addCharacter(character);
|
||||||
|
}
|
||||||
|
completeStructure(lines, currentLine, currentWord, text);
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void completeStructure(List<Line> lines, Line currentLine, Word currentWord, GUIText text) {
|
||||||
|
boolean added = currentLine.attemptToAddWord(currentWord);
|
||||||
|
if (!added) {
|
||||||
|
lines.add(currentLine);
|
||||||
|
currentLine = new Line(metaData.getSpaceWidth(), text.getFontSize(), text.getMaxLineSize());
|
||||||
|
currentLine.attemptToAddWord(currentWord);
|
||||||
|
}
|
||||||
|
lines.add(currentLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextMeshData createQuadVertices(GUIText text, List<Line> lines) {
|
||||||
|
text.setNumberOfLines(lines.size());
|
||||||
|
double curserX = 0f;
|
||||||
|
double curserY = 0f;
|
||||||
|
List<Float> vertices = new ArrayList<Float>();
|
||||||
|
List<Float> textureCoords = new ArrayList<Float>();
|
||||||
|
for (Line line : lines) {
|
||||||
|
if (text.isCentered()) {
|
||||||
|
curserX = (line.getMaxLength() - line.getLineLength()) / 2;
|
||||||
|
}
|
||||||
|
for (Word word : line.getWords()) {
|
||||||
|
for (Character letter : word.getCharacters()) {
|
||||||
|
addVerticesForCharacter(curserX, curserY, letter, text.getFontSize(), vertices);
|
||||||
|
addTexCoords(textureCoords, letter.getxTextureCoord(), letter.getyTextureCoord(),
|
||||||
|
letter.getXMaxTextureCoord(), letter.getYMaxTextureCoord());
|
||||||
|
curserX += letter.getxAdvance() * text.getFontSize();
|
||||||
|
}
|
||||||
|
curserX += metaData.getSpaceWidth() * text.getFontSize();
|
||||||
|
}
|
||||||
|
curserX = 0;
|
||||||
|
curserY += LINE_HEIGHT * text.getFontSize();
|
||||||
|
}
|
||||||
|
return new TextMeshData(listToArray(vertices), listToArray(textureCoords));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addVerticesForCharacter(double curserX, double curserY, Character character, double fontSize,
|
||||||
|
List<Float> vertices) {
|
||||||
|
double x = curserX + (character.getxOffset() * fontSize);
|
||||||
|
double y = curserY + (character.getyOffset() * fontSize);
|
||||||
|
double maxX = x + (character.getSizeX() * fontSize);
|
||||||
|
double maxY = y + (character.getSizeY() * fontSize);
|
||||||
|
double properX = (2 * x) - 1;
|
||||||
|
double properY = (-2 * y) + 1;
|
||||||
|
double properMaxX = (2 * maxX) - 1;
|
||||||
|
double properMaxY = (-2 * maxY) + 1;
|
||||||
|
addVertices(vertices, properX, properY, properMaxX, properMaxY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addVertices(List<Float> vertices, double x, double y, double maxX, double maxY) {
|
||||||
|
vertices.add((float) x);
|
||||||
|
vertices.add((float) y);
|
||||||
|
vertices.add((float) x);
|
||||||
|
vertices.add((float) maxY);
|
||||||
|
vertices.add((float) maxX);
|
||||||
|
vertices.add((float) maxY);
|
||||||
|
vertices.add((float) maxX);
|
||||||
|
vertices.add((float) maxY);
|
||||||
|
vertices.add((float) maxX);
|
||||||
|
vertices.add((float) y);
|
||||||
|
vertices.add((float) x);
|
||||||
|
vertices.add((float) y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addTexCoords(List<Float> texCoords, double x, double y, double maxX, double maxY) {
|
||||||
|
texCoords.add((float) x);
|
||||||
|
texCoords.add((float) y);
|
||||||
|
texCoords.add((float) x);
|
||||||
|
texCoords.add((float) maxY);
|
||||||
|
texCoords.add((float) maxX);
|
||||||
|
texCoords.add((float) maxY);
|
||||||
|
texCoords.add((float) maxX);
|
||||||
|
texCoords.add((float) maxY);
|
||||||
|
texCoords.add((float) maxX);
|
||||||
|
texCoords.add((float) y);
|
||||||
|
texCoords.add((float) x);
|
||||||
|
texCoords.add((float) y);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static float[] listToArray(List<Float> listOfFloats) {
|
||||||
|
float[] array = new float[listOfFloats.size()];
|
||||||
|
for (int i = 0; i < array.length; i++) {
|
||||||
|
array[i] = listOfFloats.get(i);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package nl.andrewl.aos2_client.render.font;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the vertex data for all the quads on which a text will be rendered.
|
||||||
|
* @author Karl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TextMeshData {
|
||||||
|
|
||||||
|
private final float[] vertexPositions;
|
||||||
|
private final float[] textureCoords;
|
||||||
|
|
||||||
|
protected TextMeshData(float[] vertexPositions, float[] textureCoords){
|
||||||
|
this.vertexPositions = vertexPositions;
|
||||||
|
this.textureCoords = textureCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getVertexPositions() {
|
||||||
|
return vertexPositions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getTextureCoords() {
|
||||||
|
return textureCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVertexCount() {
|
||||||
|
return vertexPositions.length/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package nl.andrewl.aos2_client.render.font;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* During the loading of a text this represents one word in the text.
|
||||||
|
* @author Karl
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Word {
|
||||||
|
|
||||||
|
private final List<Character> characters = new ArrayList<>();
|
||||||
|
private double width = 0;
|
||||||
|
private final double fontSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new empty word.
|
||||||
|
* @param fontSize - the font size of the text which this word is in.
|
||||||
|
*/
|
||||||
|
protected Word(double fontSize){
|
||||||
|
this.fontSize = fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a character to the end of the current word and increases the screen-space width of the word.
|
||||||
|
* @param character - the character to be added.
|
||||||
|
*/
|
||||||
|
protected void addCharacter(Character character){
|
||||||
|
characters.add(character);
|
||||||
|
width += character.getxAdvance() * fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The list of characters in the word.
|
||||||
|
*/
|
||||||
|
protected List<Character> getCharacters(){
|
||||||
|
return characters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The width of the word in terms of screen size.
|
||||||
|
*/
|
||||||
|
protected double getWordWidth(){
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
info face="JetBrains Mono Regular" size=74 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=3,3,3,3 spacing=0,0
|
||||||
|
common lineHeight=105 base=76 scaleW=512 scaleH=512 pages=1 packed=0
|
||||||
|
page id=0 file="jetbrains-mono.png"
|
||||||
|
chars count=97
|
||||||
|
char id=0 x=0 y=444 width=48 height=26 xoffset=-1 yoffset=36 xadvance=50 page=0 chnl=0
|
||||||
|
char id=10 x=0 y=0 width=0 height=0 xoffset=-3 yoffset=0 xadvance=6 page=0 chnl=0
|
||||||
|
char id=32 x=0 y=0 width=0 height=0 xoffset=-3 yoffset=0 xadvance=50 page=0 chnl=0
|
||||||
|
char id=33 x=490 y=82 width=18 height=62 xoffset=13 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=34 x=457 y=396 width=29 height=29 xoffset=8 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=36 x=0 y=0 width=41 height=82 xoffset=2 yoffset=7 xadvance=50 page=0 chnl=0
|
||||||
|
char id=37 x=312 y=151 width=49 height=62 xoffset=-2 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=38 x=368 y=82 width=44 height=63 xoffset=1 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=39 x=486 y=396 width=14 height=29 xoffset=15 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=40 x=77 y=0 width=29 height=78 xoffset=10 yoffset=10 xadvance=50 page=0 chnl=0
|
||||||
|
char id=41 x=106 y=0 width=29 height=78 xoffset=5 yoffset=10 xadvance=50 page=0 chnl=0
|
||||||
|
char id=42 x=290 y=396 width=46 height=45 xoffset=-1 yoffset=27 xadvance=50 page=0 chnl=0
|
||||||
|
char id=43 x=336 y=396 width=42 height=41 xoffset=1 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=44 x=483 y=0 width=22 height=30 xoffset=9 yoffset=61 xadvance=50 page=0 chnl=0
|
||||||
|
char id=45 x=179 y=444 width=31 height=12 xoffset=7 yoffset=45 xadvance=50 page=0 chnl=0
|
||||||
|
char id=46 x=137 y=444 width=19 height=19 xoffset=13 yoffset=61 xadvance=50 page=0 chnl=0
|
||||||
|
char id=47 x=149 y=0 width=40 height=77 xoffset=2 yoffset=10 xadvance=50 page=0 chnl=0
|
||||||
|
char id=48 x=328 y=82 width=40 height=63 xoffset=2 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=49 x=460 y=274 width=40 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=50 x=77 y=151 width=40 height=62 xoffset=2 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=51 x=117 y=151 width=39 height=62 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=52 x=0 y=335 width=38 height=61 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=53 x=156 y=151 width=39 height=62 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=54 x=195 y=151 width=42 height=62 xoffset=1 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=55 x=38 y=335 width=42 height=61 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=56 x=286 y=82 width=42 height=63 xoffset=1 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=57 x=237 y=151 width=42 height=62 xoffset=1 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=58 x=483 y=151 width=19 height=49 xoffset=13 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=59 x=477 y=213 width=23 height=60 xoffset=9 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=60 x=0 y=0 width=0 height=0 xoffset=3 yoffset=0 xadvance=50 page=0 chnl=0
|
||||||
|
char id=61 x=418 y=396 width=39 height=30 xoffset=3 yoffset=36 xadvance=50 page=0 chnl=0
|
||||||
|
char id=62 x=165 y=335 width=110 height=50 xoffset=3 yoffset=27 xadvance=50 page=0 chnl=0
|
||||||
|
char id=63 x=279 y=151 width=33 height=62 xoffset=6 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=64 x=396 y=0 width=45 height=75 xoffset=0 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=65 x=361 y=151 width=44 height=61 xoffset=0 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=66 x=405 y=151 width=40 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=67 x=129 y=82 width=39 height=63 xoffset=3 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=68 x=445 y=151 width=38 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=69 x=0 y=213 width=38 height=61 xoffset=4 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=70 x=38 y=213 width=38 height=61 xoffset=4 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=71 x=168 y=82 width=39 height=63 xoffset=3 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=72 x=76 y=213 width=38 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=73 x=114 y=213 width=36 height=61 xoffset=4 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=74 x=412 y=82 width=40 height=62 xoffset=0 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=75 x=150 y=213 width=42 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=76 x=192 y=213 width=38 height=61 xoffset=6 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=77 x=230 y=213 width=40 height=61 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=78 x=270 y=213 width=38 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=79 x=207 y=82 width=38 height=63 xoffset=3 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=80 x=308 y=213 width=41 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=81 x=356 y=0 width=40 height=75 xoffset=2 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=82 x=349 y=213 width=41 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=83 x=245 y=82 width=41 height=63 xoffset=2 yoffset=17 xadvance=50 page=0 chnl=0
|
||||||
|
char id=84 x=390 y=213 width=43 height=61 xoffset=1 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=85 x=452 y=82 width=38 height=62 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=86 x=433 y=213 width=44 height=61 xoffset=0 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=87 x=0 y=274 width=48 height=61 xoffset=-2 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=88 x=48 y=274 width=46 height=61 xoffset=-1 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=89 x=94 y=274 width=46 height=61 xoffset=-1 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=90 x=140 y=274 width=39 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=91 x=229 y=0 width=25 height=76 xoffset=12 yoffset=11 xadvance=50 page=0 chnl=0
|
||||||
|
char id=92 x=189 y=0 width=40 height=77 xoffset=2 yoffset=10 xadvance=50 page=0 chnl=0
|
||||||
|
char id=93 x=254 y=0 width=25 height=76 xoffset=8 yoffset=11 xadvance=50 page=0 chnl=0
|
||||||
|
char id=94 x=378 y=396 width=40 height=36 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=95 x=0 y=82 width=129 height=69 xoffset=1 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=96 x=156 y=444 width=23 height=17 xoffset=8 yoffset=14 xadvance=50 page=0 chnl=0
|
||||||
|
char id=97 x=275 y=335 width=40 height=49 xoffset=1 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=98 x=0 y=151 width=39 height=62 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=99 x=315 y=335 width=39 height=49 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=100 x=39 y=151 width=38 height=62 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=101 x=354 y=335 width=39 height=49 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=102 x=179 y=274 width=42 height=61 xoffset=1 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=103 x=221 y=274 width=38 height=61 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=104 x=259 y=274 width=38 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=105 x=441 y=0 width=42 height=65 xoffset=3 yoffset=14 xadvance=50 page=0 chnl=0
|
||||||
|
char id=106 x=41 y=0 width=36 height=78 xoffset=2 yoffset=14 xadvance=50 page=0 chnl=0
|
||||||
|
char id=107 x=297 y=274 width=41 height=61 xoffset=4 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=108 x=338 y=274 width=45 height=61 xoffset=-1 yoffset=18 xadvance=50 page=0 chnl=0
|
||||||
|
char id=109 x=0 y=396 width=42 height=48 xoffset=1 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=110 x=471 y=335 width=38 height=48 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=111 x=393 y=335 width=39 height=49 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=112 x=383 y=274 width=39 height=61 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=113 x=422 y=274 width=38 height=61 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=114 x=42 y=396 width=38 height=48 xoffset=5 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=115 x=432 y=335 width=39 height=49 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0
|
||||||
|
char id=116 x=124 y=335 width=41 height=59 xoffset=1 yoffset=20 xadvance=50 page=0 chnl=0
|
||||||
|
char id=117 x=80 y=396 width=38 height=48 xoffset=3 yoffset=32 xadvance=50 page=0 chnl=0
|
||||||
|
char id=118 x=118 y=396 width=44 height=47 xoffset=0 yoffset=32 xadvance=50 page=0 chnl=0
|
||||||
|
char id=119 x=162 y=396 width=46 height=47 xoffset=-1 yoffset=32 xadvance=50 page=0 chnl=0
|
||||||
|
char id=120 x=208 y=396 width=44 height=47 xoffset=0 yoffset=32 xadvance=50 page=0 chnl=0
|
||||||
|
char id=121 x=80 y=335 width=44 height=60 xoffset=0 yoffset=32 xadvance=50 page=0 chnl=0
|
||||||
|
char id=122 x=252 y=396 width=38 height=47 xoffset=3 yoffset=32 xadvance=50 page=0 chnl=0
|
||||||
|
char id=123 x=279 y=0 width=39 height=76 xoffset=2 yoffset=11 xadvance=50 page=0 chnl=0
|
||||||
|
char id=124 x=135 y=0 width=14 height=77 xoffset=15 yoffset=10 xadvance=50 page=0 chnl=0
|
||||||
|
char id=125 x=318 y=0 width=38 height=76 xoffset=4 yoffset=11 xadvance=50 page=0 chnl=0
|
||||||
|
char id=126 x=96 y=444 width=41 height=22 xoffset=2 yoffset=38 xadvance=50 page=0 chnl=0
|
||||||
|
char id=127 x=48 y=444 width=48 height=25 xoffset=-1 yoffset=36 xadvance=50 page=0 chnl=0
|
||||||
|
kernings count=0
|
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
|
@ -126,14 +126,4 @@ public class Chunk {
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector3f getColor(byte blockValue) {
|
|
||||||
float v = blockValue / 127.0f;
|
|
||||||
return new Vector3f(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void getColor(byte blockValue, Vector3f vec) {
|
|
||||||
float v = blockValue / 127.0f;
|
|
||||||
vec.set(v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,9 +56,7 @@ public class World {
|
||||||
getChunkPosAt(pos, util);
|
getChunkPosAt(pos, util);
|
||||||
Chunk chunk = chunkMap.get(util);
|
Chunk chunk = chunkMap.get(util);
|
||||||
if (chunk == null) return 0;
|
if (chunk == null) return 0;
|
||||||
util.x = (int) Math.floor(pos.x - util.x * Chunk.SIZE);
|
getLocalPosAt(pos.x, pos.y, pos.z, util);
|
||||||
util.y = (int) Math.floor(pos.y - util.y * Chunk.SIZE);
|
|
||||||
util.z = (int) Math.floor(pos.z - util.z * Chunk.SIZE);
|
|
||||||
return chunk.getBlockAt(util);
|
return chunk.getBlockAt(util);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,11 +68,7 @@ public class World {
|
||||||
Vector3i chunkPos = getChunkPosAt(pos);
|
Vector3i chunkPos = getChunkPosAt(pos);
|
||||||
Chunk chunk = chunkMap.get(chunkPos);
|
Chunk chunk = chunkMap.get(chunkPos);
|
||||||
if (chunk == null) return;
|
if (chunk == null) return;
|
||||||
Vector3i blockPos = new Vector3i(
|
Vector3i blockPos = getLocalPosAt(pos.x, pos.y, pos.z, chunkPos);
|
||||||
(int) Math.floor(pos.x - chunkPos.x * Chunk.SIZE),
|
|
||||||
(int) Math.floor(pos.y - chunkPos.y * Chunk.SIZE),
|
|
||||||
(int) Math.floor(pos.z - chunkPos.z * Chunk.SIZE)
|
|
||||||
);
|
|
||||||
chunk.setBlockAt(blockPos.x, blockPos.y, blockPos.z, block);
|
chunk.setBlockAt(blockPos.x, blockPos.y, blockPos.z, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +80,10 @@ public class World {
|
||||||
return chunkMap.get(chunkPos);
|
return chunkMap.get(chunkPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Chunk getChunkAt(int x, int y, int z) {
|
||||||
|
return chunkMap.get(new Vector3i(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the position that a system is looking at, within a distance limit.
|
* Gets the position that a system is looking at, within a distance limit.
|
||||||
* Usually used to determine where a player has interacted/clicked in the
|
* Usually used to determine where a player has interacted/clicked in the
|
||||||
|
@ -157,18 +155,52 @@ public class World {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the coordinates of a chunk at a given world position.
|
* Gets the chunk position at the specified world position.
|
||||||
* @param worldPos The world position.
|
* @param x The x coordinate.
|
||||||
* @return The chunk position. Note that this may not correspond to any existing chunk.
|
* @param y The y coordinate.
|
||||||
|
* @param z The z coordinate.
|
||||||
|
* @param dest The destination vector to place the chunk position in.
|
||||||
|
* @return The destination vector, for method chaining.
|
||||||
*/
|
*/
|
||||||
|
public static Vector3i getChunkPosAt(float x, float y, float z, Vector3i dest) {
|
||||||
|
dest.x = (int) Math.floor(x / Chunk.SIZE);
|
||||||
|
dest.y = (int) Math.floor(y / Chunk.SIZE);
|
||||||
|
dest.z = (int) Math.floor(z / Chunk.SIZE);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3i getChunkPosAt(Vector3f worldPos, Vector3i dest) {
|
||||||
|
return getChunkPosAt(worldPos.x, worldPos.y, worldPos.z, dest);
|
||||||
|
}
|
||||||
|
|
||||||
public static Vector3i getChunkPosAt(Vector3f worldPos) {
|
public static Vector3i getChunkPosAt(Vector3f worldPos) {
|
||||||
return getChunkPosAt(worldPos, new Vector3i());
|
return getChunkPosAt(worldPos, new Vector3i());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector3i getChunkPosAt(Vector3f worldPos, Vector3i dest) {
|
public static Vector3i getChunkPosAt(Vector3i worldPos) {
|
||||||
dest.x = (int) Math.floor(worldPos.x / Chunk.SIZE);
|
return getChunkPosAt(worldPos.x, worldPos.y, worldPos.z, new Vector3i());
|
||||||
dest.y = (int) Math.floor(worldPos.y / Chunk.SIZE);
|
}
|
||||||
dest.z = (int) Math.floor(worldPos.z / Chunk.SIZE);
|
|
||||||
|
/**
|
||||||
|
* Gets the chunk-local position at the specified world position.
|
||||||
|
* @param x The x coordinate.
|
||||||
|
* @param y The y coordinate.
|
||||||
|
* @param z The z coordinate.
|
||||||
|
* @param dest The destination vector to place the local position in.
|
||||||
|
* @return The destination vector, for method chaining.
|
||||||
|
*/
|
||||||
|
public static Vector3i getLocalPosAt(float x, float y, float z, Vector3i dest) {
|
||||||
|
getChunkPosAt(x, y, z, dest);
|
||||||
|
float chunkX = dest.x;
|
||||||
|
float chunkY = dest.y;
|
||||||
|
float chunkZ = dest.z;
|
||||||
|
dest.x = (int) Math.floor(x - chunkX * Chunk.SIZE);
|
||||||
|
dest.y = (int) Math.floor(y - chunkY * Chunk.SIZE);
|
||||||
|
dest.z = (int) Math.floor(z - chunkZ * Chunk.SIZE);
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Vector3i getLocalPosAt(Vector3i worldPos) {
|
||||||
|
return getLocalPosAt(worldPos.x, worldPos.y, worldPos.z, new Vector3i());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,21 @@ package nl.andrewl.aos_core.net.udp;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A message that' sent periodically by the client when the player's input
|
* A message that's sent periodically by the client when the player's input
|
||||||
* changes.
|
* changes.
|
||||||
*/
|
*/
|
||||||
public record ClientInputState(
|
public record ClientInputState(
|
||||||
int clientId,
|
int clientId,
|
||||||
|
// Movement
|
||||||
boolean forward,
|
boolean forward,
|
||||||
boolean backward,
|
boolean backward,
|
||||||
boolean left,
|
boolean left,
|
||||||
boolean right,
|
boolean right,
|
||||||
boolean jumping,
|
boolean jumping,
|
||||||
boolean crouching,
|
boolean crouching,
|
||||||
boolean sprinting
|
boolean sprinting,
|
||||||
|
|
||||||
|
// Interaction
|
||||||
|
boolean hitting, // Usually a "left-click" action.
|
||||||
|
boolean interacting // Usually a "right-click" action.
|
||||||
) implements Message {}
|
) implements Message {}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.World;
|
import nl.andrewl.aos_core.model.World;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ChunkUpdateMessage;
|
||||||
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
||||||
import org.joml.Math;
|
import org.joml.Math;
|
||||||
import org.joml.Vector2i;
|
import org.joml.Vector2i;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Vector3i;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -17,6 +20,8 @@ public class ServerPlayer extends Player {
|
||||||
|
|
||||||
public static final float HEIGHT = 1.8f;
|
public static final float HEIGHT = 1.8f;
|
||||||
public static final float HEIGHT_CROUCH = 1.4f;
|
public static final float HEIGHT_CROUCH = 1.4f;
|
||||||
|
public static final float EYE_HEIGHT = HEIGHT - 0.1f;
|
||||||
|
public static final float EYE_HEIGHT_CROUCH = HEIGHT_CROUCH - 0.1f;
|
||||||
public static final float WIDTH = 0.75f;
|
public static final float WIDTH = 0.75f;
|
||||||
public static final float RADIUS = WIDTH / 2f;
|
public static final float RADIUS = WIDTH / 2f;
|
||||||
|
|
||||||
|
@ -28,13 +33,17 @@ public class ServerPlayer extends Player {
|
||||||
public static final float MOVEMENT_DECELERATION = 2f;
|
public static final float MOVEMENT_DECELERATION = 2f;
|
||||||
public static final float JUMP_SPEED = 8f;
|
public static final float JUMP_SPEED = 8f;
|
||||||
|
|
||||||
|
public static final int BLOCK_REMOVE_COOLDOWN = 250;
|
||||||
|
|
||||||
private ClientInputState lastInputState;
|
private ClientInputState lastInputState;
|
||||||
|
private long lastBlockRemovedAt = 0;
|
||||||
|
|
||||||
private boolean updated = false;
|
private boolean updated = false;
|
||||||
|
|
||||||
public ServerPlayer(int id, String username) {
|
public ServerPlayer(int id, String username) {
|
||||||
super(id, username);
|
super(id, username);
|
||||||
// Initialize with a default state of no input.
|
// Initialize with a default state of no input.
|
||||||
lastInputState = new ClientInputState(id, false, false, false, false, false, false, false);
|
lastInputState = new ClientInputState(id, false, false, false, false, false, false, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientInputState getLastInputState() {
|
public ClientInputState getLastInputState() {
|
||||||
|
@ -49,13 +58,36 @@ public class ServerPlayer extends Player {
|
||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void tick(float dt, World world) {
|
public void tick(float dt, World world, Server server) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (lastInputState.hitting() && now - lastBlockRemovedAt > BLOCK_REMOVE_COOLDOWN) {
|
||||||
|
Vector3f eyePos = new Vector3f(position);
|
||||||
|
eyePos.y += getEyeHeight();
|
||||||
|
Vector3i targetPos = world.getLookingAtPos(eyePos, viewVector, 10);
|
||||||
|
System.out.println(targetPos);
|
||||||
|
if (targetPos != null) {
|
||||||
|
Vector3i chunkPos = World.getChunkPosAt(targetPos);
|
||||||
|
Vector3i localPos = World.getLocalPosAt(targetPos);
|
||||||
|
world.setBlockAt(targetPos.x, targetPos.y, targetPos.z, (byte) 0);
|
||||||
|
lastBlockRemovedAt = now;
|
||||||
|
server.getPlayerManager().broadcastUdpMessage(new ChunkUpdateMessage(
|
||||||
|
chunkPos.x, chunkPos.y, chunkPos.z,
|
||||||
|
localPos.x, localPos.y, localPos.z,
|
||||||
|
(byte) 0
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tickMovement(dt, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tickMovement(float dt, World world) {
|
||||||
updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick.
|
updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick.
|
||||||
|
boolean grounded = isGrounded(world);
|
||||||
|
tickHorizontalVelocity(grounded);
|
||||||
|
|
||||||
if (isGrounded(world)) {
|
if (isGrounded(world)) {
|
||||||
tickHorizontalVelocity();
|
|
||||||
if (lastInputState.jumping()) {
|
if (lastInputState.jumping()) {
|
||||||
velocity.y = JUMP_SPEED;
|
velocity.y = JUMP_SPEED * (lastInputState.sprinting() ? 1.25f : 1f);
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -73,7 +105,7 @@ public class ServerPlayer extends Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tickHorizontalVelocity() {
|
private void tickHorizontalVelocity(boolean doDeceleration) {
|
||||||
Vector3f horizontalVelocity = new Vector3f(
|
Vector3f horizontalVelocity = new Vector3f(
|
||||||
velocity.x == velocity.x ? velocity.x : 0f,
|
velocity.x == velocity.x ? velocity.x : 0f,
|
||||||
0,
|
0,
|
||||||
|
@ -101,7 +133,7 @@ public class ServerPlayer extends Player {
|
||||||
horizontalVelocity.normalize(maxSpeed);
|
horizontalVelocity.normalize(maxSpeed);
|
||||||
}
|
}
|
||||||
updated = true;
|
updated = true;
|
||||||
} else if (horizontalVelocity.lengthSquared() > 0) {
|
} else if (doDeceleration && horizontalVelocity.lengthSquared() > 0) {
|
||||||
Vector3f deceleration = new Vector3f(horizontalVelocity)
|
Vector3f deceleration = new Vector3f(horizontalVelocity)
|
||||||
.negate().normalize()
|
.negate().normalize()
|
||||||
.mul(Math.min(horizontalVelocity.length(), MOVEMENT_DECELERATION));
|
.mul(Math.min(horizontalVelocity.length(), MOVEMENT_DECELERATION));
|
||||||
|
@ -154,105 +186,144 @@ public class ServerPlayer extends Player {
|
||||||
// movement.x, movement.y, movement.z,
|
// movement.x, movement.y, movement.z,
|
||||||
// nextTickPosition.x, nextTickPosition.y, nextTickPosition.z
|
// nextTickPosition.x, nextTickPosition.y, nextTickPosition.z
|
||||||
// );
|
// );
|
||||||
checkWallCollision(world, nextTickPosition, movement);
|
float height = getCurrentHeight();
|
||||||
checkCeilingCollision(world, nextTickPosition, movement);
|
float delta = 0.00001f;
|
||||||
checkFloorCollision(world, nextTickPosition, movement);
|
final Vector3f stepSize = new Vector3f(movement).normalize(1.0f);
|
||||||
}
|
// The number of steps we'll make towards the next tick position.
|
||||||
|
int stepCount = (int) Math.ceil(movement.length());
|
||||||
|
if (stepCount == 0) return; // No movement, so exit.
|
||||||
|
final Vector3f nextPos = new Vector3f(position);
|
||||||
|
final Vector3f lastPos = new Vector3f(position);
|
||||||
|
for (int i = 0; i < stepCount; i++) {
|
||||||
|
lastPos.set(nextPos);
|
||||||
|
nextPos.add(stepSize);
|
||||||
|
// If we shot past the next tick position, clamp it to that.
|
||||||
|
if (new Vector3f(nextPos).sub(position).length() > movement.length()) {
|
||||||
|
nextPos.set(nextTickPosition);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkFloorCollision(World world, Vector3f nextTickPosition, Vector3f movement) {
|
// Check if we collide with anything at this new position.
|
||||||
// If the player is moving up or not falling out of their current y level, no point in checking.
|
|
||||||
if (velocity.y >= 0 || Math.floor(position.y) == Math.floor(nextTickPosition.y)) return;
|
|
||||||
float dropHeight = Math.abs(movement.y);
|
float playerBodyPrevMinZ = lastPos.z - RADIUS;
|
||||||
int steps = (int) Math.ceil(dropHeight);
|
float playerBodyPrevMaxZ = lastPos.z + RADIUS;
|
||||||
// System.out.printf(" dropHeight=%.3f, steps=%d%n", dropHeight, steps);
|
float playerBodyPrevMinX = lastPos.x - RADIUS;
|
||||||
// Get a vector describing how much we move for each 1 unit Y decreases.
|
float playerBodyPrevMaxX = lastPos.x + RADIUS;
|
||||||
Vector3f stepSize = new Vector3f(movement).div(dropHeight);
|
float playerBodyPrevMinY = lastPos.y;
|
||||||
Vector3f potentialPosition = new Vector3f(position);
|
float playerBodyPrevMaxY = lastPos.y + height;
|
||||||
for (int i = 0; i < steps; i++) {
|
|
||||||
potentialPosition.add(stepSize);
|
float playerBodyMinZ = nextPos.z - RADIUS;
|
||||||
// System.out.printf(" Checking: %.3f, %.3f, %.3f%n", potentialPosition.x, potentialPosition.y, potentialPosition.z);
|
float playerBodyMaxZ = nextPos.z + RADIUS;
|
||||||
if (getHorizontalSpaceOccupied(potentialPosition).stream()
|
float playerBodyMinX = nextPos.x - RADIUS;
|
||||||
.anyMatch(p -> world.getBlockAt(p.x, potentialPosition.y, p.y) != 0)) {
|
float playerBodyMaxX = nextPos.x + RADIUS;
|
||||||
// System.out.println(" Occupied!");
|
float playerBodyMinY = nextPos.y;
|
||||||
position.y = Math.ceil(potentialPosition.y);
|
float playerBodyMaxY = nextPos.y + height;
|
||||||
velocity.y = 0;
|
|
||||||
movement.y = 0;
|
// Compute the bounds of all blocks the player is intersecting with.
|
||||||
updated = true;
|
int minX = (int) Math.floor(playerBodyMinX);
|
||||||
return; // Exit before doing any extra work.
|
int minZ = (int) Math.floor(playerBodyMinZ);
|
||||||
|
int minY = (int) Math.floor(playerBodyMinY);
|
||||||
|
int maxX = (int) Math.floor(playerBodyMaxX);
|
||||||
|
int maxZ = (int) Math.floor(playerBodyMaxZ);
|
||||||
|
int maxY = (int) Math.floor(playerBodyMaxY);
|
||||||
|
|
||||||
|
for (int x = minX; x <= maxX; x++) {
|
||||||
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
|
for (int y = minY; y <= maxY; y++) {
|
||||||
|
byte block = world.getBlockAt(x, y, z);
|
||||||
|
if (block <= 0) continue; // We're not colliding with this block.
|
||||||
|
float blockMinY = (float) y;
|
||||||
|
float blockMaxY = (float) y + 1;
|
||||||
|
float blockMinX = (float) x;
|
||||||
|
float blockMaxX = (float) x + 1;
|
||||||
|
float blockMinZ = (float) z;
|
||||||
|
float blockMaxZ = (float) z + 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving into the -Z side of a block:
|
||||||
|
- The player's max z position went from < blockMinZ to >= blockMinZ.
|
||||||
|
- The block to the -Z direction is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithNegativeZ = playerBodyPrevMaxZ < blockMinZ && playerBodyMaxZ >= blockMinZ && world.getBlockAt(x, y, z - 1) <= 0;
|
||||||
|
if (collidingWithNegativeZ) {
|
||||||
|
position.z = blockMinZ - RADIUS - delta;
|
||||||
|
velocity.z = 0;
|
||||||
|
movement.z = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving into the +Z side of a block:
|
||||||
|
- The player's min z position went from >= blockMaxZ to < blockMaxZ.
|
||||||
|
- The block to the +Z direction is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithPositiveZ = playerBodyPrevMinZ >= blockMaxZ && playerBodyMinZ < blockMaxZ && world.getBlockAt(x, y, z + 1) <= 0;
|
||||||
|
if (collidingWithPositiveZ) {
|
||||||
|
position.z = blockMaxZ + RADIUS + delta;
|
||||||
|
velocity.z = 0;
|
||||||
|
movement.z = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving into the -X side of a block:
|
||||||
|
- The player's max x position went from < blockMinX to >= blockMinX
|
||||||
|
- The block to the -X direction is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithNegativeX = playerBodyPrevMaxX < blockMinX && playerBodyMaxX >= blockMinX && world.getBlockAt(x - 1, y, z) <= 0;
|
||||||
|
if (collidingWithNegativeX) {
|
||||||
|
position.x = blockMinX - RADIUS - delta;
|
||||||
|
velocity.x = 0;
|
||||||
|
movement.x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving into the +X side of a block:
|
||||||
|
- The player's min x position went from >= blockMaxX to < blockMaxX.
|
||||||
|
- The block to the +X direction is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithPositiveX = playerBodyPrevMinX >= blockMaxX && playerBodyMinX < blockMaxX && world.getBlockAt(x + 1, y, z) <= 0;
|
||||||
|
if (collidingWithPositiveX) {
|
||||||
|
position.x = blockMaxX + RADIUS + delta;
|
||||||
|
velocity.x = 0;
|
||||||
|
movement.x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving down onto a block:
|
||||||
|
- The player's min y position went from >= blockMaxY to < blockMaxY
|
||||||
|
- The block above the current one is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithFloor = playerBodyPrevMinY >= blockMaxY && playerBodyMinY < blockMaxY && world.getBlockAt(x, y + 1, z) <= 0;
|
||||||
|
if (collidingWithFloor) {
|
||||||
|
position.y = blockMaxY;
|
||||||
|
velocity.y = 0;
|
||||||
|
movement.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving up into a block:
|
||||||
|
- The player's y position went from below blockMinY to >= blockMinY
|
||||||
|
- The block below the current one is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithCeiling = playerBodyPrevMaxY < blockMinY && playerBodyMaxY >= blockMinY && world.getBlockAt(x, y - 1, z) <= 0;
|
||||||
|
if (collidingWithCeiling) {
|
||||||
|
position.y = blockMinY - height - delta;
|
||||||
|
velocity.y = 0;
|
||||||
|
movement.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkCeilingCollision(World world, Vector3f nextTickPosition, Vector3f movement) {
|
public float getCurrentHeight() {
|
||||||
// If the player is moving down, or not moving out of their current y level, no point in checking.
|
return lastInputState.crouching() ? HEIGHT_CROUCH : HEIGHT;
|
||||||
if (velocity.y <= 0 || Math.floor(position.y) == Math.floor(nextTickPosition.y)) return;
|
|
||||||
float riseHeight = Math.abs(movement.y);
|
|
||||||
int steps = (int) Math.ceil(riseHeight);
|
|
||||||
Vector3f stepSize = new Vector3f(movement).div(riseHeight);
|
|
||||||
Vector3f potentialPosition = new Vector3f(position);
|
|
||||||
float playerHeight = lastInputState.crouching() ? HEIGHT_CROUCH : HEIGHT;
|
|
||||||
for (int i = 0; i < steps; i++) {
|
|
||||||
potentialPosition.add(stepSize);
|
|
||||||
if (getHorizontalSpaceOccupied(potentialPosition).stream()
|
|
||||||
.anyMatch(p -> world.getBlockAt(p.x, potentialPosition.y + playerHeight, p.y) != 0)) {
|
|
||||||
position.y = Math.floor(potentialPosition.y);
|
|
||||||
velocity.y = 0;
|
|
||||||
movement.y = 0;
|
|
||||||
updated = true;
|
|
||||||
return; // Exit before doing any extra work.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkWallCollision(World world, Vector3f nextTickPosition, Vector3f movement) {
|
public float getEyeHeight() {
|
||||||
// If the player isn't moving horizontally, no point in checking.
|
return lastInputState.crouching() ? EYE_HEIGHT_CROUCH : EYE_HEIGHT;
|
||||||
if (velocity.x == 0 && velocity.z == 0) return;
|
|
||||||
Vector3f potentialPosition = new Vector3f(position);
|
|
||||||
Vector3f stepSize = new Vector3f(movement).normalize(); // Step by 1 meter each time. This will guarantee we check everything, no matter what.
|
|
||||||
int steps = (int) Math.ceil(movement.length());
|
|
||||||
for (int i = 0; i < steps; i++) {
|
|
||||||
potentialPosition.add(stepSize);
|
|
||||||
float x = potentialPosition.x;
|
|
||||||
float y = potentialPosition.y + 1f;
|
|
||||||
float z = potentialPosition.z;
|
|
||||||
|
|
||||||
float borderMinZ = z - RADIUS;
|
|
||||||
float borderMaxZ = z + RADIUS;
|
|
||||||
float borderMinX = x - RADIUS;
|
|
||||||
float borderMaxX = x + RADIUS;
|
|
||||||
|
|
||||||
// -Z
|
|
||||||
if (world.getBlockAt(x, y, borderMinZ) != 0) {
|
|
||||||
System.out.println("-z");
|
|
||||||
position.z = Math.ceil(borderMinZ) + RADIUS;
|
|
||||||
velocity.z = 0;
|
|
||||||
movement.z = 0;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
// +Z
|
|
||||||
if (world.getBlockAt(x, y, borderMaxZ) != 0) {
|
|
||||||
System.out.println("+z");
|
|
||||||
position.z = Math.floor(borderMaxZ) - RADIUS;
|
|
||||||
velocity.z = 0;
|
|
||||||
movement.z = 0;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
// -X
|
|
||||||
if (world.getBlockAt(borderMinX, y, z) != 0) {
|
|
||||||
System.out.println("-x");
|
|
||||||
position.x = Math.ceil(borderMinX) + RADIUS;
|
|
||||||
velocity.x = 0;
|
|
||||||
movement.z = 0;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
// +X
|
|
||||||
if (world.getBlockAt(borderMaxX, y, z) != 0) {
|
|
||||||
System.out.println("+x");
|
|
||||||
position.x = Math.floor(borderMaxX) - RADIUS;
|
|
||||||
velocity.x = 0;
|
|
||||||
movement.x = 0;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ public class WorldUpdater implements Runnable {
|
||||||
|
|
||||||
private void tick() {
|
private void tick() {
|
||||||
for (var player : server.getPlayerManager().getPlayers()) {
|
for (var player : server.getPlayerManager().getPlayers()) {
|
||||||
player.tick(secondsPerTick, server.getWorld());
|
player.tick(secondsPerTick, server.getWorld(), server);
|
||||||
if (player.isUpdated()) server.getPlayerManager().broadcastUdpMessage(new PlayerUpdateMessage(
|
if (player.isUpdated()) server.getPlayerManager().broadcastUdpMessage(new PlayerUpdateMessage(
|
||||||
player.getId(),
|
player.getId(),
|
||||||
player.getPosition().x, player.getPosition().y, player.getPosition().z,
|
player.getPosition().x, player.getPosition().y, player.getPosition().z,
|
||||||
|
|
Loading…
Reference in New Issue