From 2afeee50d5580ba01bf67deee18ea0698b44e172 Mon Sep 17 00:00:00 2001 From: MDietermann Date: Mon, 1 Jun 2026 10:37:42 +0200 Subject: [PATCH] feat: add Player class wrapping Camera with chunk-coordinate accessor --- Game/Player.cs | 45 +++++++++++++++++++++++++++ VoxelEngine.Tests/PlayerTests.cs | 53 ++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 Game/Player.cs create mode 100644 VoxelEngine.Tests/PlayerTests.cs diff --git a/Game/Player.cs b/Game/Player.cs new file mode 100644 index 0000000..94fd03e --- /dev/null +++ b/Game/Player.cs @@ -0,0 +1,45 @@ +using OpenTK.Mathematics; +using VoxelEngine.Core; +using VoxelEngine.Rendering; +using VoxelChunk = VoxelEngine.World.Chunk.Chunk; + +namespace VoxelEngine.Game; + +public sealed class Player +{ + private readonly Configuration _cfg; + + public Camera Camera { get; } + public Vector3 Position => this.Camera.Position; + public float MoveSpeed => this._cfg.MoveSpeed; + public float MouseSensitivity => this._cfg.MouseSensitivity; + + public Vector3i ChunkPosition + { + get + { + Vector3 p = this.Position; + return new Vector3i( + Player.FloorDiv((int)MathF.Floor(p.X), VoxelChunk.Size), + Player.FloorDiv((int)MathF.Floor(p.Y), VoxelChunk.Size), + Player.FloorDiv((int)MathF.Floor(p.Z), VoxelChunk.Size)); + } + } + + public Player(Configuration cfg, Vector3 spawnPosition, float aspectRatio) + { + this._cfg = cfg; + this.Camera = new Camera(spawnPosition, aspectRatio) { Fov = cfg.Fov }; + } + + public void Move(Vector3 delta) => this.Camera.Move(delta); + + public void AddRotation(float yawDelta, float pitchDelta) => this.Camera.AddRotation(yawDelta, pitchDelta); + + private static int FloorDiv(int value, int size) + { + int q = value / size; + if (value % size != 0 && value < 0) q--; + return q; + } +} diff --git a/VoxelEngine.Tests/PlayerTests.cs b/VoxelEngine.Tests/PlayerTests.cs new file mode 100644 index 0000000..fe38936 --- /dev/null +++ b/VoxelEngine.Tests/PlayerTests.cs @@ -0,0 +1,53 @@ +using OpenTK.Mathematics; +using VoxelEngine.Core; +using VoxelEngine.Game; +using Xunit; + +namespace VoxelEngine.Tests; + +public class PlayerTests +{ + [Fact] + public void ChunkPosition_Origin_IsZero() + { + var p = new Player(new Configuration(), new Vector3(0, 0, 0), aspectRatio: 1f); + Assert.Equal(new Vector3i(0, 0, 0), p.ChunkPosition); + } + + [Fact] + public void ChunkPosition_PositiveCoords_FloorsDown() + { + var p = new Player(new Configuration(), new Vector3(17.5f, 32f, 8f), aspectRatio: 1f); + Assert.Equal(new Vector3i(1, 2, 0), p.ChunkPosition); + } + + [Fact] + public void ChunkPosition_NegativeCoords_FloorsToNegativeInfinity() + { + var p = new Player(new Configuration(), new Vector3(-1f, -16f, -17f), aspectRatio: 1f); + Assert.Equal(new Vector3i(-1, -1, -2), p.ChunkPosition); + } + + [Fact] + public void ChunkPosition_PositiveExactBoundary_IsNextChunk() + { + var p = new Player(new Configuration(), new Vector3(16f, 0f, 0f), aspectRatio: 1f); + Assert.Equal(new Vector3i(1, 0, 0), p.ChunkPosition); + } + + [Fact] + public void Move_UpdatesPosition() + { + var p = new Player(new Configuration(), new Vector3(0, 0, 0), aspectRatio: 1f); + p.Move(new Vector3(5, 0, 0)); + Assert.Equal(5f, p.Position.X); + } + + [Fact] + public void Constructor_AppliesFovFromConfiguration() + { + var cfg = new Configuration { Fov = 75f }; + var p = new Player(cfg, new Vector3(0, 0, 0), aspectRatio: 1.5f); + Assert.Equal(75f, p.Camera.Fov); + } +}