// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT using System; using ClassicalSharp.Model; using OpenTK; namespace ClassicalSharp.Entities { /// Contains a model, along with position, velocity, and rotation. /// May also contain other fields and properties. public abstract partial class Entity { /// Returns the bounding box that contains the model, assuming it is not rotated. public BoundingBox PickingBounds { get { UpdateModel(); return Model.PickingBounds.Offset( Position ); } } /// Bounding box of the model that collision detection /// is performed with, in world coordinates. public virtual BoundingBox CollisionBounds { get { Vector3 pos = Position; Vector3 size = CollisionSize; return new BoundingBox( pos.X - size.X / 2, pos.Y, pos.Z - size.Z / 2, pos.X + size.X / 2, pos.Y + size.Y, pos.Z + size.Z / 2 ); } } /// Determines whether any of the blocks that intersect the /// bounding box of this entity satisfy the given condition. public bool TouchesAny( Predicate condition ) { return TouchesAny( CollisionBounds, condition ); } /// Determines whether any of the blocks that intersect the /// given bounding box satisfy the given condition. public bool TouchesAny( BoundingBox bounds, Predicate condition ) { Vector3I bbMin = Vector3I.Floor( bounds.Min ); Vector3I bbMax = Vector3I.Floor( bounds.Max ); // Order loops so that we minimise cache misses 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++ ) { if( !game.World.IsValidPos( x, y, z ) ) continue; byte block = game.World.GetBlock( x, y, z ); Vector3 min = new Vector3( x, y, z ) + info.MinBB[block]; Vector3 max = new Vector3( x, y, z ) + info.MaxBB[block]; BoundingBox blockBB = new BoundingBox( min, max ); if( !blockBB.Intersects( bounds ) ) continue; if( condition( block ) ) return true; } return false; } /// Constant offset used to avoid floating point roundoff errors. public const float Adjustment = 0.001f; static readonly Vector3 liqExpand = new Vector3( 0.25f/16f, 0/16f, 0.25f/16f ); /// Determines whether any of the blocks that intersect the /// bounding box of this entity are lava or still lava. public bool TouchesAnyLava() { BoundingBox bounds = CollisionBounds.Expand( liqExpand ); AdjustLiquidTestBounds( ref bounds ); return TouchesAny( bounds, b => b == (byte)Block.Lava || b == (byte)Block.StillLava ); } /// Determines whether any of the blocks that intersect the /// bounding box of this entity are rope. public bool TouchesAnyRope() { BoundingBox bounds = CollisionBounds; bounds.Max.Y += 0.5f/16f; return TouchesAny( bounds, b => b == (byte)Block.Rope ); } /// Determines whether any of the blocks that intersect the /// bounding box of this entity are water or still water. public bool TouchesAnyWater() { BoundingBox bounds = CollisionBounds.Expand( liqExpand ); AdjustLiquidTestBounds( ref bounds ); return TouchesAny( bounds, b => b == (byte)Block.Water || b == (byte)Block.StillWater ); } void AdjustLiquidTestBounds( ref BoundingBox bounds ) { // Even though we collide with lava 3 blocks above our feet, vanilla client thinks // that we do not.. so we have to maintain compatibility here. float height = bounds.Max.Y - bounds.Min.Y; const float pHeight = (28.5f - 4f)/16f; bounds.Max.Y = bounds.Min.Y + Math.Min( height, pHeight ); } } }