Added more preparations for true multiplayer functionality.
This commit is contained in:
parent
c81da242ef
commit
4ef8e88e81
|
@ -89,7 +89,13 @@ public class Camera {
|
||||||
setOrientation((float) Math.toRadians(x), (float) Math.toRadians(y));
|
setOrientation((float) Math.toRadians(x), (float) Math.toRadians(y));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateViewTransform() {
|
public void interpolatePosition(float dt) {
|
||||||
|
Vector3f movement = new Vector3f(velocity).mul(dt);
|
||||||
|
position.add(movement);
|
||||||
|
updateViewTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateViewTransform() {
|
||||||
viewTransform.identity();
|
viewTransform.identity();
|
||||||
viewTransform.rotate(-orientation.y + ((float) Math.PI / 2), RIGHT);
|
viewTransform.rotate(-orientation.y + ((float) Math.PI / 2), RIGHT);
|
||||||
viewTransform.rotate(-orientation.x, UP);
|
viewTransform.rotate(-orientation.x, UP);
|
||||||
|
|
|
@ -8,7 +8,6 @@ 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.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.Vector3f;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -27,15 +26,15 @@ public class Client implements Runnable {
|
||||||
private final GameRenderer gameRenderer;
|
private final GameRenderer gameRenderer;
|
||||||
|
|
||||||
private int clientId;
|
private int clientId;
|
||||||
private final World world;
|
private final ClientWorld world;
|
||||||
|
|
||||||
public Client(InetAddress serverAddress, int serverPort, String username) {
|
public Client(InetAddress serverAddress, int serverPort, String username) {
|
||||||
this.serverAddress = serverAddress;
|
this.serverAddress = serverAddress;
|
||||||
this.serverPort = serverPort;
|
this.serverPort = serverPort;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.communicationHandler = new CommunicationHandler(this);
|
this.communicationHandler = new CommunicationHandler(this);
|
||||||
this.gameRenderer = new GameRenderer();
|
this.world = new ClientWorld();
|
||||||
this.world = new World();
|
this.gameRenderer = new GameRenderer(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -58,10 +57,8 @@ public class Client implements Runnable {
|
||||||
while (!gameRenderer.windowShouldClose()) {
|
while (!gameRenderer.windowShouldClose()) {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
float dt = (now - lastFrameAt) / 1000f;
|
float dt = (now - lastFrameAt) / 1000f;
|
||||||
|
gameRenderer.getCamera().interpolatePosition(dt);
|
||||||
gameRenderer.draw();
|
gameRenderer.draw();
|
||||||
// Interpolate camera movement to make the game feel smooth.
|
|
||||||
Vector3f camMovement = new Vector3f(gameRenderer.getCamera().getVelocity()).mul(dt);
|
|
||||||
gameRenderer.getCamera().getPosition().add(camMovement);
|
|
||||||
lastFrameAt = now;
|
lastFrameAt = now;
|
||||||
}
|
}
|
||||||
gameRenderer.freeWindow();
|
gameRenderer.freeWindow();
|
||||||
|
@ -86,6 +83,7 @@ public class Client implements Runnable {
|
||||||
if (playerUpdate.clientId() == clientId) {
|
if (playerUpdate.clientId() == clientId) {
|
||||||
gameRenderer.getCamera().setPosition(playerUpdate.px(), playerUpdate.py() + 1.8f, playerUpdate.pz());
|
gameRenderer.getCamera().setPosition(playerUpdate.px(), playerUpdate.py() + 1.8f, 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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import nl.andrewl.aos2_client.CommunicationHandler;
|
||||||
import nl.andrewl.aos_core.net.udp.ClientOrientationState;
|
import nl.andrewl.aos_core.net.udp.ClientOrientationState;
|
||||||
import org.lwjgl.glfw.GLFWCursorPosCallbackI;
|
import org.lwjgl.glfw.GLFWCursorPosCallbackI;
|
||||||
|
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.glfwGetCursorPos;
|
import static org.lwjgl.glfw.GLFW.glfwGetCursorPos;
|
||||||
|
|
||||||
public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI {
|
public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI {
|
||||||
|
@ -40,7 +42,7 @@ public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI {
|
||||||
camera.setOrientation(camera.getOrientation().x - dx * mouseCursorSensitivity, camera.getOrientation().y - dy * mouseCursorSensitivity);
|
camera.setOrientation(camera.getOrientation().x - dx * mouseCursorSensitivity, camera.getOrientation().y - dy * mouseCursorSensitivity);
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (lastOrientationUpdateSentAt + ORIENTATION_UPDATE_LIMIT < now) {
|
if (lastOrientationUpdateSentAt + ORIENTATION_UPDATE_LIMIT < now) {
|
||||||
comm.sendDatagramPacket(new ClientOrientationState(comm.getClientId(), camera.getOrientation().x, camera.getOrientation().y));
|
ForkJoinPool.commonPool().submit(() -> comm.sendDatagramPacket(new ClientOrientationState(comm.getClientId(), camera.getOrientation().x, camera.getOrientation().y)));
|
||||||
lastOrientationUpdateSentAt = now;
|
lastOrientationUpdateSentAt = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package nl.andrewl.aos2_client.render;
|
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 static org.lwjgl.opengl.GL46.*;
|
import static org.lwjgl.opengl.GL46.*;
|
||||||
|
|
||||||
|
@ -16,9 +17,11 @@ public class ChunkMesh {
|
||||||
|
|
||||||
private final int[] positionData;
|
private final int[] positionData;
|
||||||
private final Chunk chunk;
|
private final Chunk chunk;
|
||||||
|
private final World world;
|
||||||
|
|
||||||
public ChunkMesh(Chunk chunk, ChunkMeshGenerator meshGenerator) {
|
public ChunkMesh(Chunk chunk, World world, ChunkMeshGenerator meshGenerator) {
|
||||||
this.chunk = chunk;
|
this.chunk = chunk;
|
||||||
|
this.world = world;
|
||||||
this.positionData = new int[]{chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z};
|
this.positionData = new int[]{chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z};
|
||||||
|
|
||||||
this.vboId = glGenBuffers();
|
this.vboId = glGenBuffers();
|
||||||
|
@ -39,7 +42,7 @@ public class ChunkMesh {
|
||||||
*/
|
*/
|
||||||
private void loadMesh(ChunkMeshGenerator meshGenerator) {
|
private void loadMesh(ChunkMeshGenerator meshGenerator) {
|
||||||
long start = System.nanoTime();
|
long start = System.nanoTime();
|
||||||
var meshData = meshGenerator.generateMesh(chunk);
|
var meshData = meshGenerator.generateMesh(chunk, world);
|
||||||
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.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package nl.andrewl.aos2_client.render;
|
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 org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.joml.Vector3i;
|
import org.joml.Vector3i;
|
||||||
import org.lwjgl.BufferUtils;
|
import org.lwjgl.BufferUtils;
|
||||||
|
@ -16,16 +17,19 @@ public final class ChunkMeshGenerator {
|
||||||
private final FloatBuffer vertexBuffer;
|
private final FloatBuffer vertexBuffer;
|
||||||
private final IntBuffer indexBuffer;
|
private final IntBuffer indexBuffer;
|
||||||
|
|
||||||
private final Vector3i pos = new Vector3i();
|
private final Vector3i pos = new Vector3i();// Pre-allocated vector to hold current local chunk block position.
|
||||||
private final Vector3f color = new Vector3f();
|
private final Vector3f color = new Vector3f();// Pre-allocated vector to hold current block color.
|
||||||
private final Vector3f norm = new Vector3f();
|
private final Vector3f norm = new Vector3f();// Pre-allocated vector to hold current face normal.
|
||||||
|
|
||||||
|
private final Vector3f checkPos = new Vector3f();// Pre-allocated vector to hold world block position.
|
||||||
|
private final Vector3i checkUtil = new Vector3i();// Pre-allocated utility vector to give to World for position stuff.
|
||||||
|
|
||||||
public ChunkMeshGenerator() {
|
public ChunkMeshGenerator() {
|
||||||
vertexBuffer = BufferUtils.createFloatBuffer(300_000);
|
vertexBuffer = BufferUtils.createFloatBuffer(300_000);
|
||||||
indexBuffer = BufferUtils.createIntBuffer(100_000);
|
indexBuffer = BufferUtils.createIntBuffer(100_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChunkMeshData generateMesh(Chunk chunk) {
|
public ChunkMeshData generateMesh(Chunk chunk, World world) {
|
||||||
vertexBuffer.clear();
|
vertexBuffer.clear();
|
||||||
indexBuffer.clear();
|
indexBuffer.clear();
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
|
@ -34,12 +38,15 @@ public final class ChunkMeshGenerator {
|
||||||
int x = pos.x;
|
int x = pos.x;
|
||||||
int y = pos.y;
|
int y = pos.y;
|
||||||
int z = pos.z;
|
int z = pos.z;
|
||||||
|
int worldX = Chunk.SIZE * chunk.getPosition().x + x;
|
||||||
|
int worldY = Chunk.SIZE * chunk.getPosition().y + y;
|
||||||
|
int worldZ = Chunk.SIZE * chunk.getPosition().z + z;
|
||||||
byte block = chunk.getBlocks()[i];
|
byte block = chunk.getBlocks()[i];
|
||||||
if (block <= 0) {
|
if (block <= 0) {
|
||||||
continue; // Don't render empty blocks.
|
continue; // Don't render empty blocks.
|
||||||
}
|
}
|
||||||
|
|
||||||
Chunk.getColor(block, color);
|
color.set(world.getPalette().getColor(block));
|
||||||
|
|
||||||
// See /design/block_rendering.svg for a diagram of how these vertices are defined.
|
// See /design/block_rendering.svg for a diagram of how these vertices are defined.
|
||||||
// var a = new Vector3f(x, y + 1, z + 1);
|
// var a = new Vector3f(x, y + 1, z + 1);
|
||||||
|
@ -52,7 +59,8 @@ public final class ChunkMeshGenerator {
|
||||||
// var h = new Vector3f(x + 1, y, z);
|
// var h = new Vector3f(x + 1, y, z);
|
||||||
|
|
||||||
// Top
|
// Top
|
||||||
if (chunk.getBlockAt(x, y + 1, z) == 0) {
|
checkPos.set(worldX, worldY + 1, worldZ);
|
||||||
|
if (world.getBlockAt(checkPos, checkUtil) == 0) {
|
||||||
norm.set(0, 1, 0);
|
norm.set(0, 1, 0);
|
||||||
genFace(idx,
|
genFace(idx,
|
||||||
x, y+1, z+1, // a
|
x, y+1, z+1, // a
|
||||||
|
@ -63,7 +71,8 @@ public final class ChunkMeshGenerator {
|
||||||
idx += 4;
|
idx += 4;
|
||||||
}
|
}
|
||||||
// Bottom
|
// Bottom
|
||||||
if (chunk.getBlockAt(x, y - 1, z) == 0) {
|
checkPos.set(worldX, worldY - 1, worldZ);
|
||||||
|
if (world.getBlockAt(checkPos, checkUtil) == 0) {
|
||||||
norm.set(0, -1, 0);// c h g d
|
norm.set(0, -1, 0);// c h g d
|
||||||
genFace(idx,
|
genFace(idx,
|
||||||
x, y, z, // c
|
x, y, z, // c
|
||||||
|
@ -74,7 +83,8 @@ public final class ChunkMeshGenerator {
|
||||||
idx += 4;
|
idx += 4;
|
||||||
}
|
}
|
||||||
// Positive z
|
// Positive z
|
||||||
if (chunk.getBlockAt(x, y, z + 1) == 0) {
|
checkPos.set(worldX, worldY, worldZ + 1);
|
||||||
|
if (world.getBlockAt(checkPos, checkUtil) == 0) {
|
||||||
norm.set(0, 0, 1);
|
norm.set(0, 0, 1);
|
||||||
genFace(idx,
|
genFace(idx,
|
||||||
x+1, y+1, z+1, // f
|
x+1, y+1, z+1, // f
|
||||||
|
@ -85,7 +95,8 @@ public final class ChunkMeshGenerator {
|
||||||
idx += 4;
|
idx += 4;
|
||||||
}
|
}
|
||||||
// Negative z
|
// Negative z
|
||||||
if (chunk.getBlockAt(x, y, z - 1) == 0) {
|
checkPos.set(worldX, worldY, worldZ - 1);
|
||||||
|
if (world.getBlockAt(checkPos, checkUtil) == 0) {
|
||||||
norm.set(0, 0, -1);
|
norm.set(0, 0, -1);
|
||||||
genFace(idx,
|
genFace(idx,
|
||||||
x, y+1, z, // b
|
x, y+1, z, // b
|
||||||
|
@ -96,7 +107,8 @@ public final class ChunkMeshGenerator {
|
||||||
idx += 4;
|
idx += 4;
|
||||||
}
|
}
|
||||||
// Positive x
|
// Positive x
|
||||||
if (chunk.getBlockAt(x + 1, y, z) == 0) {
|
checkPos.set(worldX + 1, worldY, worldZ);
|
||||||
|
if (world.getBlockAt(checkPos, checkUtil) == 0) {
|
||||||
norm.set(1, 0, 0);
|
norm.set(1, 0, 0);
|
||||||
genFace(idx,
|
genFace(idx,
|
||||||
x+1, y+1, z, // e
|
x+1, y+1, z, // e
|
||||||
|
@ -107,7 +119,8 @@ public final class ChunkMeshGenerator {
|
||||||
idx += 4;
|
idx += 4;
|
||||||
}
|
}
|
||||||
// Negative x
|
// Negative x
|
||||||
if (chunk.getBlockAt(x - 1, y, z) == 0) {
|
checkPos.set(worldX - 1, worldY, worldZ);
|
||||||
|
if (world.getBlockAt(checkPos, checkUtil) == 0) {
|
||||||
norm.set(-1, 0, 0);
|
norm.set(-1, 0, 0);
|
||||||
genFace(idx,
|
genFace(idx,
|
||||||
x, y+1, z+1, // a
|
x, y+1, z+1, // a
|
||||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewl.aos2_client.render;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.Camera;
|
import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos_core.model.Chunk;
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
|
import nl.andrewl.aos_core.model.World;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -50,9 +51,9 @@ public class ChunkRenderer {
|
||||||
glUniformMatrix4fv(projectionTransformUniform, false, projectionTransform.get(new float[16]));
|
glUniformMatrix4fv(projectionTransformUniform, false, projectionTransform.get(new float[16]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(Camera cam) {
|
public void draw(Camera cam, World world) {
|
||||||
while (!meshGenerationQueue.isEmpty()) {
|
while (!meshGenerationQueue.isEmpty()) {
|
||||||
chunkMeshes.add(new ChunkMesh(meshGenerationQueue.remove(), chunkMeshGenerator));
|
chunkMeshes.add(new ChunkMesh(meshGenerationQueue.remove(), world, chunkMeshGenerator));
|
||||||
}
|
}
|
||||||
shaderProgram.use();
|
shaderProgram.use();
|
||||||
glUniformMatrix4fv(viewTransformUniform, false, cam.getViewTransformData());
|
glUniformMatrix4fv(viewTransformUniform, false, cam.getViewTransformData());
|
||||||
|
|
|
@ -4,6 +4,7 @@ import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos2_client.control.PlayerInputKeyCallback;
|
import nl.andrewl.aos2_client.control.PlayerInputKeyCallback;
|
||||||
import nl.andrewl.aos2_client.control.PlayerViewCursorCallback;
|
import nl.andrewl.aos2_client.control.PlayerViewCursorCallback;
|
||||||
import nl.andrewl.aos_core.model.Chunk;
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
|
import nl.andrewl.aos_core.model.World;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.lwjgl.glfw.Callbacks;
|
import org.lwjgl.glfw.Callbacks;
|
||||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||||
|
@ -28,6 +29,7 @@ public class GameRenderer {
|
||||||
|
|
||||||
private final ChunkRenderer chunkRenderer;
|
private final ChunkRenderer chunkRenderer;
|
||||||
private final Camera camera;
|
private final Camera camera;
|
||||||
|
private final World world;
|
||||||
|
|
||||||
private long windowHandle;
|
private long windowHandle;
|
||||||
private GLFWVidMode primaryMonitorSettings;
|
private GLFWVidMode primaryMonitorSettings;
|
||||||
|
@ -38,7 +40,8 @@ public class GameRenderer {
|
||||||
|
|
||||||
private final Matrix4f perspectiveTransform;
|
private final Matrix4f perspectiveTransform;
|
||||||
|
|
||||||
public GameRenderer() {
|
public GameRenderer(World world) {
|
||||||
|
this.world = world;
|
||||||
this.chunkRenderer = new ChunkRenderer();
|
this.chunkRenderer = new ChunkRenderer();
|
||||||
this.camera = new Camera();
|
this.camera = new Camera();
|
||||||
this.perspectiveTransform = new Matrix4f();
|
this.perspectiveTransform = new Matrix4f();
|
||||||
|
@ -140,7 +143,7 @@ public class GameRenderer {
|
||||||
public void draw() {
|
public void draw() {
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
chunkRenderer.draw(camera);
|
chunkRenderer.draw(camera, world);
|
||||||
|
|
||||||
glfwSwapBuffers(windowHandle);
|
glfwSwapBuffers(windowHandle);
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package nl.andrewl.aos2_client.render;
|
||||||
|
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL46.*;
|
||||||
|
|
||||||
|
public class Mesh {
|
||||||
|
private final int vboId;
|
||||||
|
private final int vaoId;
|
||||||
|
private final int eboId;
|
||||||
|
private int indexCount;
|
||||||
|
private final Matrix4f transform = new Matrix4f();
|
||||||
|
private final float[] transformData = new float[16];
|
||||||
|
|
||||||
|
public Mesh(MeshData initialData) {
|
||||||
|
this.vboId = glGenBuffers();
|
||||||
|
this.eboId = glGenBuffers();
|
||||||
|
this.vaoId = glGenVertexArrays();
|
||||||
|
load(initialData);
|
||||||
|
initVertexArrayAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void load(MeshData data) {
|
||||||
|
indexCount = data.indexBuffer().limit();
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, data.vertexBuffer(), GL_STATIC_DRAW);
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||||
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.indexBuffer(), GL_STATIC_DRAW);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix4f getTransform() {
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateTransform() {
|
||||||
|
transform.set(transformData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getTransformData() {
|
||||||
|
return transformData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes this mesh's vertex array attribute settings.
|
||||||
|
*/
|
||||||
|
private void initVertexArrayAttributes() {
|
||||||
|
glBindVertexArray(vaoId);
|
||||||
|
// Vertex position floats.
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 3, GL_FLOAT, false, 9 * Float.BYTES, 0);
|
||||||
|
// Vertex color floats.
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(1, 3, GL_FLOAT, false, 9 * Float.BYTES, 3 * Float.BYTES);
|
||||||
|
// Vertex normal floats.
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glVertexAttribPointer(2, 3, GL_FLOAT, false, 9 * Float.BYTES, 6 * Float.BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw() {
|
||||||
|
glBindVertexArray(vaoId);
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||||
|
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void free() {
|
||||||
|
glDeleteBuffers(vboId);
|
||||||
|
glDeleteBuffers(eboId);
|
||||||
|
glDeleteVertexArrays(vaoId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package nl.andrewl.aos2_client.render;
|
||||||
|
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
import java.nio.IntBuffer;
|
||||||
|
|
||||||
|
public record MeshData(FloatBuffer vertexBuffer, IntBuffer indexBuffer) {}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package nl.andrewl.aos2_client.render;
|
||||||
|
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Vector3i;
|
||||||
|
import org.lwjgl.BufferUtils;
|
||||||
|
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
import java.nio.IntBuffer;
|
||||||
|
|
||||||
|
public class PlayerMeshGenerator {
|
||||||
|
private final FloatBuffer vertexBuffer;
|
||||||
|
private final IntBuffer indexBuffer;
|
||||||
|
|
||||||
|
private final Vector3i pos = new Vector3i();
|
||||||
|
private final Vector3f color = new Vector3f();
|
||||||
|
private final Vector3f norm = new Vector3f();
|
||||||
|
|
||||||
|
public PlayerMeshGenerator() {
|
||||||
|
vertexBuffer = BufferUtils.createFloatBuffer(1000);
|
||||||
|
indexBuffer = BufferUtils.createIntBuffer(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public PlayerMesh generateMesh() {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
}
|
|
@ -30,6 +30,8 @@ public final class Net {
|
||||||
serializer.registerType(8, ClientInputState.class);
|
serializer.registerType(8, ClientInputState.class);
|
||||||
serializer.registerType(9, ClientOrientationState.class);
|
serializer.registerType(9, ClientOrientationState.class);
|
||||||
serializer.registerType(10, PlayerUpdateMessage.class);
|
serializer.registerType(10, PlayerUpdateMessage.class);
|
||||||
|
serializer.registerType(11, PlayerJoinMessage.class);
|
||||||
|
serializer.registerType(12, PlayerLeaveMessage.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package nl.andrewl.aos_core.model;
|
||||||
|
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A palette of 127 colors that can be used for coloring a world.
|
||||||
|
*/
|
||||||
|
public class ColorPalette {
|
||||||
|
public static final int MAX_COLORS = 127;
|
||||||
|
|
||||||
|
private final Vector3f[] colors = new Vector3f[MAX_COLORS];
|
||||||
|
|
||||||
|
public ColorPalette() {
|
||||||
|
for (int i = 0; i < MAX_COLORS; i++) {
|
||||||
|
colors[i] = new Vector3f();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3f getColor(byte value) {
|
||||||
|
if (value < 0) return null;
|
||||||
|
return colors[value - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(byte value, float r, float g, float b) {
|
||||||
|
if (value < 0) return;
|
||||||
|
colors[value - 1].set(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ColorPalette grayscale() {
|
||||||
|
ColorPalette palette = new ColorPalette();
|
||||||
|
for (int i = 0; i < MAX_COLORS; i++) {
|
||||||
|
palette.colors[i].set((float) i / MAX_COLORS);
|
||||||
|
}
|
||||||
|
return palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ColorPalette rainbow() {
|
||||||
|
ColorPalette palette = new ColorPalette();
|
||||||
|
for (int i = 0; i < MAX_COLORS; i++) {
|
||||||
|
Color c = Color.getHSBColor((float) i / MAX_COLORS, 0.8f, 0.8f);
|
||||||
|
float[] values = c.getRGBColorComponents(null);
|
||||||
|
palette.colors[i].x = values[0];
|
||||||
|
palette.colors[i].y = values[1];
|
||||||
|
palette.colors[i].z = values[2];
|
||||||
|
}
|
||||||
|
return palette;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,12 +17,12 @@ public class Player {
|
||||||
* then the player's y coordinate is y=6.0. The x and z coordinates are
|
* then the player's y coordinate is y=6.0. The x and z coordinates are
|
||||||
* simply the center of the player.
|
* simply the center of the player.
|
||||||
*/
|
*/
|
||||||
private final Vector3f position;
|
protected final Vector3f position;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The player's velocity in each of the coordinate axes.
|
* The player's velocity in each of the coordinate axes.
|
||||||
*/
|
*/
|
||||||
private final Vector3f velocity;
|
protected final Vector3f velocity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The player's orientation. The x component refers to rotation about the
|
* The player's orientation. The x component refers to rotation about the
|
||||||
|
@ -33,17 +33,17 @@ public class Player {
|
||||||
* The y component is limited to between 0 and PI, with y=0 looking
|
* The y component is limited to between 0 and PI, with y=0 looking
|
||||||
* straight down, and y=PI looking straight up.
|
* straight down, and y=PI looking straight up.
|
||||||
*/
|
*/
|
||||||
private final Vector2f orientation;
|
protected final Vector2f orientation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A vector that's internally re-computed each time the player's
|
* A vector that's internally re-computed each time the player's
|
||||||
* orientation changes, and represents unit vector pointing in the
|
* orientation changes, and represents unit vector pointing in the
|
||||||
* direction the player is looking.
|
* direction the player is looking.
|
||||||
*/
|
*/
|
||||||
private final Vector3f viewVector;
|
protected final Vector3f viewVector;
|
||||||
|
|
||||||
private final String username;
|
protected final String username;
|
||||||
private final int id;
|
protected final int id;
|
||||||
|
|
||||||
public Player(int id, String username) {
|
public Player(int id, String username) {
|
||||||
this.position = new Vector3f();
|
this.position = new Vector3f();
|
||||||
|
|
|
@ -14,25 +14,40 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class World {
|
public class World {
|
||||||
protected final Map<Vector3ic, Chunk> chunkMap = new HashMap<>();
|
protected final Map<Vector3ic, Chunk> chunkMap = new HashMap<>();
|
||||||
|
protected final ColorPalette palette = ColorPalette.rainbow();
|
||||||
|
|
||||||
public void addChunk(Chunk chunk) {
|
public void addChunk(Chunk chunk) {
|
||||||
chunkMap.put(chunk.getPosition(), chunk);
|
chunkMap.put(chunk.getPosition(), chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeChunk(Vector3i chunkPos) {
|
||||||
|
chunkMap.remove(chunkPos);
|
||||||
|
}
|
||||||
|
|
||||||
public Map<Vector3ic, Chunk> getChunkMap() {
|
public Map<Vector3ic, Chunk> getChunkMap() {
|
||||||
return chunkMap;
|
return chunkMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ColorPalette getPalette() {
|
||||||
|
return palette;
|
||||||
|
}
|
||||||
|
|
||||||
public byte getBlockAt(Vector3f pos) {
|
public byte getBlockAt(Vector3f pos) {
|
||||||
Vector3i chunkPos = getChunkPosAt(pos);
|
return getBlockAt(pos, new Vector3i());
|
||||||
Chunk chunk = chunkMap.get(chunkPos);
|
}
|
||||||
|
|
||||||
|
public byte getBlockAt(Vector3f pos, Vector3i util) {
|
||||||
|
getChunkPosAt(pos, util);
|
||||||
|
Chunk chunk = chunkMap.get(util);
|
||||||
if (chunk == null) return 0;
|
if (chunk == null) return 0;
|
||||||
Vector3i blockPos = new Vector3i(
|
util.x = (int) Math.floor(pos.x - util.x * Chunk.SIZE);
|
||||||
(int) Math.floor(pos.x - chunkPos.x * Chunk.SIZE),
|
util.y = (int) Math.floor(pos.y - util.y * Chunk.SIZE);
|
||||||
(int) Math.floor(pos.y - chunkPos.y * Chunk.SIZE),
|
util.z = (int) Math.floor(pos.z - util.z * Chunk.SIZE);
|
||||||
(int) Math.floor(pos.z - chunkPos.z * Chunk.SIZE)
|
return chunk.getBlockAt(util);
|
||||||
);
|
}
|
||||||
return chunk.getBlockAt(blockPos);
|
|
||||||
|
public byte getBlockAt(float x, float y, float z) {
|
||||||
|
return getBlockAt(new Vector3f(x, y, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBlockAt(Vector3f pos, byte block) {
|
public void setBlockAt(Vector3f pos, byte block) {
|
||||||
|
@ -47,18 +62,18 @@ public class World {
|
||||||
chunk.setBlockAt(blockPos.x, blockPos.y, blockPos.z, block);
|
chunk.setBlockAt(blockPos.x, blockPos.y, blockPos.z, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getBlockAt(int x, int y, int z) {
|
// public byte getBlockAt(int x, int y, int z) {
|
||||||
int chunkX = x / Chunk.SIZE;
|
//// int chunkX = x / Chunk.SIZE;
|
||||||
int localX = x % Chunk.SIZE;
|
//// int localX = x % Chunk.SIZE;
|
||||||
int chunkY = y / Chunk.SIZE;
|
//// int chunkY = y / Chunk.SIZE;
|
||||||
int localY = y % Chunk.SIZE;
|
//// int localY = y % Chunk.SIZE;
|
||||||
int chunkZ = z / Chunk.SIZE;
|
//// int chunkZ = z / Chunk.SIZE;
|
||||||
int localZ = z % Chunk.SIZE;
|
//// int localZ = z % Chunk.SIZE;
|
||||||
Vector3i chunkPos = new Vector3i(chunkX, chunkY, chunkZ);
|
//// Vector3i chunkPos = new Vector3i(chunkX, chunkY, chunkZ);
|
||||||
Chunk chunk = chunkMap.get(chunkPos);
|
//// Chunk chunk = chunkMap.get(chunkPos);
|
||||||
if (chunk == null) return 0;
|
//// if (chunk == null) return 0;
|
||||||
return chunk.getBlockAt(localX, localY, localZ);
|
//// return chunk.getBlockAt(localX, localY, localZ);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public Chunk getChunkAt(Vector3i chunkPos) {
|
public Chunk getChunkAt(Vector3i chunkPos) {
|
||||||
return chunkMap.get(chunkPos);
|
return chunkMap.get(chunkPos);
|
||||||
|
@ -70,10 +85,13 @@ public class World {
|
||||||
* @return The chunk position. Note that this may not correspond to any existing chunk.
|
* @return The chunk position. Note that this may not correspond to any existing chunk.
|
||||||
*/
|
*/
|
||||||
public static Vector3i getChunkPosAt(Vector3f worldPos) {
|
public static Vector3i getChunkPosAt(Vector3f worldPos) {
|
||||||
return new Vector3i(
|
return getChunkPosAt(worldPos, new Vector3i());
|
||||||
(int) Math.floor(worldPos.x / Chunk.SIZE),
|
}
|
||||||
(int) Math.floor(worldPos.y / Chunk.SIZE),
|
|
||||||
(int) Math.floor(worldPos.z / Chunk.SIZE)
|
public static Vector3i getChunkPosAt(Vector3f worldPos, Vector3i dest) {
|
||||||
);
|
dest.x = (int) Math.floor(worldPos.x / Chunk.SIZE);
|
||||||
|
dest.y = (int) Math.floor(worldPos.y / Chunk.SIZE);
|
||||||
|
dest.z = (int) Math.floor(worldPos.z / Chunk.SIZE);
|
||||||
|
return dest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package nl.andrewl.aos_core.net;
|
||||||
|
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An announcement message that's broadcast to all players when a new player
|
||||||
|
* joins, so that they can add that player to their world.
|
||||||
|
*/
|
||||||
|
public record PlayerJoinMessage(
|
||||||
|
int id, String username,
|
||||||
|
float px, float py, float pz,
|
||||||
|
float vx, float vy, float vz,
|
||||||
|
float ox, float oy
|
||||||
|
) implements Message {}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package nl.andrewl.aos_core.net;
|
||||||
|
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Announcement that's sent when a player leaves, so that all clients can stop
|
||||||
|
* rendering the player.
|
||||||
|
*/
|
||||||
|
public record PlayerLeaveMessage(int id) implements Message {
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ public class WorldTest {
|
||||||
assertEquals(1, world.getBlockAt(new Vector3f(1, 0, 0)));
|
assertEquals(1, world.getBlockAt(new Vector3f(1, 0, 0)));
|
||||||
assertEquals(1, world.getBlockAt(new Vector3f(1.9f, 0, 0)));
|
assertEquals(1, world.getBlockAt(new Vector3f(1.9f, 0, 0)));
|
||||||
assertEquals(1, world.getBlockAt(new Vector3f(1.5f, 0.7f, 0.3f)));
|
assertEquals(1, world.getBlockAt(new Vector3f(1.5f, 0.7f, 0.3f)));
|
||||||
|
assertEquals(0, world.getBlockAt(new Vector3f(2f, 0.7f, 0.3f)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -84,4 +84,18 @@ public class PlayerManager {
|
||||||
log.warn("An error occurred while broadcasting a UDP message.", e);
|
log.warn("An error occurred while broadcasting a UDP message.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void broadcastUdpMessageToAllBut(Message msg, ServerPlayer player) {
|
||||||
|
try {
|
||||||
|
byte[] data = Net.write(msg);
|
||||||
|
DatagramPacket packet = new DatagramPacket(data, data.length);
|
||||||
|
for (var entry : clientHandlers.entrySet()) {
|
||||||
|
if (entry.getKey() != player.getId()) {
|
||||||
|
entry.getValue().sendDatagramPacket(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("An error occurred while broadcasting a UDP message.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.model.Chunk;
|
||||||
import nl.andrewl.aos_core.model.World;
|
import nl.andrewl.aos_core.model.World;
|
||||||
import nl.andrewl.aos_core.model.WorldIO;
|
import nl.andrewl.aos_core.model.WorldIO;
|
||||||
import nl.andrewl.aos_core.net.UdpReceiver;
|
import nl.andrewl.aos_core.net.UdpReceiver;
|
||||||
|
@ -8,12 +9,14 @@ import nl.andrewl.aos_core.net.udp.ClientOrientationState;
|
||||||
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
||||||
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.Vector3f;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
public class Server implements Runnable {
|
public class Server implements Runnable {
|
||||||
|
@ -36,26 +39,57 @@ public class Server implements Runnable {
|
||||||
this.worldUpdater = new WorldUpdater(this, 20);
|
this.worldUpdater = new WorldUpdater(this, 20);
|
||||||
|
|
||||||
// Generate world. TODO: do this elsewhere.
|
// Generate world. TODO: do this elsewhere.
|
||||||
// Random rand = new Random(1);
|
Random rand = new Random(1);
|
||||||
// this.world = new World();
|
this.world = new World();
|
||||||
// for (int x = -5; x <= 5; x++) {
|
for (int x = -5; x <= 5; x++) {
|
||||||
// for (int y = 0; y <= 5; y++) {
|
for (int y = 0; y <= 5; y++) {
|
||||||
// for (int z = -3; z <= 3; z++) {
|
for (int z = -3; z <= 3; z++) {
|
||||||
// Chunk chunk = new Chunk(x, y, z);
|
Chunk chunk = new Chunk(x, y, z);
|
||||||
// if (y <= 3) {
|
if (y <= 3) {
|
||||||
// for (int i = 0; i < Chunk.TOTAL_SIZE; i++) {
|
for (int i = 0; i < Chunk.TOTAL_SIZE; i++) {
|
||||||
// chunk.getBlocks()[i] = (byte) rand.nextInt(20, 40);
|
chunk.getBlocks()[i] = (byte) rand.nextInt(20, 40);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// world.addChunk(chunk);
|
world.addChunk(chunk);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// world.setBlockAt(new Vector3f(5, 64, 5), (byte) 50);
|
world.setBlockAt(new Vector3f(5, 64, 5), (byte) 50);
|
||||||
// world.setBlockAt(new Vector3f(5, 65, 6), (byte) 50);
|
world.setBlockAt(new Vector3f(5, 64, 6), (byte) 50);
|
||||||
// world.setBlockAt(new Vector3f(5, 66, 7), (byte) 50);
|
world.setBlockAt(new Vector3f(5, 64, 7), (byte) 50);
|
||||||
// WorldIO.write(world, Path.of("testworld"));
|
world.setBlockAt(new Vector3f(5, 65, 6), (byte) 50);
|
||||||
this.world = WorldIO.read(Path.of("testworld"));
|
world.setBlockAt(new Vector3f(5, 66, 7), (byte) 50);
|
||||||
|
world.setBlockAt(new Vector3f(5, 65, 7), (byte) 50);
|
||||||
|
world.setBlockAt(new Vector3f(5, 67, 8), (byte) 50);
|
||||||
|
world.setBlockAt(new Vector3f(6, 67, 8), (byte) 50);
|
||||||
|
world.setBlockAt(new Vector3f(7, 67, 8), (byte) 50);
|
||||||
|
world.setBlockAt(new Vector3f(5, 67, 9), (byte) 50);
|
||||||
|
world.setBlockAt(new Vector3f(6, 67, 9), (byte) 50);
|
||||||
|
world.setBlockAt(new Vector3f(7, 67, 9), (byte) 50);
|
||||||
|
|
||||||
|
for (int z = 0; z > -20; z--) {
|
||||||
|
world.setBlockAt(new Vector3f(0, 63, z), (byte) 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int x = 0; x < 10; x++) {
|
||||||
|
world.setBlockAt(new Vector3f(x - 5, 64, 3), (byte) 80);
|
||||||
|
world.setBlockAt(new Vector3f(x - 5, 65, 3), (byte) 80);
|
||||||
|
world.setBlockAt(new Vector3f(x - 5, 66, 3), (byte) 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int z = 0; z < 10; z++) {
|
||||||
|
world.setBlockAt(new Vector3f(20, 64, z), (byte) 80);
|
||||||
|
world.setBlockAt(new Vector3f(20, 65, z), (byte) 80);
|
||||||
|
world.setBlockAt(new Vector3f(20, 66, z), (byte) 80);
|
||||||
|
}
|
||||||
|
world.setBlockAt(new Vector3f(21, 64, 6), (byte) 1);
|
||||||
|
|
||||||
|
for (int x = 0; x < 127; x++) {
|
||||||
|
world.setBlockAt(new Vector3f(x - 50, 63, -15), (byte) x);
|
||||||
|
}
|
||||||
|
|
||||||
|
WorldIO.write(world, Path.of("testworld"));
|
||||||
|
// this.world = WorldIO.read(Path.of("testworld"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -89,7 +123,7 @@ public class Server implements Runnable {
|
||||||
ServerPlayer player = playerManager.getPlayer(orientationState.clientId());
|
ServerPlayer player = playerManager.getPlayer(orientationState.clientId());
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
player.setOrientation(orientationState.x(), orientationState.y());
|
player.setOrientation(orientationState.x(), orientationState.y());
|
||||||
playerManager.broadcastUdpMessage(new PlayerUpdateMessage(player));
|
playerManager.broadcastUdpMessageToAllBut(new PlayerUpdateMessage(player), player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,36 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
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.net.udp.ClientInputState;
|
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
||||||
|
import org.joml.Math;
|
||||||
|
import org.joml.Vector2i;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Vector3fc;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class ServerPlayer extends Player {
|
public class ServerPlayer extends Player {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ServerPlayer.class);
|
||||||
|
|
||||||
|
public static final float HEIGHT = 1.8f;
|
||||||
|
public static final float HEIGHT_CROUCH = 1.4f;
|
||||||
|
public static final float WIDTH = 0.75f;
|
||||||
|
public static final float RADIUS = WIDTH / 2f;
|
||||||
|
|
||||||
|
public static final float GRAVITY = 9.81f * 3;
|
||||||
|
public static final float SPEED_NORMAL = 4f;
|
||||||
|
public static final float SPEED_CROUCH = 1.5f;
|
||||||
|
public static final float SPEED_SPRINT = 9f;
|
||||||
|
public static final float MOVEMENT_ACCELERATION = 5f;
|
||||||
|
public static final float MOVEMENT_DECELERATION = 2f;
|
||||||
|
public static final float JUMP_SPEED = 8f;
|
||||||
|
|
||||||
private ClientInputState lastInputState;
|
private ClientInputState lastInputState;
|
||||||
|
private boolean updated = false;
|
||||||
|
|
||||||
public ServerPlayer(int id, String username) {
|
public ServerPlayer(int id, String username) {
|
||||||
super(id, username);
|
super(id, username);
|
||||||
|
@ -19,4 +45,188 @@ public class ServerPlayer extends Player {
|
||||||
public void setLastInputState(ClientInputState inputState) {
|
public void setLastInputState(ClientInputState inputState) {
|
||||||
this.lastInputState = inputState;
|
this.lastInputState = inputState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isUpdated() {
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick(float dt, World world) {
|
||||||
|
// log.info("Ticking player " + id);
|
||||||
|
updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick.
|
||||||
|
|
||||||
|
checkBlockCollisions(dt, world);
|
||||||
|
|
||||||
|
if (isGrounded(world)) {
|
||||||
|
// System.out.println("g");
|
||||||
|
tickHorizontalVelocity();
|
||||||
|
if (lastInputState.jumping()) velocity.y = JUMP_SPEED;
|
||||||
|
} else {
|
||||||
|
velocity.y -= GRAVITY * dt;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply updated velocity to the player.
|
||||||
|
if (velocity.lengthSquared() > 0) {
|
||||||
|
Vector3f scaledVelocity = new Vector3f(velocity).mul(dt);
|
||||||
|
position.add(scaledVelocity);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.out.printf("pos: [%.3f, %.3f, %.3f]%n", position.x, position.y, position.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tickHorizontalVelocity() {
|
||||||
|
Vector3f horizontalVelocity = new Vector3f(
|
||||||
|
velocity.x == velocity.x ? velocity.x : 0f,
|
||||||
|
0,
|
||||||
|
velocity.z == velocity.z ? velocity.z : 0f
|
||||||
|
);
|
||||||
|
Vector3f acceleration = new Vector3f(0);
|
||||||
|
if (lastInputState.forward()) acceleration.z -= 1;
|
||||||
|
if (lastInputState.backward()) acceleration.z += 1;
|
||||||
|
if (lastInputState.left()) acceleration.x -= 1;
|
||||||
|
if (lastInputState.right()) acceleration.x += 1;
|
||||||
|
if (acceleration.lengthSquared() > 0) {
|
||||||
|
acceleration.normalize();
|
||||||
|
acceleration.rotateAxis(orientation.x, 0, 1, 0);
|
||||||
|
acceleration.mul(MOVEMENT_ACCELERATION);
|
||||||
|
horizontalVelocity.add(acceleration);
|
||||||
|
final float maxSpeed;
|
||||||
|
if (lastInputState.crouching()) {
|
||||||
|
maxSpeed = SPEED_CROUCH;
|
||||||
|
} else if (lastInputState.sprinting()) {
|
||||||
|
maxSpeed = SPEED_SPRINT;
|
||||||
|
} else {
|
||||||
|
maxSpeed = SPEED_NORMAL;
|
||||||
|
}
|
||||||
|
if (horizontalVelocity.length() > maxSpeed) {
|
||||||
|
horizontalVelocity.normalize(maxSpeed);
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
} else if (horizontalVelocity.lengthSquared() > 0) {
|
||||||
|
Vector3f deceleration = new Vector3f(horizontalVelocity)
|
||||||
|
.negate().normalize()
|
||||||
|
.mul(Math.min(horizontalVelocity.length(), MOVEMENT_DECELERATION));
|
||||||
|
horizontalVelocity.add(deceleration);
|
||||||
|
if (horizontalVelocity.length() < 0.1f) {
|
||||||
|
horizontalVelocity.set(0);
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the player's velocity with what we've computed.
|
||||||
|
velocity.x = horizontalVelocity.x;
|
||||||
|
velocity.z = horizontalVelocity.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isGrounded(World world) {
|
||||||
|
// Player must be flat on the top of a block.
|
||||||
|
if (Math.floor(position.y) != position.y) return false;
|
||||||
|
// Check to see if there's a block under any of the spaces the player is over.
|
||||||
|
return getHorizontalSpaceOccupied().stream()
|
||||||
|
.anyMatch(point -> world.getBlockAt(point.x, position.y - 0.1f, point.y) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Vector2i> getHorizontalSpaceOccupied() {
|
||||||
|
// Get the list of 2d x,z coordinates that we overlap with.
|
||||||
|
List<Vector2i> points = new ArrayList<>(4); // Due to the size of radius, there can only be a max of 4 blocks.
|
||||||
|
int minX = (int) Math.floor(position.x - RADIUS);
|
||||||
|
int minZ = (int) Math.floor(position.z - RADIUS);
|
||||||
|
int maxX = (int) Math.floor(position.x + RADIUS);
|
||||||
|
int maxZ = (int) Math.floor(position.z + RADIUS);
|
||||||
|
for (int x = minX; x <= maxX; x++) {
|
||||||
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
|
points.add(new Vector2i(x, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBlockCollisions(float dt, World world) {
|
||||||
|
final Vector3fc nextTickPosition = new Vector3f(position).add(new Vector3f(velocity).mul(dt));
|
||||||
|
List<Vector2i> horizontalSpaces = getHorizontalSpaceOccupied();
|
||||||
|
int minXNextTick = (int) Math.floor(nextTickPosition.x() - RADIUS);
|
||||||
|
int minZNextTick = (int) Math.floor(nextTickPosition.z() - RADIUS);
|
||||||
|
int maxXNextTick = (int) Math.floor(nextTickPosition.x() + RADIUS);
|
||||||
|
int maxZNextTick = (int) Math.floor(nextTickPosition.z() + RADIUS);
|
||||||
|
|
||||||
|
// Check if the player is about to hit a wall.
|
||||||
|
// -Z
|
||||||
|
if (
|
||||||
|
world.getBlockAt(nextTickPosition.x(), nextTickPosition.y(), minZNextTick) != 0 &&
|
||||||
|
world.getBlockAt(nextTickPosition.x(), nextTickPosition.y() + 1, minZNextTick) != 0
|
||||||
|
) {
|
||||||
|
System.out.println("wall -z");
|
||||||
|
position.z = ((float) minZNextTick) + RADIUS + 0.001f;
|
||||||
|
velocity.z = 0;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
// +Z
|
||||||
|
if (
|
||||||
|
world.getBlockAt(nextTickPosition.x(), nextTickPosition.y(), maxZNextTick) != 0 &&
|
||||||
|
world.getBlockAt(nextTickPosition.x(), nextTickPosition.y() + 1, maxZNextTick) != 0
|
||||||
|
) {
|
||||||
|
System.out.println("wall +z");
|
||||||
|
position.z = ((float) maxZNextTick) - RADIUS - 0.001f;
|
||||||
|
velocity.z = 0;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
// -X
|
||||||
|
if (
|
||||||
|
world.getBlockAt(minXNextTick, nextTickPosition.y(), nextTickPosition.z()) != 0 &&
|
||||||
|
world.getBlockAt(minXNextTick, nextTickPosition.y() + 1, nextTickPosition.z()) != 0
|
||||||
|
) {
|
||||||
|
System.out.println("wall -x");
|
||||||
|
position.x = ((float) minXNextTick) + RADIUS + 0.001f;
|
||||||
|
velocity.x = 0;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
// +X
|
||||||
|
if (
|
||||||
|
world.getBlockAt(maxXNextTick, nextTickPosition.y(), nextTickPosition.z()) != 0 &&
|
||||||
|
world.getBlockAt(maxXNextTick, nextTickPosition.y() + 1, nextTickPosition.z()) != 0
|
||||||
|
) {
|
||||||
|
System.out.println("wall +x");
|
||||||
|
position.x = ((float) maxXNextTick) - RADIUS - 0.001f;
|
||||||
|
velocity.x = 0;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the player is going to hit a ceiling on the next tick, and cancel velocity and set position.
|
||||||
|
final float nextTickHeadY = nextTickPosition.y() + (lastInputState.crouching() ? HEIGHT_CROUCH : HEIGHT);
|
||||||
|
boolean playerWillHitCeiling = horizontalSpaces.stream()
|
||||||
|
.anyMatch(point -> world.getBlockAt(point.x, nextTickHeadY, point.y) != 0);
|
||||||
|
if (playerWillHitCeiling) {
|
||||||
|
position.y = Math.floor(nextTickPosition.y());
|
||||||
|
if (velocity.y > 0) velocity.y = 0;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the player is in the ground, or will be on the next tick, then move them up to the first valid space.
|
||||||
|
boolean playerFootInBlock = horizontalSpaces.stream()
|
||||||
|
.anyMatch(point -> world.getBlockAt(point.x, position.y, point.y) != 0 ||
|
||||||
|
world.getBlockAt(point.x, nextTickPosition.y(), point.y) != 0);
|
||||||
|
if (playerFootInBlock) {
|
||||||
|
// System.out.println("Player foot in block.");
|
||||||
|
int nextY = (int) Math.floor(nextTickPosition.y());
|
||||||
|
while (true) {
|
||||||
|
// System.out.println("Checking y = " + nextY);
|
||||||
|
int finalNextY = nextY;
|
||||||
|
boolean isOpen = horizontalSpaces.stream()
|
||||||
|
.allMatch(point -> {
|
||||||
|
// System.out.printf("[%d, %d, %d] -> %d%n", point.x, finalNextY, point.y, world.getBlockAt(point.x, finalNextY, point.y));
|
||||||
|
return world.getBlockAt(point.x, finalNextY, point.y) == 0;
|
||||||
|
});
|
||||||
|
if (isOpen) {
|
||||||
|
// System.out.println("It's clear to move player to y = " + nextY);
|
||||||
|
// Move the player to that spot, and cancel out their velocity.
|
||||||
|
position.y = nextY;
|
||||||
|
velocity.y = 0;
|
||||||
|
updated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nextY++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,8 @@ public class WorldUpdater implements Runnable {
|
||||||
|
|
||||||
private void tick() {
|
private void tick() {
|
||||||
for (var player : server.getPlayerManager().getPlayers()) {
|
for (var player : server.getPlayerManager().getPlayers()) {
|
||||||
updatePlayerMovement(player);
|
player.tick(secondsPerTick, server.getWorld());
|
||||||
|
if (player.isUpdated()) server.getPlayerManager().broadcastUdpMessage(new PlayerUpdateMessage(player));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ public class WorldUpdater implements Runnable {
|
||||||
boolean grounded = (Math.floor(p.y) == p.y && server.getWorld().getBlockAt(new Vector3f(p.x, p.y - 0.0001f, p.z)) != 0);
|
boolean grounded = (Math.floor(p.y) == p.y && server.getWorld().getBlockAt(new Vector3f(p.x, p.y - 0.0001f, p.z)) != 0);
|
||||||
|
|
||||||
if (!grounded) {
|
if (!grounded) {
|
||||||
v.y -= 3f;
|
v.y -= 9.81f * secondsPerTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply horizontal deceleration to the player before computing any input-derived acceleration.
|
// Apply horizontal deceleration to the player before computing any input-derived acceleration.
|
||||||
|
|
Loading…
Reference in New Issue