diff --git a/AABB.cs b/AABB.cs index 03b8866..6aee6ee 100644 --- a/AABB.cs +++ b/AABB.cs @@ -1,9 +1,4 @@ using OpenTK.Mathematics; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Voxel { @@ -18,12 +13,53 @@ namespace Voxel Max = max; } + public AABB Copy() + { + return new AABB(Min, Max); + } + public static AABB FromCenter(Vector3 center, float width, float height, float depth) { Vector3 half = new Vector3(width / 2f, 0, depth / 2f); return new AABB(center - half, center + new Vector3(half.X, height, half.Z)); } + public AABB Expand(float x, float y, float z) + { + AABB returnValue = Copy(); + + if (x > 0) + returnValue.Max.X += x; + else + returnValue.Min.X += x; + + if (y > 0) + returnValue.Max.Y += y; + else + returnValue.Min.Y += y; + + if (z > 0) + returnValue.Max.Z += z; + else + returnValue.Min.Z += z; + + return returnValue; + } + + public AABB Grow(float x, float y, float z) + { + AABB returnValue = Copy(); + + returnValue.Min.X -= x; + returnValue.Min.Y -= y; + returnValue.Min.Z -= z; + returnValue.Max.X += x; + returnValue.Max.Y += y; + returnValue.Max.Z += z; + + return returnValue; + } + public void Move(float x, float y, float z) { Min.X += x; @@ -34,26 +70,6 @@ namespace Voxel Max.Z += z; } - public bool Intersects(AABB other) - { - return IntersectsX(other) && IntersectsY(other) && IntersectsZ(other); - } - - public bool IntersectsX(AABB other) - { - return Min.X < other.Max.X && Max.X > other.Max.X; - } - - public bool IntersectsY(AABB other) - { - return Min.Y < other.Max.Y && Max.Y > other.Max.Y; - } - - public bool IntersectsZ(AABB other) - { - return Min.Z < other.Max.Z && Max.Z > other.Max.Z; - } - public bool Contains(Vector3 point) { return (point.X >= Min.X && point.X <= Max.X) && @@ -61,151 +77,90 @@ namespace Voxel (point.Z >= Min.Z && point.Z <= Max.Z); } - public float GetClipX(AABB other, float deltaX) + public bool IntersectsX(AABB against) { - if (IntersectsY(other) && IntersectsZ(other)) + return Min.X < against.Max.X && Max.X > against.Min.X; + } + + public bool IntersectsY(AABB against) + { + return Min.Y < against.Max.Y && Max.Y > against.Min.Y; + } + + public bool IntersectsZ(AABB against) + { + return Min.Z < against.Max.Z && Max.Z > against.Min.Z; + } + + public bool Intersects(AABB against) + { + return IntersectsX(against) && IntersectsY(against) && IntersectsZ(against); + } + + public float GetClipX(AABB against, float deltaX) + { + if (IntersectsY(against) && IntersectsZ(against)) { - if (deltaX > 0 && Max.X <= other.Min.X) + if (deltaX > 0 && Max.X <= against.Min.X) { - float clip = other.Min.X - other.Min.X; - - if (deltaX > clip) - deltaX = clip; + float clip = against.Min.X - Max.X; + if (deltaX > clip) deltaX = clip; } - - if (deltaX < 0 && Min.X >= other.Max.X) + if (deltaX < 0 && Min.X >= against.Max.X) { - float clip = other.Max.X - Max.X; - if (deltaX < clip) - deltaX = clip; + float clip = against.Max.X - Min.X; + if (deltaX < clip) deltaX = clip; } return deltaX; } return deltaX; } - public float GetClipY(AABB other, float deltaY) + + public float GetClipY(AABB against, float deltaY) { - if (IntersectsX(other) && IntersectsZ(other)) + if (IntersectsX(against) && IntersectsZ(against)) { - if (deltaY > 0 && Max.Y <= other.Min.Y) + if (deltaY > 0 && Max.Y <= against.Min.Y) { - float clip = other.Min.Y - other.Min.Y; - - if (deltaY > clip) - deltaY = clip; + float clip = against.Min.Y - Max.Y; + if (deltaY > clip) deltaY = clip; } - - if (deltaY < 0 && Min.X >= other.Max.X) + if (deltaY < 0 && Min.Y >= against.Max.Y) { - float clip = other.Max.X - Max.X; - if (deltaY < clip) - deltaY = clip; + float clip = against.Max.Y - Min.Y; + if (deltaY < clip) deltaY = clip; } return deltaY; } return deltaY; } - public float GetClipZ(AABB other, float deltaZ) + + public float GetClipZ(AABB against, float deltaZ) { - if (IntersectsX(other) && IntersectsY(other)) + if (IntersectsX(against) && IntersectsY(against)) { - if (deltaZ > 0 && Max.Z <= other.Min.Z) + if (deltaZ > 0 && Max.Z <= against.Min.Z) { - float clip = other.Min.Z - other.Min.Z; - - if (deltaZ > clip) - deltaZ = clip; + float clip = against.Min.Z - Max.Z; + if (deltaZ > clip) deltaZ = clip; } - - if (deltaZ < 0 && Min.Z >= other.Max.Z) + if (deltaZ < 0 && Min.Z >= against.Max.Z) { - float clip = other.Max.Z - Max.Z; - if (deltaZ < clip) - deltaZ = clip; + float clip = against.Max.Z - Min.Z; + if (deltaZ < clip) deltaZ = clip; } return deltaZ; } return deltaZ; } - public float GetBlockClipX(int blockX, int blockY, int blockZ, float deltaX) + public Vector3 GetCenter() { - // Use the current AABB as the collider (no new allocation) - AABB collider = this; - - // Block bounds - float blockMinX = blockX; - float blockMaxX = blockX + 1; - - // Quick reject: if player is not overlapping the block in Y and Z, no collision. - if (!(collider.Min.Y < blockY + 1 && collider.Max.Y > blockY) || - !(collider.Min.Z < blockZ + 1 && collider.Max.Z > blockZ)) - return deltaX; - - // Moving right (positive delta) - if (deltaX > 0 && collider.Max.X <= blockMinX) - { - float clip = blockMinX - collider.Max.X; - if (deltaX > clip) deltaX = clip; - } - // Moving left (negative delta) - else if (deltaX < 0 && collider.Min.X >= blockMaxX) - { - float clip = blockMaxX - collider.Min.X; // negative distance - if (deltaX < clip) deltaX = clip; - } - - return deltaX; - } - - public float GetBlockClipY(int blockX, int blockY, int blockZ, float deltaY) - { - AABB collider = this; - - float blockMinY = blockY; - float blockMaxY = blockY + 1; - - if (!(collider.Min.X < blockX + 1 && collider.Max.X > blockX) || - !(collider.Min.Z < blockZ + 1 && collider.Max.Z > blockZ)) - return deltaY; - - if (deltaY > 0 && collider.Max.Y <= blockMinY) - { - float clip = blockMinY - collider.Max.Y; - if (deltaY > clip) deltaY = clip; - } - else if (deltaY < 0 && collider.Min.Y >= blockMaxY) - { - float clip = blockMaxY - collider.Min.Y; - if (deltaY < clip) deltaY = clip; - } - - return deltaY; - } - - public float GetBlockClipZ(int blockX, int blockY, int blockZ, float deltaZ) - { - AABB collider = this; - - float blockMinZ = blockZ; - float blockMaxZ = blockZ + 1; - - if (!(collider.Min.X < blockX + 1 && collider.Max.X > blockX) || - !(collider.Min.Y < blockY + 1 && collider.Max.Y > blockY)) - return deltaZ; - - if (deltaZ > 0 && collider.Max.Z <= blockMinZ) - { - float clip = blockMinZ - collider.Max.Z; - if (deltaZ > clip) deltaZ = clip; - } - else if (deltaZ < 0 && collider.Min.Z >= blockMaxZ) - { - float clip = blockMaxZ - collider.Min.Z; - if (deltaZ < clip) deltaZ = clip; - } - - return deltaZ; + return new Vector3( + (Min.X + Max.X) / 2, + (Min.Y + Max.Y) / 2, + (Min.Z + Max.Z) / 2 + ); } } } diff --git a/Blocks.cs b/Blocks.cs index dc2b982..7013935 100644 --- a/Blocks.cs +++ b/Blocks.cs @@ -93,12 +93,12 @@ { Voxel.Blocks.TNT, new BlockDefinition( Voxel.Blocks.TNT, - Voxel.Textures.TntSide, // West - Voxel.Textures.TntSide, // East - Voxel.Textures.TntTop, // Top - Voxel.Textures.TntBottom, // Bottom - Voxel.Textures.TntSide, // North - Voxel.Textures.TntSide // South + Voxel.Textures.TntSide, // West + Voxel.Textures.TntSide, // East + Voxel.Textures.TntTop, // Top + Voxel.Textures.TntBottom, // Bottom + Voxel.Textures.TntSide, // North + Voxel.Textures.TntSide // South )}, }; } diff --git a/Entity.cs b/Entity.cs index 68ddf51..408b93e 100644 --- a/Entity.cs +++ b/Entity.cs @@ -24,11 +24,6 @@ namespace Voxel private float _yMultiplier = 0.98f; private float _groundMultiplier = 0.6f; - private const float COLLISION_EPSILON = 0.1f; - - private static int BlockCoordMin(float coord) => (int)MathF.Floor(coord - 1e-2f); - private static int BlockCoordMax(float coord) => (int)MathF.Floor(coord + 1e-2f); - protected World _world; public Entity(Vector3 position, float width, float height, World world) @@ -41,12 +36,7 @@ namespace Voxel public void Tick() { - var newPosition = Position; - var desiredMovement = Velocity; - CheckAndResolveCollisions(ref newPosition, desiredMovement); - Position = newPosition; - - //Move(); + Move(); if (!OnGround) { @@ -66,398 +56,61 @@ namespace Voxel { Velocity = new Vector3(Velocity.X * _groundMultiplier, 0f, Velocity.Z * _groundMultiplier); } - - UpdateOnGround(); - - Console.WriteLine(Position.Y); } public void Move() { Vector3 moveVector = Velocity; - Vector3 original = moveVector; + Vector3 originalVector = moveVector; - AABB collider = new AABB( - Position - new Vector3(Position.X - Width / 2, 0, Position.Z - Width / 2), - Position - new Vector3(Position.X + Width / 2, Height, Position.Z + Width / 2) - ); - } + AABB body = AABB.FromCenter(Position, Width, Height, Width); - public void CheckAndResolveCollisions(ref Vector3 position, Vector3 movement) - { - Vector3 originalPosition = position; - AABB futureBox = GetBoundingBoxAt(originalPosition + movement); + List collisionChecks = _world.GetColliders(body.Expand(Velocity.X, Velocity.Y, Velocity.Z).Grow(1,1,1)); - if (!HasCollision(futureBox)) + foreach (AABB collider in collisionChecks) { - position = originalPosition + movement; - return; + moveVector.X = body.GetClipX(collider, moveVector.X); } - position = originalPosition; + body.Move(moveVector.X, 0, 0); - ResolveYCollisionIndependent(ref position, movement.Y); - - Vector3 tempPosX = new Vector3(position.X + movement.X, position.Y, position.Z); - if (!HasCollision(GetBoundingBoxAt(tempPosX))) + foreach (AABB collider in collisionChecks) { - position.X = tempPosX.X; - } - else - { - ResolveXCollisionIndependent(ref position, movement.X); + moveVector.Y = body.GetClipY(collider, moveVector.Y); } - Vector3 tempPosZ = new Vector3(position.X, position.Y, position.Z + movement.Z); - if (!HasCollision(GetBoundingBoxAt(tempPosZ))) - { - position.Z = tempPosZ.Z; - } - else - { - ResolveZCollisionIndependent(ref position, movement.Z); - } - } + body.Move(0, moveVector.Y, 0); - private void ResolveYCollisionIndependent(ref Vector3 position, float velocityY) - { - if (velocityY == 0) return; - - Vector3 testPos = new Vector3(position.X, position.Y + velocityY, position.Z); - AABB testBox = GetBoundingBoxAt(testPos); - - if (HasCollision(testBox)) + foreach (AABB collider in collisionChecks) { - if (velocityY > 0) - { - float ceilingY = GetCeilingHeight(testBox); - position.Y = ceilingY - (Height / 2); - Velocity.Y = 0; - } - else // Hitting floor - { - float floorY = GetFloorHeight(testBox); - position.Y = floorY + (Height / 2); - Velocity.Y = 0; - OnGround = true; - } - } - else - { - position.Y += velocityY; - } - } - - private void ResolveXCollisionIndependent(ref Vector3 position, float velocityX) - { - if (velocityX == 0) return; - - // Check if we're already inside a block at current position - AABB currentBox = GetBoundingBoxAt(new Vector3(position.X, position.Y, position.Z)); - if (HasCollision(currentBox)) - { - // Already inside a block, allow movement to escape - position.X += velocityX; - return; + moveVector.Z = body.GetClipZ(collider, moveVector.Z); } - float direction = Math.Sign(velocityX); - float checkDistance = Math.Abs(velocityX) + COLLISION_EPSILON; + body.Move(0, 0, moveVector.Z); - for (float offset = 0; offset <= checkDistance; offset += 0.1f) + if (moveVector.X != originalVector.X) { - float testX = position.X + (offset * direction); - AABB testBox = GetBoundingBoxAt(new Vector3(testX, position.Y, position.Z)); - - if (HasCollision(testBox)) - { - if (direction > 0) - { - position.X = GetRightWallPosition(testBox) - (Width / 2) - COLLISION_EPSILON; - } - else - { - position.X = GetLeftWallPosition(testBox) + (Width / 2) + COLLISION_EPSILON; - } - Velocity.X = 0; - return; - } + Velocity.X = 0; Console.WriteLine("Collided X"); } - // No collision found, apply full movement - position.X += velocityX; - } - - private void ResolveZCollisionIndependent(ref Vector3 position, float velocityZ) - { - if (velocityZ == 0) return; - - // Check if we're already inside a block at current position - AABB currentBox = GetBoundingBoxAt(new Vector3(position.X, position.Y, position.Z)); - if (HasCollision(currentBox)) + if (moveVector.Y != originalVector.Y) { - // Already inside a block, allow movement to escape - position.Z += velocityZ; - return; + Velocity.Y = 0; } - float direction = Math.Sign(velocityZ); - float checkDistance = Math.Abs(velocityZ) + COLLISION_EPSILON; - - for (float offset = 0; offset <= checkDistance; offset += 0.01f) + if (moveVector.Z != originalVector.Z) { - float testZ = position.Z + (offset * direction); - AABB testBox = GetBoundingBoxAt(new Vector3(position.X, position.Y, testZ)); - - if (HasCollision(testBox)) - { - if (direction > 0) // Moving forward - { - position.Z = GetFrontWallPosition(testBox) - (Width / 2); - } - else // Moving backward - { - position.Z = GetBackWallPosition(testBox) + (Width / 2); - } - Velocity.Z = 0; - return; - } + Velocity.Z = 0; Console.WriteLine("Collided Z"); } - // No collision found, apply full movement - position.Z += velocityZ; - } + OnGround = moveVector.Y != originalVector.Y && originalVector.Y < 0; - private float GetFloorHeight(AABB box) - { - int minX = BlockCoordMin(box.Min.X); - int maxX = BlockCoordMax(box.Max.X); - int minZ = BlockCoordMin(box.Min.Z); - int maxZ = BlockCoordMax(box.Max.Z); - int checkY = BlockCoordMin(box.Min.Y); - - float highestFloor = float.MinValue; - - for (int x = minX; x <= maxX; x++) - { - for (int z = minZ; z <= maxZ; z++) - { - Blocks block = _world.GetBlock(x, checkY, z); - if (block != Blocks.Air) - { - highestFloor = Math.Max(highestFloor, checkY + 1); // Top of the block - } - } - } - - return highestFloor != float.MinValue ? highestFloor : box.Min.Y; - } - - private float GetCeilingHeight(AABB box) - { - int minX = BlockCoordMin(box.Min.X); - int maxX = BlockCoordMax(box.Max.X); - int minZ = BlockCoordMin(box.Min.Z); - int maxZ = BlockCoordMax(box.Max.Z); - int checkY = BlockCoordMax(box.Max.Y); - - float lowestCeiling = float.MaxValue; - - for (int x = minX; x <= maxX; x++) - { - for (int z = minZ; z <= maxZ; z++) - { - Blocks block = _world.GetBlock(x, checkY, z); - if (block != Blocks.Air) - { - lowestCeiling = Math.Min(lowestCeiling, checkY); // Bottom of the block - } - } - } - - return lowestCeiling != float.MaxValue ? lowestCeiling : box.Max.Y; - } - - private float GetLeftWallPosition(AABB box) - { - int minY = BlockCoordMin(box.Min.Y); - int maxY = BlockCoordMax(box.Max.Y); - int minZ = BlockCoordMin(box.Min.Z); - int maxZ = BlockCoordMax(box.Max.Z); - int checkX = BlockCoordMin(box.Min.X); - - float rightmostWall = float.MinValue; - - for (int y = minY; y <= maxY; y++) - { - for (int z = minZ; z <= maxZ; z++) - { - Blocks block = _world.GetBlock(checkX, y, z); - if (block != Blocks.Air) - { - rightmostWall = Math.Max(rightmostWall, checkX + 1); // Right side of the block - } - } - } - - return rightmostWall != float.MinValue ? rightmostWall : box.Min.X; - } - - private float GetRightWallPosition(AABB box) - { - int minY = BlockCoordMin(box.Min.Y); - int maxY = BlockCoordMax(box.Max.Y); - int minZ = BlockCoordMin(box.Min.Z); - int maxZ = BlockCoordMax(box.Max.Z); - int checkX = BlockCoordMax(box.Max.X); - - float leftmostWall = float.MaxValue; - - for (int y = minY; y <= maxY; y++) - { - for (int z = minZ; z <= maxZ; z++) - { - Blocks block = _world.GetBlock(checkX, y, z); - if (block != Blocks.Air) - { - leftmostWall = Math.Min(leftmostWall, checkX); // Left side of the block - } - } - } - - return leftmostWall != float.MaxValue ? leftmostWall : box.Max.X; - } - - private float GetBackWallPosition(AABB box) - { - int minX = BlockCoordMin(box.Min.X); - int maxX = BlockCoordMax(box.Max.X); - int minY = BlockCoordMin(box.Min.Y); - int maxY = BlockCoordMax(box.Max.Y); - int checkZ = BlockCoordMin(box.Min.Z); - - float frontmostWall = float.MinValue; - - for (int x = minX; x <= maxX; x++) - { - for (int y = minY; y <= maxY; y++) - { - Blocks block = _world.GetBlock(x, y, checkZ); - if (block != Blocks.Air) - { - frontmostWall = Math.Max(frontmostWall, checkZ + 1); // Front side of the block - } - } - } - - return frontmostWall != float.MinValue ? frontmostWall : box.Min.Z; - } - - private float GetFrontWallPosition(AABB box) - { - int minX = BlockCoordMin(box.Min.X); - int maxX = BlockCoordMax(box.Max.X); - int minY = BlockCoordMin(box.Min.Y); - int maxY = BlockCoordMax(box.Max.Y); - int checkZ = BlockCoordMax(box.Max.Z); - - float backmostWall = float.MaxValue; - - for (int x = minX; x <= maxX; x++) - { - for (int y = minY; y <= maxY; y++) - { - Blocks block = _world.GetBlock(x, y, checkZ); - if (block != Blocks.Air) - { - backmostWall = Math.Min(backmostWall, checkZ); // Back side of the block - } - } - } - - return backmostWall != float.MaxValue ? backmostWall : box.Max.Z; - } - - public bool HasCollision(AABB box) - { - int minX = BlockCoordMin(box.Min.X); - int maxX = BlockCoordMax(box.Max.X); - int minY = BlockCoordMin(box.Min.Y); - int maxY = BlockCoordMax(box.Max.Y); - int minZ = BlockCoordMin(box.Min.Z); - int maxZ = BlockCoordMax(box.Max.Z); - - for (int x = minX; x <= maxX; x++) - { - for (int y = minY; y <= maxY; y++) - { - for (int z = minZ; z <= maxZ; z++) - { - Blocks block = _world.GetBlock(x, y, z); - if (block != Blocks.Air) - { - return true; - } - } - } - } - - return false; + Position = new Vector3(body.GetCenter().X, body.Min.Y, body.GetCenter().Z); } public void ApplyImpulse(Vector3 force) { Velocity += force; } - - public AABB GetBoundingBox() - { - return GetBoundingBoxAt(Position); - } - - public AABB GetBoundingBoxAt(Vector3 position) - { - float halfWidth = Width / 2; - float halfHeight = Height / 2; - - Vector3 min = new Vector3( - position.X - halfWidth, - position.Y - halfHeight, // Center Y minus half height - position.Z - halfWidth - ); - - Vector3 max = new Vector3( - position.X + halfWidth, - position.Y + halfHeight, // Center Y plus half height - position.Z + halfWidth - ); - - return new AABB(min, max); - } - - public void UpdateOnGround() - { - AABB box = GetBoundingBox(); - float yCheck = box.Min.Y - COLLISION_EPSILON; - - int minX = (int)MathF.Floor(box.Min.X); - int maxX = (int)MathF.Floor(box.Max.X); - int minZ = (int)MathF.Floor(box.Min.Z); - int maxZ = (int)MathF.Floor(box.Max.Z); - - OnGround = false; - for (int x = minX; x <= maxX; x++) - { - for (int z = minZ; z <= maxZ; z++) - { - Blocks block = _world.GetBlock(x, (int)MathF.Floor(yCheck), z); - if (block != Blocks.Air) - { - OnGround = true; - return; - } - } - } - } } } \ No newline at end of file diff --git a/Player.cs b/Player.cs index 9762ad5..05ae595 100644 --- a/Player.cs +++ b/Player.cs @@ -102,7 +102,7 @@ namespace Voxel public void Update(float deltaTime, float alpha) { - Camera.Position = Vector3.Lerp(previousPosition, Position, alpha) + Vector3.UnitY * 0.62f; + Camera.Position = Vector3.Lerp(previousPosition, Position, alpha) + Vector3.UnitY * 1.62f; Rotation = Camera.Yaw; if (lastClick > 0) diff --git a/Window.cs b/Window.cs index d6440fe..67cd9c6 100644 --- a/Window.cs +++ b/Window.cs @@ -67,6 +67,7 @@ namespace Voxel Renderer.Render(); SwapBuffers(); + Thread.Sleep(5); } protected override void OnFramebufferResize(FramebufferResizeEventArgs e) diff --git a/World.cs b/World.cs index 1c53a7e..491c6f5 100644 --- a/World.cs +++ b/World.cs @@ -1,6 +1,4 @@ using OpenTK.Mathematics; -using System.Collections.Generic; -using System.Drawing; namespace Voxel { @@ -62,22 +60,17 @@ namespace Voxel int centerX = (int)Math.Floor(playerPosition.X / Chunk.Size); int centerZ = (int)Math.Floor(playerPosition.Z / Chunk.Size); - // Quick check - skip if still in same chunk if ((centerX == _lastCenter.x && centerZ == _lastCenter.z) && chunkLoadingInitialized) return; _lastCenter = (centerX, centerZ); - // Calculate bounds int minX = centerX - _loadDistance; int maxX = centerX + _loadDistance; int minZ = centerZ - _loadDistance; int maxZ = centerZ + _loadDistance; - // Unload chunks outside range UnloadDistantChunks(minX, maxX, minZ, maxZ); - - // Load chunks inside range LoadChunksInRange(minX, maxX, minZ, maxZ); chunkLoadingInitialized = true; @@ -347,9 +340,35 @@ namespace Voxel return (false, Blocks.Air, 0, 0, 0, Vector3i.Zero); } - public void Update() + public List GetColliders(AABB body) { + List collisions = new List(); + int minX = (int)Math.Floor(body.Min.X); + int maxX = (int)Math.Ceiling(body.Max.X) - 1; + int minY = (int)Math.Floor(body.Min.Y); + int maxY = (int)Math.Ceiling(body.Max.Y) - 1; + int minZ = (int)Math.Floor(body.Min.Z); + int maxZ = (int)Math.Ceiling(body.Max.Z) - 1; + + for (int x = minX; x <= maxX; x++) + { + for (int y = minY; y <= maxY; y++) + { + for (int z = minZ; z <= maxZ; z++) + { + if (GetBlock(x, y, z) != Blocks.Air) + { + collisions.Add(new AABB( + new Vector3(x, y, z), + new Vector3(x + 1, y + 1, z + 1) + )); + } + } + } + } + + return collisions; } } }