mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-10-11 07:05:57 -04:00
226 lines
9.0 KiB
C#
226 lines
9.0 KiB
C#
// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT
|
|
using System;
|
|
using ClassicalSharp.Physics;
|
|
using OpenTK;
|
|
|
|
namespace ClassicalSharp.Entities {
|
|
|
|
/// <summary> Entity component that performs collision detection. </summary>
|
|
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; }
|
|
}
|
|
|
|
/// <summary> Constant offset used to avoid floating point roundoff errors. </summary>
|
|
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;
|
|
}
|
|
}
|
|
} |