ClassiCube/ClassicalSharp/Entities/Components/CollisionsComponent.cs

226 lines
9.2 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;
}
}
}