// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT using System; using ClassicalSharp.Physics; using OpenTK; namespace ClassicalSharp.Entities { /// Entity component that performs collision detection. public sealed class CollisionsComponent { Game game; Entity entity; BlockInfo info; public CollisionsComponent(Game game, Entity entity) { this.game = game; this.entity = entity; info = game.BlockInfo; } internal bool hitXMin, hitYMin, hitZMin; internal bool hitXMax, hitYMax, hitZMax; internal bool HorCollision { get { return hitXMin || hitXMax || hitZMin || hitZMax; } } /// Constant offset used to avoid floating point roundoff errors. public const float Adjustment = 0.001f; public byte GetPhysicsBlockId(int x, int y, int z) { if (x < 0 || x >= game.World.Width || z < 0 || z >= game.World.Length || y < 0) return Block.Bedrock; if (y >= game.World.Height) return Block.Air; return game.World.GetBlock(x, y, z); } // TODO: test for corner cases, and refactor this. internal void MoveAndWallSlide() { if (entity.Velocity == Vector3.Zero) return; Vector3 size = entity.Size; AABB entityBB, entityExtentBB; int count = Searcher.FindReachableBlocks(game, entity, out entityBB, out entityExtentBB); CollideWithReachableBlocks(count, ref size, ref entityBB, ref entityExtentBB); } void CollideWithReachableBlocks(int count, ref Vector3 size, ref AABB entityBB, ref AABB entityExtentBB) { bool wasOn = entity.onGround; entity.onGround = false; hitXMin = false; hitYMin = false; hitZMin = false; hitXMax = false; hitYMax = false; hitZMax = false; AABB blockBB = default(AABB); for (int i = 0; i < count; i++) { State state = Searcher.stateCache[i]; Vector3 blockPos = new Vector3(state.X >> 3, state.Y >> 3, state.Z >> 3); int block = (state.X & 0x7) | (state.Y & 0x7) << 3 | (state.Z & 0x7) << 6; blockBB.Min = blockPos + info.MinBB[block]; blockBB.Max = blockPos + info.MaxBB[block]; if (!entityExtentBB.Intersects(blockBB)) continue; float tx = 0, ty = 0, tz = 0; Searcher.CalcTime(ref entity.Velocity, ref entityBB, ref blockBB, out tx, out ty, out tz); if (tx > 1 || ty > 1 || tz > 1) Utils.LogDebug("t > 1 in physics calculation.. this shouldn't have happened."); Vector3 v = entity.Velocity; v.X *= tx; v.Y *= ty; v.Z *= tz; AABB finalBB = entityBB.Offset(v); // if we have hit the bottom of a block, we need to change the axis we test first. if (!hitYMin) { if (finalBB.Min.Y + Adjustment >= blockBB.Max.Y) { ClipYMax(ref blockBB, ref entityBB, ref entityExtentBB, ref size); } else if (finalBB.Max.Y - Adjustment <= blockBB.Min.Y) { ClipYMin(ref blockBB, ref entityBB, ref entityExtentBB, ref size); } else if (finalBB.Min.X + Adjustment >= blockBB.Max.X) { ClipXMax(ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size); } else if (finalBB.Max.X - Adjustment <= blockBB.Min.X) { ClipXMin(ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size); } else if (finalBB.Min.Z + Adjustment >= blockBB.Max.Z) { ClipZMax(ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size); } else if (finalBB.Max.Z - Adjustment <= blockBB.Min.Z) { ClipZMin(ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size); } continue; } // if flying or falling, test the horizontal axes first. if (finalBB.Min.X + Adjustment >= blockBB.Max.X) { ClipXMax(ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size); } else if (finalBB.Max.X - Adjustment <= blockBB.Min.X) { ClipXMin(ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size); } else if (finalBB.Min.Z + Adjustment >= blockBB.Max.Z) { ClipZMax(ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size); } else if (finalBB.Max.Z - Adjustment <= blockBB.Min.Z) { ClipZMin(ref blockBB, ref entityBB, wasOn, finalBB, ref entityExtentBB, ref size); } else if (finalBB.Min.Y + Adjustment >= blockBB.Max.Y) { ClipYMax(ref blockBB, ref entityBB, ref entityExtentBB, ref size); } else if (finalBB.Max.Y - Adjustment <= blockBB.Min.Y) { ClipYMin(ref blockBB, ref entityBB, ref entityExtentBB, ref size); } } } void ClipXMin(ref AABB blockBB, ref AABB entityBB, bool wasOn, AABB finalBB, ref AABB entityExtentBB, ref Vector3 size) { if (!wasOn || !DidSlide(blockBB, ref size, finalBB, ref entityBB, ref entityExtentBB)) { entity.Position.X = blockBB.Min.X - size.X / 2 - Adjustment; ClipX(ref size, ref entityBB, ref entityExtentBB); hitXMin = true; } } void ClipXMax(ref AABB blockBB, ref AABB entityBB, bool wasOn, AABB finalBB, ref AABB entityExtentBB, ref Vector3 size) { if (!wasOn || !DidSlide(blockBB, ref size, finalBB, ref entityBB, ref entityExtentBB)) { entity.Position.X = blockBB.Max.X + size.X / 2 + Adjustment; ClipX(ref size, ref entityBB, ref entityExtentBB); hitXMax = true; } } void ClipZMax(ref AABB blockBB, ref AABB entityBB, bool wasOn, AABB finalBB, ref AABB entityExtentBB, ref Vector3 size) { if (!wasOn || !DidSlide(blockBB, ref size, finalBB, ref entityBB, ref entityExtentBB)) { entity.Position.Z = blockBB.Max.Z + size.Z / 2 + Adjustment; ClipZ(ref size, ref entityBB, ref entityExtentBB); hitZMax = true; } } void ClipZMin(ref AABB blockBB, ref AABB entityBB, bool wasOn, AABB finalBB, ref AABB extentBB, ref Vector3 size) { if (!wasOn || !DidSlide(blockBB, ref size, finalBB, ref entityBB, ref extentBB)) { entity.Position.Z = blockBB.Min.Z - size.Z / 2 - Adjustment; ClipZ(ref size, ref entityBB, ref extentBB); hitZMin = true; } } void ClipYMin(ref AABB blockBB, ref AABB entityBB, ref AABB extentBB, ref Vector3 size) { entity.Position.Y = blockBB.Min.Y - size.Y - Adjustment; ClipY(ref size, ref entityBB, ref extentBB); hitYMin = true; } void ClipYMax(ref AABB blockBB, ref AABB entityBB, ref AABB extentBB, ref Vector3 size) { entity.Position.Y = blockBB.Max.Y + Adjustment; entity.onGround = true; ClipY(ref size, ref entityBB, ref extentBB); hitYMax = true; } bool DidSlide(AABB blockBB, ref Vector3 size, AABB finalBB, ref AABB entityBB, ref AABB entityExtentBB) { float yDist = blockBB.Max.Y - entityBB.Min.Y; if (yDist > 0 && yDist <= entity.StepSize + 0.01f) { float blockXMin = blockBB.Min.X, blockZMin = blockBB.Min.Z; blockBB.Min.X = Math.Max(blockBB.Min.X, blockBB.Max.X - size.X / 2); blockBB.Max.X = Math.Min(blockBB.Max.X, blockXMin + size.X / 2); blockBB.Min.Z = Math.Max(blockBB.Min.Z, blockBB.Max.Z - size.Z / 2); blockBB.Max.Z = Math.Min(blockBB.Max.Z, blockZMin + size.Z / 2); AABB adjBB = finalBB; adjBB.Min.X = Math.Min(finalBB.Min.X, blockBB.Min.X + Adjustment); adjBB.Max.X = Math.Max(finalBB.Max.X, blockBB.Max.X - Adjustment); adjBB.Min.Y = blockBB.Max.Y + Adjustment; adjBB.Max.Y = adjBB.Min.Y + size.Y; adjBB.Min.Z = Math.Min(finalBB.Min.Z, blockBB.Min.Z + Adjustment); adjBB.Max.Z = Math.Max(finalBB.Max.Z, blockBB.Max.Z - Adjustment); if (!CanSlideThrough(ref adjBB)) return false; entity.Position.Y = blockBB.Max.Y + Adjustment; entity.onGround = true; ClipY(ref size, ref entityBB, ref entityExtentBB); return true; } return false; } bool CanSlideThrough(ref AABB adjFinalBB) { Vector3I bbMin = Vector3I.Floor(adjFinalBB.Min); Vector3I bbMax = Vector3I.Floor(adjFinalBB.Max); for (int y = bbMin.Y; y <= bbMax.Y; y++) for (int z = bbMin.Z; z <= bbMax.Z; z++) for (int x = bbMin.X; x <= bbMax.X; x++) { byte block = GetPhysicsBlockId(x, y, z); Vector3 min = new Vector3(x, y, z) + info.MinBB[block]; Vector3 max = new Vector3(x, y, z) + info.MaxBB[block]; AABB blockBB = new AABB(min, max); if (!blockBB.Intersects(adjFinalBB)) continue; if (info.Collide[GetPhysicsBlockId(x, y, z)] == CollideType.Solid) return false; } return true; } void ClipX(ref Vector3 size, ref AABB entityBB, ref AABB entityExtentBB) { entity.Velocity.X = 0; entityBB.Min.X = entityExtentBB.Min.X = entity.Position.X - size.X / 2; entityBB.Max.X = entityExtentBB.Max.X = entity.Position.X + size.X / 2; } void ClipY(ref Vector3 size, ref AABB entityBB, ref AABB entityExtentBB) { entity.Velocity.Y = 0; entityBB.Min.Y = entityExtentBB.Min.Y = entity.Position.Y; entityBB.Max.Y = entityExtentBB.Max.Y = entity.Position.Y + size.Y; } void ClipZ(ref Vector3 size, ref AABB entityBB, ref AABB entityExtentBB) { entity.Velocity.Z = 0; entityBB.Min.Z = entityExtentBB.Min.Z = entity.Position.Z - size.Z / 2; entityBB.Max.Z = entityExtentBB.Max.Z = entity.Position.Z + size.Z / 2; } } }