From f99a01cba93591f8e67f8c8749407f449bd152c0 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sat, 10 Oct 2015 10:17:13 +1100 Subject: [PATCH] Refactor physics some more, also fix bug with terrain atlas sometimes causing crash. More work on occlusion, still broken. --- ClassicalSharp/ClassicalSharp.csproj | 5 +- ClassicalSharp/Entities/Entity.cs | 6 +- ClassicalSharp/Entities/LocalPlayer.cs | 4 +- .../PhysicsEntity.cs} | 440 +++++++++--------- ClassicalSharp/Entities/Player.cs | 17 +- ClassicalSharp/GraphicsAPI/IGraphicsApi.cs | 2 + .../Rendering/MapRenderer.Occlusion.cs | 195 ++++++++ .../Rendering/MapRenderer.Rendering.cs | 6 +- ClassicalSharp/Rendering/MapRenderer.cs | 142 +----- ClassicalSharp/TexturePack/TerrainAtlas1D.cs | 23 +- ClassicalSharp/Utils/FastBitmap.cs | 2 +- 11 files changed, 453 insertions(+), 389 deletions(-) rename ClassicalSharp/{Physics/Entity.Physics.cs => Entities/PhysicsEntity.cs} (94%) create mode 100644 ClassicalSharp/Rendering/MapRenderer.Occlusion.cs diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index cfcc9f9c7..aba30613f 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -19,7 +19,7 @@ 4 - AnyCPU + x86 4194304 False Auto @@ -108,6 +108,7 @@ + @@ -159,12 +160,12 @@ - + diff --git a/ClassicalSharp/Entities/Entity.cs b/ClassicalSharp/Entities/Entity.cs index d05c8fad6..d4c4f0c12 100644 --- a/ClassicalSharp/Entities/Entity.cs +++ b/ClassicalSharp/Entities/Entity.cs @@ -4,7 +4,7 @@ using OpenTK; namespace ClassicalSharp { - public abstract partial class Entity { + public abstract class Entity { public Entity( Game game ) { map = game.Map; @@ -19,6 +19,8 @@ namespace ClassicalSharp { public Vector3 Velocity; public float YawDegrees, PitchDegrees; protected float StepSize; + protected Map map; + protected BlockInfo info; public float YawRadians { get { return YawDegrees * Utils.Deg2Rad; } @@ -87,5 +89,7 @@ namespace ClassicalSharp { } return false; } + + public const float Adjustment = 0.001f; } } \ No newline at end of file diff --git a/ClassicalSharp/Entities/LocalPlayer.cs b/ClassicalSharp/Entities/LocalPlayer.cs index ecdee3863..6761d4e76 100644 --- a/ClassicalSharp/Entities/LocalPlayer.cs +++ b/ClassicalSharp/Entities/LocalPlayer.cs @@ -143,7 +143,7 @@ namespace ClassicalSharp { const float liquidGrav = 0.02f, ropeGrav = 0.034f, normalGrav = 0.08f; void PhysicsTick( float xMoving, float zMoving ) { - float multiply = flying ? ( speeding ? 90 : 15 ) : ( speeding ? 10 : 1 ); + float multiply = flying ? (speeding ? 90 : 15) : (speeding ? 10 : 1); float modifier = LowestSpeedModifier(); multiply *= modifier; @@ -155,7 +155,7 @@ namespace ClassicalSharp { Move( xMoving, zMoving, 0.02f * 1.7f, ropeDrag, ropeGrav, 1 ); } else { float factor = !flying && onGround ? 0.1f : 0.02f; - float yMul = modifier * Math.Max( 1, multiply / 5f ); + float yMul = multiply == 1 ? 1 : multiply / 5f; float gravity = useLiquidGravity ? liquidGrav : normalGrav; Move( xMoving, zMoving, factor * multiply, normalDrag, gravity, yMul ); diff --git a/ClassicalSharp/Physics/Entity.Physics.cs b/ClassicalSharp/Entities/PhysicsEntity.cs similarity index 94% rename from ClassicalSharp/Physics/Entity.Physics.cs rename to ClassicalSharp/Entities/PhysicsEntity.cs index 6419cc135..cfb75e3a8 100644 --- a/ClassicalSharp/Physics/Entity.Physics.cs +++ b/ClassicalSharp/Entities/PhysicsEntity.cs @@ -1,221 +1,221 @@ -using System; -using System.Collections.Generic; -using OpenTK; - -namespace ClassicalSharp { - - public partial class Entity { - - protected bool onGround, collideX, collideY, collideZ; - protected Map map; - protected BlockInfo info; - public const float Adjustment = 0.001f; - - protected byte GetPhysicsBlockId( int x, int y, int z ) { - if( x < 0 || x >= map.Width || z < 0 || z >= map.Length || y < 0 ) return (byte)Block.Bedrock; - if( y >= map.Height ) return (byte)Block.Air; - return map.GetBlock( x, y, z ); - } - - bool GetBoundingBox( byte block, int x, int y, int z, out BoundingBox box ) { - box = new BoundingBox( Vector3.Zero, Vector3.Zero ); - if( info.CollideType[block] != BlockCollideType.Solid ) return false; - Vector3 min = new Vector3( x, y, z ); - Vector3 max = new Vector3( x + 1, y + info.Height[block], z + 1 ); - box = new BoundingBox( min, max ); - return true; - } - - struct State { - public BoundingBox BlockBB; - public byte Block; - public float tSquared; - - public State( BoundingBox bb, byte block, float tSquared ) { - BlockBB = bb; - Block = block; - this.tSquared = tSquared; - } - } - - // TODO: test for corner cases, and refactor this. - static State[] stateCache = new State[0]; - class StateComparer : IComparer { - public int Compare( State x, State y ) { - return x.tSquared.CompareTo( y.tSquared ); - } - } - static StateComparer comparer = new StateComparer(); - protected void MoveAndWallSlide() { - if( Velocity == Vector3.Zero ) - return; - Vector3 size = CollisionSize; - BoundingBox entityBB, entityExtentBB; - int count = 0; - FindReachableBlocks( ref count, ref size, out entityBB, out entityExtentBB ); - CollideWithReachableBlocks( count, ref size, ref entityBB, ref entityExtentBB ); - } - - void FindReachableBlocks( ref int count, ref Vector3 size, - out BoundingBox entityBB, out BoundingBox entityExtentBB ) { - Vector3 vel = Velocity; - Vector3 pos = Position; - entityBB = 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 - ); - - // Exact maximum extent the entity can reach, and the equivalent map coordinates. - entityExtentBB = new BoundingBox( - vel.X < 0 ? entityBB.Min.X + vel.X : entityBB.Min.X, - vel.Y < 0 ? entityBB.Min.Y + vel.Y : entityBB.Min.Y, - vel.Z < 0 ? entityBB.Min.Z + vel.Z : entityBB.Min.Z, - vel.X > 0 ? entityBB.Max.X + vel.X : entityBB.Max.X, - vel.Y > 0 ? entityBB.Max.Y + vel.Y : entityBB.Max.Y, - vel.Z > 0 ? entityBB.Max.Z + vel.Z : entityBB.Max.Z - ); - Vector3I min = Vector3I.Floor( entityExtentBB.Min ); - Vector3I max = Vector3I.Floor( entityExtentBB.Max ); - - int elements = ( max.X + 1 - min.X ) * ( max.Y + 1 - min.Y ) * ( max.Z + 1 - min.Z ); - if( elements > stateCache.Length ) { - stateCache = new State[elements]; - } - - for( int x = min.X; x <= max.X; x++ ) { - for( int y = min.Y; y <= max.Y; y++ ) { - for( int z = min.Z; z <= max.Z; z++ ) { - byte blockId = GetPhysicsBlockId( x, y, z ); - BoundingBox blockBB; - if( !GetBoundingBox( blockId, x, y, z, out blockBB ) ) continue; - if( !entityExtentBB.Intersects( blockBB ) ) continue; // necessary for some non whole blocks. (slabs) - - float tx = 0, ty = 0, tz = 0; - CalcTime( ref vel, ref entityBB, ref blockBB, out tx, out ty, out tz ); - if( tx > 1 || ty > 1 || tz > 1 ) continue; - float tSquared = tx * tx + ty * ty + tz * tz; - stateCache[count++] = new State( blockBB, blockId, tSquared ); - } - } - } - } - - void CollideWithReachableBlocks( int count, ref Vector3 size, - ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { - bool wasOn = onGround; - onGround = false; - Array.Sort( stateCache, 0, count, comparer ); - collideX = false; collideY = false; collideZ = false; - - for( int i = 0; i < count; i++ ) { - State state = stateCache[i]; - BoundingBox blockBB = state.BlockBB; - if( !entityExtentBB.Intersects( blockBB ) ) continue; - - float tx = 0, ty = 0, tz = 0; - CalcTime( ref Velocity, ref entityBB, ref blockBB, out tx, out ty, out tz ); - if( tx > 1 || ty > 1 || tz > 1 ) - Utils.LogWarning( "t > 1 in physics calculation.. this shouldn't have happened." ); - BoundingBox finalBB = entityBB.Offset( Velocity * new Vector3( tx, ty, tz ) ); - - if( finalBB.Min.Y >= blockBB.Max.Y ) { - Position.Y = blockBB.Max.Y + Adjustment; - onGround = true; - ClipY( ref size, ref entityBB, ref entityExtentBB ); - } else if( finalBB.Max.Y <= blockBB.Min.Y ) { - Position.Y = blockBB.Min.Y - size.Y - Adjustment; - ClipY( ref size, ref entityBB, ref entityExtentBB ); - } else if( finalBB.Min.X >= blockBB.Max.X ) { - if( !wasOn || !DidSlide( ref blockBB, ref size, ref finalBB, ref entityBB, ref entityExtentBB ) ) { - Position.X = blockBB.Max.X + size.X / 2 + Adjustment; - ClipX( ref size, ref entityBB, ref entityExtentBB ); - } - } else if( finalBB.Max.X <= blockBB.Min.X ) { - if( !wasOn || !DidSlide( ref blockBB, ref size, ref finalBB, ref entityBB, ref entityExtentBB ) ) { - Position.X = blockBB.Min.X - size.X / 2 - Adjustment; - ClipX( ref size, ref entityBB, ref entityExtentBB ); - } - } else if( finalBB.Min.Z >= blockBB.Max.Z ) { - if( !wasOn || !DidSlide( ref blockBB, ref size, ref finalBB, ref entityBB, ref entityExtentBB ) ) { - Position.Z = blockBB.Max.Z + size.Z / 2 + Adjustment; - ClipZ( ref size, ref entityBB, ref entityExtentBB ); - } - } else if( finalBB.Max.Z <= blockBB.Min.Z ) { - if( !wasOn || !DidSlide( ref blockBB, ref size, ref finalBB, ref entityBB, ref entityExtentBB ) ) { - Position.Z = blockBB.Min.Z - size.Z / 2 - Adjustment; - ClipZ( ref size, ref entityBB, ref entityExtentBB ); - } - } - } - } - - bool DidSlide( ref BoundingBox blockBB, ref Vector3 size, ref BoundingBox finalBB, - ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { - float yDist = blockBB.Max.Y - entityBB.Min.Y; - if( yDist > 0 && yDist <= StepSize + 0.01f ) { - - // Adjust entity bounding box to include the block being tested - BoundingBox adjFinalBB = finalBB; - adjFinalBB.Min.X = Math.Min( finalBB.Min.X, blockBB.Min.X + Adjustment ); - adjFinalBB.Max.X = Math.Max( finalBB.Max.X, blockBB.Max.X - Adjustment ); - adjFinalBB.Min.Y = (float)Math.Ceiling( blockBB.Max.Y ) + Adjustment; - adjFinalBB.Max.Y = adjFinalBB.Min.Y + size.Y; - adjFinalBB.Min.Z = Math.Min( finalBB.Min.Z, blockBB.Min.Z + Adjustment ); - adjFinalBB.Max.Z = Math.Max( finalBB.Max.Z, blockBB.Max.Z - Adjustment ); - - Vector3I min = Vector3I.Floor( adjFinalBB.Min ); - Vector3I max = Vector3I.Floor( adjFinalBB.Max ); - for( int x = min.X; x <= max.X; x++ ) { - for( int y = min.Y; y <= max.Y; y++ ) { - for( int z = min.Z; z <= max.Z; z++ ) { - if( info.CollideType[GetPhysicsBlockId( x, y, z )] == BlockCollideType.Solid ) - return false; - } - } - } - - Position.Y = blockBB.Max.Y + Adjustment; - onGround = true; - ClipY( ref size, ref entityBB, ref entityExtentBB ); - return true; - } - return false; - } - - void ClipX( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { - Velocity.X = 0; - entityBB.Min.X = entityExtentBB.Min.X = Position.X - size.X / 2; - entityBB.Max.X = entityExtentBB.Max.X = Position.X + size.X / 2; - collideX = true; - } - - void ClipY( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { - Velocity.Y = 0; - entityBB.Min.Y = entityExtentBB.Min.Y = Position.Y; - entityBB.Max.Y = entityExtentBB.Max.Y = Position.Y + size.Y; - collideY = true; - } - - void ClipZ( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { - Velocity.Z = 0; - entityBB.Min.Z = entityExtentBB.Min.Z = Position.Z - size.Z / 2; - entityBB.Max.Z = entityExtentBB.Max.Z = Position.Z + size.Z / 2; - collideZ = true; - } - - static void CalcTime( ref Vector3 vel, ref BoundingBox entityBB, ref BoundingBox blockBB, - out float tx, out float ty, out float tz ) { - float dx = vel.X > 0 ? blockBB.Min.X - entityBB.Max.X : entityBB.Min.X - blockBB.Max.X; - float dy = vel.Y > 0 ? blockBB.Min.Y - entityBB.Max.Y : entityBB.Min.Y - blockBB.Max.Y; - float dz = vel.Z > 0 ? blockBB.Min.Z - entityBB.Max.Z : entityBB.Min.Z - blockBB.Max.Z; - - tx = vel.X == 0 ? float.PositiveInfinity : Math.Abs( dx / vel.X ); - ty = vel.Y == 0 ? float.PositiveInfinity : Math.Abs( dy / vel.Y ); - tz = vel.Z == 0 ? float.PositiveInfinity : Math.Abs( dz / vel.Z ); - - if( entityBB.XIntersects( blockBB ) ) tx = 0; - if( entityBB.YIntersects( blockBB ) ) ty = 0; - if( entityBB.ZIntersects( blockBB ) ) tz = 0; - } - } +using System; +using System.Collections.Generic; +using OpenTK; + +namespace ClassicalSharp { + + public abstract class PhysicsEntity : Entity { + + public PhysicsEntity( Game game ) : base( game ) { + } + protected bool onGround, collideX, collideY, collideZ; + + protected byte GetPhysicsBlockId( int x, int y, int z ) { + if( x < 0 || x >= map.Width || z < 0 || z >= map.Length || y < 0 ) return (byte)Block.Bedrock; + if( y >= map.Height ) return (byte)Block.Air; + return map.GetBlock( x, y, z ); + } + + bool GetBoundingBox( byte block, int x, int y, int z, out BoundingBox box ) { + box = new BoundingBox( Vector3.Zero, Vector3.Zero ); + if( info.CollideType[block] != BlockCollideType.Solid ) return false; + Vector3 min = new Vector3( x, y, z ); + Vector3 max = new Vector3( x + 1, y + info.Height[block], z + 1 ); + box = new BoundingBox( min, max ); + return true; + } + + struct State { + public BoundingBox BlockBB; + public byte Block; + public float tSquared; + + public State( BoundingBox bb, byte block, float tSquared ) { + BlockBB = bb; + Block = block; + this.tSquared = tSquared; + } + } + + // TODO: test for corner cases, and refactor this. + static State[] stateCache = new State[0]; + class StateComparer : IComparer { + public int Compare( State x, State y ) { + return x.tSquared.CompareTo( y.tSquared ); + } + } + + static StateComparer comparer = new StateComparer(); + protected void MoveAndWallSlide() { + if( Velocity == Vector3.Zero ) + return; + Vector3 size = CollisionSize; + BoundingBox entityBB, entityExtentBB; + int count = 0; + FindReachableBlocks( ref count, ref size, out entityBB, out entityExtentBB ); + CollideWithReachableBlocks( count, ref size, ref entityBB, ref entityExtentBB ); + } + + void FindReachableBlocks( ref int count, ref Vector3 size, + out BoundingBox entityBB, out BoundingBox entityExtentBB ) { + Vector3 vel = Velocity; + Vector3 pos = Position; + entityBB = 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 + ); + + // Exact maximum extent the entity can reach, and the equivalent map coordinates. + entityExtentBB = new BoundingBox( + vel.X < 0 ? entityBB.Min.X + vel.X : entityBB.Min.X, + vel.Y < 0 ? entityBB.Min.Y + vel.Y : entityBB.Min.Y, + vel.Z < 0 ? entityBB.Min.Z + vel.Z : entityBB.Min.Z, + vel.X > 0 ? entityBB.Max.X + vel.X : entityBB.Max.X, + vel.Y > 0 ? entityBB.Max.Y + vel.Y : entityBB.Max.Y, + vel.Z > 0 ? entityBB.Max.Z + vel.Z : entityBB.Max.Z + ); + Vector3I min = Vector3I.Floor( entityExtentBB.Min ); + Vector3I max = Vector3I.Floor( entityExtentBB.Max ); + + int elements = (max.X + 1 - min.X) * (max.Y + 1 - min.Y) * (max.Z + 1 - min.Z); + if( elements > stateCache.Length ) { + stateCache = new State[elements]; + } + + for( int x = min.X; x <= max.X; x++ ) { + for( int y = min.Y; y <= max.Y; y++ ) { + for( int z = min.Z; z <= max.Z; z++ ) { + byte blockId = GetPhysicsBlockId( x, y, z ); + BoundingBox blockBB; + if( !GetBoundingBox( blockId, x, y, z, out blockBB ) ) continue; + if( !entityExtentBB.Intersects( blockBB ) ) continue; // necessary for some non whole blocks. (slabs) + + float tx = 0, ty = 0, tz = 0; + CalcTime( ref vel, ref entityBB, ref blockBB, out tx, out ty, out tz ); + if( tx > 1 || ty > 1 || tz > 1 ) continue; + float tSquared = tx * tx + ty * ty + tz * tz; + stateCache[count++] = new State( blockBB, blockId, tSquared ); + } + } + } + } + + void CollideWithReachableBlocks( int count, ref Vector3 size, + ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { + bool wasOn = onGround; + onGround = false; + Array.Sort( stateCache, 0, count, comparer ); + collideX = false; collideY = false; collideZ = false; + + for( int i = 0; i < count; i++ ) { + State state = stateCache[i]; + BoundingBox blockBB = state.BlockBB; + if( !entityExtentBB.Intersects( blockBB ) ) continue; + + float tx = 0, ty = 0, tz = 0; + CalcTime( ref Velocity, ref entityBB, ref blockBB, out tx, out ty, out tz ); + if( tx > 1 || ty > 1 || tz > 1 ) + Utils.LogWarning( "t > 1 in physics calculation.. this shouldn't have happened." ); + BoundingBox finalBB = entityBB.Offset( Velocity * new Vector3( tx, ty, tz ) ); + + if( finalBB.Min.Y >= blockBB.Max.Y ) { + Position.Y = blockBB.Max.Y + Adjustment; + onGround = true; + ClipY( ref size, ref entityBB, ref entityExtentBB ); + } else if( finalBB.Max.Y <= blockBB.Min.Y ) { + Position.Y = blockBB.Min.Y - size.Y - Adjustment; + ClipY( ref size, ref entityBB, ref entityExtentBB ); + } else if( finalBB.Min.X >= blockBB.Max.X ) { + if( !wasOn || !DidSlide( ref blockBB, ref size, ref finalBB, ref entityBB, ref entityExtentBB ) ) { + Position.X = blockBB.Max.X + size.X / 2 + Adjustment; + ClipX( ref size, ref entityBB, ref entityExtentBB ); + } + } else if( finalBB.Max.X <= blockBB.Min.X ) { + if( !wasOn || !DidSlide( ref blockBB, ref size, ref finalBB, ref entityBB, ref entityExtentBB ) ) { + Position.X = blockBB.Min.X - size.X / 2 - Adjustment; + ClipX( ref size, ref entityBB, ref entityExtentBB ); + } + } else if( finalBB.Min.Z >= blockBB.Max.Z ) { + if( !wasOn || !DidSlide( ref blockBB, ref size, ref finalBB, ref entityBB, ref entityExtentBB ) ) { + Position.Z = blockBB.Max.Z + size.Z / 2 + Adjustment; + ClipZ( ref size, ref entityBB, ref entityExtentBB ); + } + } else if( finalBB.Max.Z <= blockBB.Min.Z ) { + if( !wasOn || !DidSlide( ref blockBB, ref size, ref finalBB, ref entityBB, ref entityExtentBB ) ) { + Position.Z = blockBB.Min.Z - size.Z / 2 - Adjustment; + ClipZ( ref size, ref entityBB, ref entityExtentBB ); + } + } + } + } + + bool DidSlide( ref BoundingBox blockBB, ref Vector3 size, ref BoundingBox finalBB, + ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { + float yDist = blockBB.Max.Y - entityBB.Min.Y; + if( yDist > 0 && yDist <= StepSize + 0.01f ) { + + // Adjust entity bounding box to include the block being tested + BoundingBox adjFinalBB = finalBB; + adjFinalBB.Min.X = Math.Min( finalBB.Min.X, blockBB.Min.X + Adjustment ); + adjFinalBB.Max.X = Math.Max( finalBB.Max.X, blockBB.Max.X - Adjustment ); + adjFinalBB.Min.Y = (float)Math.Ceiling( blockBB.Max.Y ) + Adjustment; + adjFinalBB.Max.Y = adjFinalBB.Min.Y + size.Y; + adjFinalBB.Min.Z = Math.Min( finalBB.Min.Z, blockBB.Min.Z + Adjustment ); + adjFinalBB.Max.Z = Math.Max( finalBB.Max.Z, blockBB.Max.Z - Adjustment ); + + Vector3I min = Vector3I.Floor( adjFinalBB.Min ); + Vector3I max = Vector3I.Floor( adjFinalBB.Max ); + for( int x = min.X; x <= max.X; x++ ) { + for( int y = min.Y; y <= max.Y; y++ ) { + for( int z = min.Z; z <= max.Z; z++ ) { + if( info.CollideType[GetPhysicsBlockId( x, y, z )] == BlockCollideType.Solid ) + return false; + } + } + } + + Position.Y = blockBB.Max.Y + Adjustment; + onGround = true; + ClipY( ref size, ref entityBB, ref entityExtentBB ); + return true; + } + return false; + } + + void ClipX( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { + Velocity.X = 0; + entityBB.Min.X = entityExtentBB.Min.X = Position.X - size.X / 2; + entityBB.Max.X = entityExtentBB.Max.X = Position.X + size.X / 2; + collideX = true; + } + + void ClipY( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { + Velocity.Y = 0; + entityBB.Min.Y = entityExtentBB.Min.Y = Position.Y; + entityBB.Max.Y = entityExtentBB.Max.Y = Position.Y + size.Y; + collideY = true; + } + + void ClipZ( ref Vector3 size, ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { + Velocity.Z = 0; + entityBB.Min.Z = entityExtentBB.Min.Z = Position.Z - size.Z / 2; + entityBB.Max.Z = entityExtentBB.Max.Z = Position.Z + size.Z / 2; + collideZ = true; + } + + static void CalcTime( ref Vector3 vel, ref BoundingBox entityBB, ref BoundingBox blockBB, + out float tx, out float ty, out float tz ) { + float dx = vel.X > 0 ? blockBB.Min.X - entityBB.Max.X : entityBB.Min.X - blockBB.Max.X; + float dy = vel.Y > 0 ? blockBB.Min.Y - entityBB.Max.Y : entityBB.Min.Y - blockBB.Max.Y; + float dz = vel.Z > 0 ? blockBB.Min.Z - entityBB.Max.Z : entityBB.Min.Z - blockBB.Max.Z; + + tx = vel.X == 0 ? float.PositiveInfinity : Math.Abs( dx / vel.X ); + ty = vel.Y == 0 ? float.PositiveInfinity : Math.Abs( dy / vel.Y ); + tz = vel.Z == 0 ? float.PositiveInfinity : Math.Abs( dz / vel.Z ); + + if( entityBB.XIntersects( blockBB ) ) tx = 0; + if( entityBB.YIntersects( blockBB ) ) ty = 0; + if( entityBB.ZIntersects( blockBB ) ) tz = 0; + } + } } \ No newline at end of file diff --git a/ClassicalSharp/Entities/Player.cs b/ClassicalSharp/Entities/Player.cs index 0434619c4..2e4d1d800 100644 --- a/ClassicalSharp/Entities/Player.cs +++ b/ClassicalSharp/Entities/Player.cs @@ -1,13 +1,11 @@ -using System; -using System.Drawing; -using OpenTK; -using ClassicalSharp.Network; -using ClassicalSharp.Model; -using ClassicalSharp.Renderers; +using System; +using System.Drawing; +using ClassicalSharp.Network; +using OpenTK; namespace ClassicalSharp { - public abstract partial class Player : Entity { + public abstract partial class Player : PhysicsEntity { /// Gets the position of the player's eye in the world. public Vector3 EyePosition { @@ -36,9 +34,8 @@ namespace ClassicalSharp { } Block GetBlock( Vector3 coords ) { - Vector3I blockCoords = Vector3I.Floor( coords ); - return map.IsValidPos( blockCoords ) ? - (Block)map.GetBlock( blockCoords ) : Block.Air; + Vector3I p = Vector3I.Floor( coords ); + return (Block)map.SafeGetBlock( p.X, p.Y, p.Z ); } public abstract void Tick( double delta ); diff --git a/ClassicalSharp/GraphicsAPI/IGraphicsApi.cs b/ClassicalSharp/GraphicsAPI/IGraphicsApi.cs index e196597c6..be691a6bf 100644 --- a/ClassicalSharp/GraphicsAPI/IGraphicsApi.cs +++ b/ClassicalSharp/GraphicsAPI/IGraphicsApi.cs @@ -246,6 +246,7 @@ namespace ClassicalSharp.GraphicsAPI { LoadIdentityMatrix(); AlphaBlending = true; if( setFog ) Fog = false; + //OpenTK.Graphics.OpenGL.GL.PolygonMode( 0x0408, 0x1B02 ); } protected virtual void LoadOrthoMatrix( float width, float height ) { @@ -262,6 +263,7 @@ namespace ClassicalSharp.GraphicsAPI { DepthTest = true; AlphaBlending = false; if( setFog ) Fog = true; + //OpenTK.Graphics.OpenGL.GL.PolygonMode( 0x0408, 0x1B01 ); } internal unsafe int MakeDefaultIb() { diff --git a/ClassicalSharp/Rendering/MapRenderer.Occlusion.cs b/ClassicalSharp/Rendering/MapRenderer.Occlusion.cs new file mode 100644 index 000000000..e2b9cf3ca --- /dev/null +++ b/ClassicalSharp/Rendering/MapRenderer.Occlusion.cs @@ -0,0 +1,195 @@ +using System; +using ClassicalSharp.GraphicsAPI; +using OpenTK; + +namespace ClassicalSharp { + + public partial class MapRenderer : IDisposable { + + void SimpleOcclusionCulling() { // TODO: broken + Vector3 p = game.LocalPlayer.EyePosition; + Vector3I chunkLoc = Vector3I.Floor( p ); + Utils.Clamp( ref chunkLoc.X, 0, game.Map.Width - 1 ); + Utils.Clamp( ref chunkLoc.Y, 0, game.Map.Height - 1 ); + Utils.Clamp( ref chunkLoc.Z, 0, game.Map.Length- 1 ); + + int cx = chunkLoc.X >> 4; + int cy = chunkLoc.Y >> 4; + int cz = chunkLoc.Z >> 4; + + ChunkInfo chunkIn = unsortedChunks[cx + chunksX * (cy + cz * chunksY)]; + byte chunkInFlags = chunkIn.OcclusionFlags; + chunkIn.OcclusionFlags = 0; + + ChunkQueue queue = new ChunkQueue( chunksX * chunksY * chunksZ ); + for( int i = 0; i < chunks.Length; i++ ) { + chunks[i].Visited = false; + chunks[i].Occluded = false; + chunks[i].VisibilityFlags = 0; + } + + chunkIn.Visited = true; + QueueChunk( cx - 1, cy, cz, queue ); + QueueChunk( cx + 1, cy, cz, queue ); + QueueChunk( cx, cy - 1, cz, queue ); + QueueChunk( cx, cy + 1, cz, queue ); + QueueChunk( cx, cy, cz - 1, queue ); + QueueChunk( cx, cy, cz + 1, queue ); + + ProcessQueue( chunkIn, queue ); + chunkIn.OcclusionFlags = chunkInFlags; + } + + void ProcessQueue( ChunkInfo src, ChunkQueue queue ) { + Vector3I p = new Vector3I( src.CentreX, src.CentreY, src.CentreZ ); + while( queue.Size > 0 ) { + ChunkInfo chunk = queue.Dequeue(); + chunk.VisibilityFlags = chunk.OcclusionFlags; + int x1 = chunk.CentreX - 8, x2 = chunk.CentreX + 8; + int y1 = chunk.CentreY - 8, y2 = chunk.CentreY + 8; + int z1 = chunk.CentreZ - 8, z2 = chunk.CentreZ + 8; + int cx = chunk.CentreX >> 4; + int cy = chunk.CentreY >> 4; + int cz = chunk.CentreZ >> 4; + + int xOffset, yOffset, zOffset; + int dx = Math.Max( x1 - p.X, Math.Max( 0, p.X - x2 ) ); + int dy = Math.Max( y1 - p.Y, Math.Max( 0, p.Y - y2 ) ); + int dz = Math.Max( z1 - p.Z, Math.Max( 0, p.Z - z2 ) ); + int distX, distY, distZ; + + // X axis collisions + int dxLeft = Math.Abs( x1 - p.X ), dxRight = Math.Abs( x2 - p.X ); + if( dxLeft < dxRight ) { + distX = dxLeft * dxLeft + dy * dy + dz * dz; xOffset = -1; + } else { + distX = dxRight * dxRight + dy * dy + dz * dz; xOffset = 1; + } + + // Z axis collisions + int dxFront = Math.Abs( z1 - p.Z ), dxBack = Math.Abs( z2 - p.Z ); + if( dxFront < dxBack ) { + distZ = dx * dx + dy * dy + dxFront * dxFront; zOffset = -1; + } else { + distZ = dx * dx + dy * dy + dxBack * dxBack; zOffset = 1; + } + + // Y axis collisions + int dxBottom = Math.Abs( y1 - p.Y ), dxTop = Math.Abs( y2 - p.Y ); + if( dxBottom < dxTop ) { + distY = dx * dx + dxBottom * dxBottom + dz * dz; yOffset = -1; + } else { + distY = dx * dx + dxTop * dxTop + dz * dz; yOffset = 1; + } + + int distMin = Math.Min( distX, Math.Min( distY, distZ ) ); + bool occlude = true; + byte flags = 0; + if( distMin == distX ) + OccludeX( cx, cy, cz, xOffset, ref occlude, ref flags ); + if( distMin == distZ ) + OccludeZ( cx, cy, cz, zOffset, ref occlude, ref flags ); + if( distMin == distY ) + OccludeY( cx, cy, cz, yOffset, ref occlude, ref flags ); + + if( occlude ) + chunk.Occluded = true; + chunk.VisibilityFlags = (byte)( flags | chunk.OcclusionFlags ); + QueueChunk( cx - 1, cy, cz, queue ); + QueueChunk( cx + 1, cy, cz, queue ); + QueueChunk( cx, cy, cz - 1, queue ); + QueueChunk( cx, cy, cz + 1, queue ); + QueueChunk( cx, cy - 1, cz, queue ); + QueueChunk( cx, cy + 1, cz, queue ); + } + Console.WriteLine( "======================" ); + } + + void OccludeX( int cx, int cy, int cz, int xOffset, ref bool occlude, ref byte flags ) { + cx += xOffset; + if( cx >= 0 && cx < chunksX ) { + ChunkInfo neighbour = unsortedChunks[cx + chunksX * (cy + cz * chunksY)]; + if( (neighbour.VisibilityFlags & 1) == 0 ) + occlude = false; + else + flags |= 1; + } + } + + void OccludeZ( int cx, int cy, int cz, int zOffset, ref bool occlude, ref byte flags ) { + cz += zOffset; + if( cz >= 0 && cz < chunksZ ) { + ChunkInfo neighbour = unsortedChunks[cx + chunksX * (cy + cz * chunksY)]; + if( (neighbour.VisibilityFlags & 2) == 0 ) + occlude = false; + else + flags |= 2; + } + } + + void OccludeY( int cx, int cy, int cz, int yOffset, ref bool occlude, ref byte flags ) { + cy += yOffset; + if( cy >= 0 && cy< chunksY ) { + ChunkInfo neighbour = unsortedChunks[cx + chunksX * (cy + cz * chunksY)]; + if( (neighbour.VisibilityFlags & 4) == 0 ) + occlude = false; + else + flags |= 4; + } + } + + static float DistToRecSquared( Vector3 p, int x1, int y1, int z1, int x2, int y2, int z2 ) { + float dx = Math.Max( x1 - p.X, Math.Max( 0, p.X - x2 ) ); + float dy = Math.Max( y1 - p.Y, Math.Max( 0, p.Y - y2 ) ); + float dz = Math.Max( z1 - p.Z, Math.Max( 0, p.Z - z2 ) ); + return dx * dx + dy * dy + dz * dz; + } + + void QueueChunk( int cx, int cy, int cz, ChunkQueue queue ) { + if( cx >= 0 && cy >= 0 && cz >= 0 && cx < chunksX && cy < chunksY && cz < chunksZ ) { + ChunkInfo info = unsortedChunks[cx + chunksX * (cy + cz * chunksY)]; + if( !info.Visited ) + queue.Enqueue( info ); + info.Visited = true; + } + } + + class ChunkQueue { + ChunkInfo[] array; + int head, tail; + public int Size; + + public ChunkQueue( int capacity ) { + array = new ChunkInfo[capacity]; + } + + public void Clear() { + Array.Clear( array, 0, Size ); + head = 0; + tail = 0; + Size = 0; + } + + public void Enqueue( ChunkInfo item ) { + if( Size == array.Length ) + throw new InvalidOperationException( "Queue limit reached" ); + + array[tail] = item; + tail = (tail + 1) % array.Length; + Size++; + } + + public ChunkInfo Dequeue() { + if( Size == 0 ) + throw new InvalidOperationException( "No elements left in queue" ); + + ChunkInfo item = array[head]; + array[head] = null; + head = (head + 1) % array.Length; + Size--; + return item; + } + } + } +} + diff --git a/ClassicalSharp/Rendering/MapRenderer.Rendering.cs b/ClassicalSharp/Rendering/MapRenderer.Rendering.cs index c2b230dc7..b09995781 100644 --- a/ClassicalSharp/Rendering/MapRenderer.Rendering.cs +++ b/ClassicalSharp/Rendering/MapRenderer.Rendering.cs @@ -14,7 +14,7 @@ namespace ClassicalSharp { void RenderNormalBatch( int batch ) { for( int i = 0; i < chunks.Length; i++ ) { ChunkInfo info = chunks[i]; - if( info.NormalParts == null || !info.Visible ) continue; + if( info.NormalParts == null || !info.Visible || info.Occluded ) continue; ChunkPartInfo part = info.NormalParts[batch]; if( part.IndicesCount == 0 ) continue; @@ -33,7 +33,7 @@ namespace ClassicalSharp { void RenderTranslucentBatch( int batch ) { for( int i = 0; i < chunks.Length; i++ ) { ChunkInfo info = chunks[i]; - if( info.TranslucentParts == null || !info.Visible ) continue; + if( info.TranslucentParts == null || !info.Visible || info.Occluded ) continue; ChunkPartInfo part = info.TranslucentParts[batch]; if( part.IndicesCount == 0 ) continue; @@ -45,7 +45,7 @@ namespace ClassicalSharp { void RenderTranslucentBatchDepthPass( int batch ) { for( int i = 0; i < chunks.Length; i++ ) { ChunkInfo info = chunks[i]; - if( info.TranslucentParts == null || !info.Visible ) continue; + if( info.TranslucentParts == null || !info.Visible || info.Occluded ) continue; ChunkPartInfo part = info.TranslucentParts[batch]; if( part.IndicesCount == 0 ) continue; diff --git a/ClassicalSharp/Rendering/MapRenderer.cs b/ClassicalSharp/Rendering/MapRenderer.cs index a1e634d3a..7a39180f8 100644 --- a/ClassicalSharp/Rendering/MapRenderer.cs +++ b/ClassicalSharp/Rendering/MapRenderer.cs @@ -11,8 +11,8 @@ namespace ClassicalSharp { class ChunkInfo { public ushort CentreX, CentreY, CentreZ; - public bool Visible = true; - public bool Empty = false; + public bool Visible = true, Occluded = false; + public bool Visited = false, Empty = false; public bool DrawLeft, DrawRight, DrawFront, DrawBack, DrawBottom, DrawTop; public byte OcclusionFlags, VisibilityFlags; @@ -216,7 +216,6 @@ namespace ClassicalSharp { if( chunks == null ) return; UpdateSortOrder(); UpdateChunks( deltaTime ); - //SimpleOcclusionCulling(); RenderNormal(); game.MapEnvRenderer.Render( deltaTime ); @@ -255,6 +254,7 @@ namespace ClassicalSharp { info.DrawBottom = !(dY1 <= 0 && dY2 <= 0); info.DrawTop = !(dY1 >= 0 && dY2 >= 0); } + //SimpleOcclusionCulling(); } int chunksTarget = 4; @@ -332,141 +332,5 @@ namespace ClassicalSharp { api.AlphaBlending = false; api.Texturing = false; } - - void SimpleOcclusionCulling() { // TODO: broken - Vector3 p = game.LocalPlayer.EyePosition; - Vector3I chunkLoc = Vector3I.Floor( p ); - ChunkInfo chunkIn = null; - byte chunkInFlags = 0; - // We have to pretend that the chunk the player is in does no occlusion - // (because for example, only X15 may be filled while X0 is air) - if( game.Map.IsValidPos( chunkLoc ) ) { - int cx = chunkLoc.X >> 4; - int cy = chunkLoc.Y >> 4; - int cz = chunkLoc.Z >> 4; - chunkIn = unsortedChunks[cx + chunksX * (cy + cz * chunksY)]; - chunkInFlags = chunkIn.OcclusionFlags; - chunkIn.OcclusionFlags = 0; - } - - for( int i = 0; i < chunks.Length; i++ ) { - ChunkInfo chunk = chunks[i]; - chunk.VisibilityFlags = chunk.OcclusionFlags; - int x1 = chunk.CentreX - 8, x2 = chunk.CentreX + 8; - int y1 = chunk.CentreY - 8, y2 = chunk.CentreY + 8; - int z1 = chunk.CentreZ - 8, z2 = chunk.CentreZ + 8; - int xOffset = 0, yOffset = 0, zOffset = 0; - float dx = 0, dy = 0, dz = 0; - float distX, distY, distZ; - - // TODO: two axes with same distance - - // X axis collisions - dy = Math.Max( y1 - p.Y, Math.Max( 0, p.Y - y2 ) ); - dz = Math.Max( z1 - p.Z, Math.Max( 0, p.Z - z2 ) ); - - float dxLeft = Math.Max( x1 - p.X, Math.Max( 0, p.X - x1 ) ); - float dxRight = Math.Max( x2 - p.X, Math.Max( 0, p.X - x2 ) ); - if( dxLeft < dxRight ) { - xOffset = -1; - distX = dxLeft * dxLeft + dy * dy + dz * dz; - } else { - xOffset = 1; - distX = dxRight * dxRight + dy * dy + dz * dz; - } - - // Z axis collisions - dx = Math.Max( x1 - p.X, Math.Max( 0, p.X - x2 ) ); - dy = Math.Max( y1 - p.Y, Math.Max( 0, p.Y - y2 ) ); - - float dxFront = Math.Max( z1 - p.Z, Math.Max( 0, p.Z - z1 ) ); - float dxBack = Math.Max( z2 - p.Z, Math.Max( 0, p.Z - z2 ) ); - if( dxFront < dxBack ) { - zOffset = -1; - distZ = dx * dx + dy * dy + dxFront * dxFront; - } else { - zOffset = 1; - distZ = dx * dx + dy * dy + dxBack * dxBack; - } - - // Y axis collisions - dx = Math.Max( x1 - p.X, Math.Max( 0, p.X - x2 ) ); - dz = Math.Max( z1 - p.Z, Math.Max( 0, p.Z - z2 ) ); - - float dxBottom = Math.Max( y1 - p.Y, Math.Max( 0, p.Y - y1 ) ); - float dxTop = Math.Max( y2 - p.Y, Math.Max( 0, p.Y - y2 ) ); - if( dxBottom < dxTop ) { - yOffset = -1; - distY = dx * dx + dxBottom * dxBottom + dz * dz; - } else { - yOffset = 1; - distY = dx * dx + dxTop * dxTop + dz * dz; - } - - int cx = chunk.CentreX >> 4; - int cy = chunk.CentreY >> 4; - int cz = chunk.CentreZ >> 4; - float distMin = Math.Min( distX, Math.Min( distY, distZ ) ); - - bool occlude = true; - byte flags = 0; - if( Math.Abs( distMin - distX ) < 0.00001f ) - OccludeX( cx, cy, cz, xOffset, ref occlude, ref flags ); - if( Math.Abs( distMin - distZ ) < 0.00001f ) - OccludeZ( cx, cy, cz, zOffset, ref occlude, ref flags ); - if( Math.Abs( distMin - distY ) < 0.00001f ) - OccludeY( cx, cy, cz, yOffset, ref occlude, ref flags ); - - if( occlude ) { - chunk.Visible = false; - chunk.VisibilityFlags = flags; - } - } - - if( chunkIn != null ) { - chunkIn.Visible = true; - chunkIn.OcclusionFlags = chunkInFlags; - } - } - - void OccludeX( int cx, int cy, int cz, int xOffset, ref bool occlude, ref byte flags ) { - cx += xOffset; - if( cx >= 0 && cx < chunksX ) { - ChunkInfo neighbour = unsortedChunks[cx + chunksX * (cy + cz * chunksY)]; - if( (neighbour.VisibilityFlags & 0x1) == 0 ) - occlude = false; - else - flags |= 0x1; - } - } - - void OccludeZ( int cx, int cy, int cz, int zOffset, ref bool occlude, ref byte flags ) { - cz += zOffset; - if( cz >= 0 && cz < chunksZ ) { - ChunkInfo neighbour = unsortedChunks[cx + chunksX * (cy + cz * chunksY)]; - if( (neighbour.VisibilityFlags & 0x2) == 0 ) - occlude = false; - else - flags |= 0x2; - } - } - - void OccludeY( int cx, int cy, int cz, int yOffset, ref bool occlude, ref byte flags ) { - cy += yOffset; - if( cy >= 0 && cy< chunksY ) { - ChunkInfo neighbour = unsortedChunks[cx + chunksX * (cy + cz * chunksY)]; - if( (neighbour.VisibilityFlags & 0x4) == 0 ) - occlude = false; - else - flags |= 0x4; - } - } - - static float DistToRecSquared( Vector3 p, int x1, int y1, int z1, int x2, int y2, int z2 ) { - float dx = Math.Max( x1 - p.X, Math.Max( 0, p.X - x2 ) ); - float dy = Math.Max( y1 - p.Y, Math.Max( 0, p.Y - y2 ) ); - float dz = Math.Max( z1 - p.Z, Math.Max( 0, p.Z - z2 ) ); - return dx * dx + dy * dy + dz * dz; - } } } \ No newline at end of file diff --git a/ClassicalSharp/TexturePack/TerrainAtlas1D.cs b/ClassicalSharp/TexturePack/TerrainAtlas1D.cs index 69f48f6f9..4dc373e2e 100644 --- a/ClassicalSharp/TexturePack/TerrainAtlas1D.cs +++ b/ClassicalSharp/TexturePack/TerrainAtlas1D.cs @@ -7,7 +7,7 @@ namespace ClassicalSharp { /// Represents a 2D packed texture atlas that has been converted into an array of 1D atlases. public sealed class TerrainAtlas1D : IDisposable { - int usedElementsPerAtlas1D; + int elementsPerAtlas1D; internal int elementsPerBitmap; public float invElementSize; public int[] TexIds; @@ -18,38 +18,39 @@ namespace ClassicalSharp { } public TextureRec GetTexRec( int texId, int uCount, out int index ) { - index = texId / usedElementsPerAtlas1D; - int y = texId % usedElementsPerAtlas1D; + index = texId / elementsPerAtlas1D; + int y = texId % elementsPerAtlas1D; return new TextureRec( 0, y * invElementSize, uCount, invElementSize ); } public int Get1DIndex( int texId ) { - return texId / usedElementsPerAtlas1D; + return texId / elementsPerAtlas1D; } public int Get1DRowId( int texId ) { - return texId % usedElementsPerAtlas1D; + return texId % elementsPerAtlas1D; } public void UpdateState( TerrainAtlas2D atlas2D ) { int maxVerSize = Math.Min( 4096, graphics.MaxTextureDimensions ); - int verElements = maxVerSize / atlas2D.elementSize; + int elemPerFullAtlas = maxVerSize / atlas2D.elementSize; int totalElements = TerrainAtlas2D.RowsCount * TerrainAtlas2D.ElementsPerRow; int elemSize = atlas2D.elementSize; - int atlasesCount = Utils.CeilDiv( totalElements, verElements ); - usedElementsPerAtlas1D = Math.Min( verElements, totalElements ); - int atlas1DHeight = Utils.NextPowerOf2( usedElementsPerAtlas1D * atlas2D.elementSize ); + int atlasesCount = Utils.CeilDiv( totalElements, elemPerFullAtlas ); + elementsPerAtlas1D = Math.Min( elemPerFullAtlas, totalElements ); + int atlas1DHeight = Utils.NextPowerOf2( elementsPerAtlas1D * elemSize ); int index = 0; TexIds = new int[atlasesCount]; - Utils.LogDebug( "Loaded new atlas: {0} bmps, {1} per bmp", atlasesCount, usedElementsPerAtlas1D ); + Utils.LogDebug( "Loaded new atlas: {0} bmps, {1} per bmp", atlasesCount, elementsPerAtlas1D ); using( FastBitmap atlas = new FastBitmap( atlas2D.AtlasBitmap, true ) ) { for( int i = 0; i < TexIds.Length; i++ ) { Bitmap atlas1d = new Bitmap( atlas2D.elementSize, atlas1DHeight ); using( FastBitmap dst = new FastBitmap( atlas1d, true ) ) { - for( int y_1D = 0; y_1D < usedElementsPerAtlas1D; y_1D++ ) { + for( int y_1D = 0; y_1D < elementsPerAtlas1D; y_1D++ ) { + if( index >= 256 ) break; int x = index & 0x0F; int y = index >> 4; FastBitmap.MovePortion( x * elemSize, y * elemSize, 0, y_1D * elemSize, atlas, dst, elemSize ); diff --git a/ClassicalSharp/Utils/FastBitmap.cs b/ClassicalSharp/Utils/FastBitmap.cs index 96da49e1c..73c77ee10 100644 --- a/ClassicalSharp/Utils/FastBitmap.cs +++ b/ClassicalSharp/Utils/FastBitmap.cs @@ -71,7 +71,7 @@ namespace ClassicalSharp { /// Returns a pointer to the start of the y'th scanline. public int* GetRowPtr( int y ) { - return (int*)( scan0Byte + ( y * Stride ) ); + return (int*)(scan0Byte + (y * Stride)); } public static void MovePortion( int srcX, int srcY, int dstX, int dstY, FastBitmap src, FastBitmap dst, int size ) {