diff --git a/AABB.cs b/AABB.cs index 1d26385..03b8866 100644 --- a/AABB.cs +++ b/AABB.cs @@ -24,11 +24,34 @@ namespace Voxel return new AABB(center - half, center + new Vector3(half.X, height, half.Z)); } + public void Move(float x, float y, float z) + { + Min.X += x; + Max.X += x; + Min.Y += y; + Max.Y += y; + Min.Z += z; + Max.Z += z; + } + public bool Intersects(AABB other) { - return (Min.X <= other.Max.X && Max.X >= other.Min.X) && - (Min.Y <= other.Max.Y && Max.Y >= other.Min.Y) && - (Min.Z <= other.Max.Z && Max.Z >= other.Min.Z); + 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) @@ -37,5 +60,152 @@ namespace Voxel (point.Y >= Min.Y && point.Y <= Max.Y) && (point.Z >= Min.Z && point.Z <= Max.Z); } + + public float GetClipX(AABB other, float deltaX) + { + if (IntersectsY(other) && IntersectsZ(other)) + { + if (deltaX > 0 && Max.X <= other.Min.X) + { + float clip = other.Min.X - other.Min.X; + + if (deltaX > clip) + deltaX = clip; + } + + if (deltaX < 0 && Min.X >= other.Max.X) + { + float clip = other.Max.X - Max.X; + if (deltaX < clip) + deltaX = clip; + } + return deltaX; + } + return deltaX; + } + public float GetClipY(AABB other, float deltaY) + { + if (IntersectsX(other) && IntersectsZ(other)) + { + if (deltaY > 0 && Max.Y <= other.Min.Y) + { + float clip = other.Min.Y - other.Min.Y; + + if (deltaY > clip) + deltaY = clip; + } + + if (deltaY < 0 && Min.X >= other.Max.X) + { + float clip = other.Max.X - Max.X; + if (deltaY < clip) + deltaY = clip; + } + return deltaY; + } + return deltaY; + } + public float GetClipZ(AABB other, float deltaZ) + { + if (IntersectsX(other) && IntersectsY(other)) + { + if (deltaZ > 0 && Max.Z <= other.Min.Z) + { + float clip = other.Min.Z - other.Min.Z; + + if (deltaZ > clip) + deltaZ = clip; + } + + if (deltaZ < 0 && Min.Z >= other.Max.Z) + { + float clip = other.Max.Z - Max.Z; + if (deltaZ < clip) + deltaZ = clip; + } + return deltaZ; + } + return deltaZ; + } + + public float GetBlockClipX(int blockX, int blockY, int blockZ, float deltaX) + { + // 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; + } } } diff --git a/Entity.cs b/Entity.cs index 65dc214..68ddf51 100644 --- a/Entity.cs +++ b/Entity.cs @@ -1,6 +1,7 @@ using OpenTK.Mathematics; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -23,14 +24,13 @@ namespace Voxel private float _yMultiplier = 0.98f; private float _groundMultiplier = 0.6f; - private const float COLLISION_EPSILON = 0.001f; + 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; - // Helper methods for consistent block coordinate conversion - private static int BlockCoordMin(float coord) => (int)MathF.Floor(coord - 1e-6f); - private static int BlockCoordMax(float coord) => (int)MathF.Floor(coord + 1e-6f); - public Entity(Vector3 position, float width, float height, World world) { Position = position; @@ -41,13 +41,13 @@ namespace Voxel public void Tick() { - Vector3 desiredMovement = Velocity; - Vector3 newPosition = Position; - + var newPosition = Position; + var desiredMovement = Velocity; CheckAndResolveCollisions(ref newPosition, desiredMovement); - Position = newPosition; + //Move(); + if (!OnGround) { Vector3 acceleration = new Vector3(0, -_gravity, 0); @@ -56,6 +56,7 @@ namespace Voxel Velocity.Z *= _airMultiplier; Velocity.Y *= _yMultiplier; + if (Velocity.Y < _terminalVelocity) { Velocity.Y = _terminalVelocity; @@ -67,58 +68,59 @@ namespace Voxel } UpdateOnGround(); + + Console.WriteLine(Position.Y); + } + + public void Move() + { + Vector3 moveVector = Velocity; + Vector3 original = 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) + ); } public void CheckAndResolveCollisions(ref Vector3 position, Vector3 movement) { Vector3 originalPosition = position; - - // Try full movement first AABB futureBox = GetBoundingBoxAt(originalPosition + movement); if (!HasCollision(futureBox)) { - // No collision, apply full movement position = originalPosition + movement; return; } - // Collision detected, resolve each axis independently position = originalPosition; - // Resolve Y collision first ResolveYCollisionIndependent(ref position, movement.Y); - // Resolve X collision - ResolveXCollisionIndependent(ref position, movement.X); + Vector3 tempPosX = new Vector3(position.X + movement.X, position.Y, position.Z); + if (!HasCollision(GetBoundingBoxAt(tempPosX))) + { + position.X = tempPosX.X; + } + else + { + ResolveXCollisionIndependent(ref position, movement.X); + } - // Resolve Z collision - ResolveZCollisionIndependent(ref position, movement.Z); - - // After moving horizontally, re‑resolve Y to handle any new vertical collisions - // (e.g., sliding into a ledge) - ResolveYCollisionIndependent(ref position, 0); + 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); + } } private void ResolveYCollisionIndependent(ref Vector3 position, float velocityY) { - // First, handle if we're already inside a block (shouldn't happen normally) - AABB currentBox = GetBoundingBoxAt(position); - if (HasCollision(currentBox)) - { - // Try to push upward to escape - float step = 0.05f; - for (int i = 0; i < 10; i++) - { - position.Y += step; - if (!HasCollision(GetBoundingBoxAt(position))) - break; - } - Velocity.Y = 0; - OnGround = false; - return; - } - if (velocityY == 0) return; Vector3 testPos = new Vector3(position.X, position.Y + velocityY, position.Z); @@ -126,7 +128,7 @@ namespace Voxel if (HasCollision(testBox)) { - if (velocityY > 0) // Hitting ceiling + if (velocityY > 0) { float ceilingY = GetCeilingHeight(testBox); position.Y = ceilingY - (Height / 2); @@ -150,66 +152,80 @@ namespace Voxel { if (velocityX == 0) return; - float halfWidth = Width / 2; + // 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; + } + float direction = Math.Sign(velocityX); - float targetX = position.X + velocityX; + float checkDistance = Math.Abs(velocityX) + COLLISION_EPSILON; - // Sweep the bounding box to the target X position - Vector3 testPos = new Vector3(targetX, position.Y, position.Z); - AABB testBox = GetBoundingBoxAt(testPos); + for (float offset = 0; offset <= checkDistance; offset += 0.1f) + { + float testX = position.X + (offset * direction); + AABB testBox = GetBoundingBoxAt(new Vector3(testX, position.Y, position.Z)); - if (HasCollision(testBox)) - { - // Find the wall we hit - float wallX; - if (direction > 0) // Moving right + if (HasCollision(testBox)) { - wallX = GetRightWallPosition(testBox) - halfWidth - COLLISION_EPSILON; + if (direction > 0) + { + position.X = GetRightWallPosition(testBox) - (Width / 2) - COLLISION_EPSILON; + } + else + { + position.X = GetLeftWallPosition(testBox) + (Width / 2) + COLLISION_EPSILON; + } + Velocity.X = 0; + return; } - else // Moving left - { - wallX = GetLeftWallPosition(testBox) + halfWidth + COLLISION_EPSILON; - } - position.X = wallX; - Velocity.X = 0; - } - else - { - position.X = targetX; } + + // No collision found, apply full movement + position.X += velocityX; } private void ResolveZCollisionIndependent(ref Vector3 position, float velocityZ) { if (velocityZ == 0) return; - float halfWidth = Width / 2; + // 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.Z += velocityZ; + return; + } + float direction = Math.Sign(velocityZ); - float targetZ = position.Z + velocityZ; + float checkDistance = Math.Abs(velocityZ) + COLLISION_EPSILON; - // Sweep the bounding box to the target Z position - Vector3 testPos = new Vector3(position.X, position.Y, targetZ); - AABB testBox = GetBoundingBoxAt(testPos); + for (float offset = 0; offset <= checkDistance; offset += 0.01f) + { + float testZ = position.Z + (offset * direction); + AABB testBox = GetBoundingBoxAt(new Vector3(position.X, position.Y, testZ)); - if (HasCollision(testBox)) - { - // Find the wall we hit - float wallZ; - if (direction > 0) // Moving forward (+Z) + if (HasCollision(testBox)) { - wallZ = GetFrontWallPosition(testBox) - halfWidth - COLLISION_EPSILON; + if (direction > 0) // Moving forward + { + position.Z = GetFrontWallPosition(testBox) - (Width / 2); + } + else // Moving backward + { + position.Z = GetBackWallPosition(testBox) + (Width / 2); + } + Velocity.Z = 0; + return; } - else // Moving backward (-Z) - { - wallZ = GetBackWallPosition(testBox) + halfWidth + COLLISION_EPSILON; - } - position.Z = wallZ; - Velocity.Z = 0; - } - else - { - position.Z = targetZ; } + + // No collision found, apply full movement + position.Z += velocityZ; } private float GetFloorHeight(AABB box) @@ -243,7 +259,7 @@ namespace Voxel int maxX = BlockCoordMax(box.Max.X); int minZ = BlockCoordMin(box.Min.Z); int maxZ = BlockCoordMax(box.Max.Z); - int checkY = BlockCoordMax(box.Max.Y); // Fix: use the block above the entity + int checkY = BlockCoordMax(box.Max.Y); float lowestCeiling = float.MaxValue; @@ -268,7 +284,7 @@ namespace Voxel int maxY = BlockCoordMax(box.Max.Y); int minZ = BlockCoordMin(box.Min.Z); int maxZ = BlockCoordMax(box.Max.Z); - int checkX = BlockCoordMin(box.Min.X); // leftmost block the entity overlaps + int checkX = BlockCoordMin(box.Min.X); float rightmostWall = float.MinValue; @@ -293,7 +309,7 @@ namespace Voxel int maxY = BlockCoordMax(box.Max.Y); int minZ = BlockCoordMin(box.Min.Z); int maxZ = BlockCoordMax(box.Max.Z); - int checkX = BlockCoordMax(box.Max.X); // rightmost block the entity overlaps + int checkX = BlockCoordMax(box.Max.X); float leftmostWall = float.MaxValue; @@ -318,7 +334,7 @@ namespace Voxel int maxX = BlockCoordMax(box.Max.X); int minY = BlockCoordMin(box.Min.Y); int maxY = BlockCoordMax(box.Max.Y); - int checkZ = BlockCoordMin(box.Min.Z); // backmost block the entity overlaps + int checkZ = BlockCoordMin(box.Min.Z); float frontmostWall = float.MinValue; @@ -343,7 +359,7 @@ namespace Voxel int maxX = BlockCoordMax(box.Max.X); int minY = BlockCoordMin(box.Min.Y); int maxY = BlockCoordMax(box.Max.Y); - int checkZ = BlockCoordMax(box.Max.Z); // frontmost block the entity overlaps + int checkZ = BlockCoordMax(box.Max.Z); float backmostWall = float.MaxValue; @@ -406,13 +422,13 @@ namespace Voxel Vector3 min = new Vector3( position.X - halfWidth, - position.Y - halfHeight, + position.Y - halfHeight, // Center Y minus half height position.Z - halfWidth ); Vector3 max = new Vector3( position.X + halfWidth, - position.Y + halfHeight, + position.Y + halfHeight, // Center Y plus half height position.Z + halfWidth ); @@ -424,18 +440,17 @@ namespace Voxel AABB box = GetBoundingBox(); float yCheck = box.Min.Y - COLLISION_EPSILON; - 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 y = BlockCoordMin(yCheck); + 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, y, z); + Blocks block = _world.GetBlock(x, (int)MathF.Floor(yCheck), z); if (block != Blocks.Air) { OnGround = true;