From fd8e765b8ba20945b36ce2fd15d3c06901699f44 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Sun, 17 Jan 2016 21:56:22 +1100 Subject: [PATCH] Optimise big maps to be even faster, optimise double/triple jump with speed to be much faster and use much less memory. --- ClassicalSharp/Entities/PhysicsEntity.cs | 84 ++++++++---- .../Rendering/MapRenderer.Rendering.cs | 56 +++++++- .../Rendering/MapRenderer.Sorting.cs | 20 +-- ClassicalSharp/Rendering/MapRenderer.cs | 124 ++++++++---------- 4 files changed, 172 insertions(+), 112 deletions(-) diff --git a/ClassicalSharp/Entities/PhysicsEntity.cs b/ClassicalSharp/Entities/PhysicsEntity.cs index 119c7156e..dfe1761cf 100644 --- a/ClassicalSharp/Entities/PhysicsEntity.cs +++ b/ClassicalSharp/Entities/PhysicsEntity.cs @@ -5,7 +5,7 @@ using OpenTK; namespace ClassicalSharp { /// Entity that performs collision detection. - public abstract class PhysicsEntity : Entity { + public abstract partial class PhysicsEntity : Entity { public PhysicsEntity( Game game ) : base( game ) { } @@ -24,33 +24,19 @@ namespace ClassicalSharp { bool GetBoundingBox( byte block, int x, int y, int z, ref BoundingBox box ) { if( info.CollideType[block] != BlockCollideType.Solid ) return false; - - box.Min = new Vector3( x, y, z ) + info.MinBB[block]; - box.Max = new Vector3( x, y, z ) + info.MaxBB[block]; + Add( x, y, z, ref info.MinBB[block], ref box.Min ); + Add( x, y, z, ref info.MaxBB[block], ref box.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; - } + static void Add( int x, int y, int z, ref Vector3 offset, ref Vector3 target ) { + target.X = x + offset.X; + target.Y = y + offset.Y; + target.Z = z + offset.Z; } - // 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(); + // TODO: test for corner cases, and refactor this. protected void MoveAndWallSlide() { if( Velocity == Vector3.Zero ) return; @@ -101,7 +87,7 @@ namespace ClassicalSharp { 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 ); + stateCache[count++] = new State( x, y, z, blockId, tSquared ); } } @@ -109,12 +95,17 @@ namespace ClassicalSharp { ref BoundingBox entityBB, ref BoundingBox entityExtentBB ) { bool wasOn = onGround; onGround = false; - Array.Sort( stateCache, 0, count, comparer ); + if( count > 0 ) + QuickSort( stateCache, 0, count - 1 ); collideX = false; collideY = false; collideZ = false; + BoundingBox blockBB = default(BoundingBox); for( int i = 0; i < count; i++ ) { State state = stateCache[i]; - BoundingBox blockBB = state.BlockBB; + 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; @@ -287,5 +278,48 @@ namespace ClassicalSharp { if( entityBB.YIntersects( blockBB ) ) ty = 0; if( entityBB.ZIntersects( blockBB ) ) tz = 0; } + + + struct State { + public int X, Y, Z; + public float tSquared; + + public State( int x, int y, int z, byte block, float tSquared ) { + X = x << 3; Y = y << 3; Z = z << 3; + X |= (block & 0x07); + Y |= (block & 0x38) >> 3; + Z |= (block & 0xC0) >> 6; + this.tSquared = tSquared; + } + } + static State[] stateCache = new State[0]; + + static void QuickSort( State[] keys, int left, int right ) { + while( left < right ) { + int i = left, j = right; + float pivot = keys[(i + j) / 2].tSquared; + // partition the list + while( i <= j ) { + while( pivot > keys[i].tSquared ) i++; + while( pivot < keys[j].tSquared ) j--; + + if( i <= j ) { + State key = keys[i]; keys[i] = keys[j]; keys[j] = key; + i++; j--; + } + } + + // recurse into the smaller subset + if( j - left <= right - i ) { + if( left < j ) + QuickSort( keys, left, j ); + left = i; + } else { + if( i < right ) + QuickSort( keys, i, right ); + right = j; + } + } + } } } \ No newline at end of file diff --git a/ClassicalSharp/Rendering/MapRenderer.Rendering.cs b/ClassicalSharp/Rendering/MapRenderer.Rendering.cs index 0689d2775..b4dc04a20 100644 --- a/ClassicalSharp/Rendering/MapRenderer.Rendering.cs +++ b/ClassicalSharp/Rendering/MapRenderer.Rendering.cs @@ -4,10 +4,62 @@ using OpenTK; namespace ClassicalSharp { - // TODO: optimise chunk rendering - // --> reduce iterations: liquid and sprite pass only need 1 row public partial class MapRenderer : IDisposable { + // Render solid and fully transparent to fill depth buffer. + // These blocks are treated as having an alpha value of either none or full. + void RenderNormal() { + int[] texIds = game.TerrainAtlas1D.TexIds; + api.SetBatchFormat( VertexFormat.Pos3fTex2fCol4b ); + api.Texturing = true; + api.AlphaTest = true; + + for( int batch = 0; batch < _1DUsed; batch++ ) { + if( pendingNormal[batch] || usedNormal[batch] ) { + api.BindTexture( texIds[batch] ); + RenderNormalBatch( batch ); + pendingNormal[batch] = false; + } + } + api.AlphaTest = false; + api.Texturing = false; + DebugPickedPos(); + } + + // Render translucent(liquid) blocks. These 'blend' into other blocks. + void RenderTranslucent() { + Block block = game.LocalPlayer.BlockAtHead; + drawAllFaces = block == Block.Water || block == Block.StillWater; + // First fill depth buffer + int[] texIds = game.TerrainAtlas1D.TexIds; + api.SetBatchFormat( VertexFormat.Pos3fTex2fCol4b ); + api.Texturing = false; + api.AlphaBlending = false; + api.ColourWrite = false; + for( int batch = 0; batch < _1DUsed; batch++ ) { + if( pendingTranslucent[batch] || usedTranslucent[batch] ) { + RenderTranslucentBatchDepthPass( batch ); + pendingTranslucent[batch] = false; + } + } + + // Then actually draw the transluscent blocks + api.AlphaBlending = true; + api.Texturing = true; + api.ColourWrite = true; + api.DepthWrite = false; // we already calculated depth values in depth pass + + for( int batch = 0; batch < _1DUsed; batch++ ) { + if( !usedTranslucent[batch] ) continue; + api.BindTexture( texIds[batch] ); + RenderTranslucentBatch( batch ); + } + api.DepthWrite = true; + api.AlphaTest = false; + api.AlphaBlending = false; + api.Texturing = false; + } + const DrawMode mode = DrawMode.Triangles; const int maxVertex = 65536; const int maxIndices = maxVertex / 4 * 6; diff --git a/ClassicalSharp/Rendering/MapRenderer.Sorting.cs b/ClassicalSharp/Rendering/MapRenderer.Sorting.cs index eb3b23b98..e05a5e8e7 100644 --- a/ClassicalSharp/Rendering/MapRenderer.Sorting.cs +++ b/ClassicalSharp/Rendering/MapRenderer.Sorting.cs @@ -21,8 +21,7 @@ namespace ClassicalSharp { } // NOTE: Over 5x faster compared to normal comparison of IComparer.Compare if( distances.Length > 1 ) - //FastSorter.QuickSort( distances, chunks, 0, chunks.Length - 1 ); - Array.Sort( distances, chunks, 0, chunks.Length - 1 ); + QuickSort( distances, chunks, 0, chunks.Length - 1 ); Vector3I pPos = newChunkPos; for( int i = 0; i < chunks.Length; i++ ) { @@ -41,19 +40,11 @@ namespace ClassicalSharp { } RecalcBooleans( false ); //SimpleOcclusionCulling(); - } - } - - static class FastSorter { - - static void Swap( int[] keys, T[] values, int a, int b ) { - int key = keys[a]; keys[a] = keys[b]; keys[b] = key; - T value = values[a]; values[a] = values[b]; values[b] = value; } - public static void QuickSort( int[] keys, T[] values, int left, int right ) { + static void QuickSort( int[] keys, ChunkInfo[] values, int left, int right ) { while( left < right ) { - int i = left, j = right; + int i = left, j = right; int pivot = keys[(i + j) / 2]; // partition the list while( i <= j ) { @@ -61,9 +52,10 @@ namespace ClassicalSharp { while( pivot < keys[j] ) j--; if( i <= j ) { - Swap( keys, values, i, j ); + int key = keys[i]; keys[i] = keys[j]; keys[j] = key; + ChunkInfo value = values[i]; values[i] = values[j]; values[j] = value; i++; j--; - } + } } // recurse into the smaller subset diff --git a/ClassicalSharp/Rendering/MapRenderer.cs b/ClassicalSharp/Rendering/MapRenderer.cs index aee107016..b52b4d080 100644 --- a/ClassicalSharp/Rendering/MapRenderer.cs +++ b/ClassicalSharp/Rendering/MapRenderer.cs @@ -55,6 +55,7 @@ namespace ClassicalSharp { game.MapEvents.OnNewMapLoaded += OnNewMapLoaded; game.MapEvents.EnvVariableChanged += EnvVariableChanged; game.Events.BlockDefinitionChanged += BlockDefinitionChanged; + game.Events.ViewDistanceChanged += ViewDistanceChanged; } public void Dispose() { @@ -66,6 +67,7 @@ namespace ClassicalSharp { game.MapEvents.OnNewMapLoaded -= OnNewMapLoaded; game.MapEvents.EnvVariableChanged -= EnvVariableChanged; game.MapEvents.BlockDefinitionChanged -= BlockDefinitionChanged; + game.Events.ViewDistanceChanged -= ViewDistanceChanged; builder.Dispose(); } @@ -111,6 +113,12 @@ namespace ClassicalSharp { builder.OnNewMap(); } + void ViewDistanceChanged( object sender, EventArgs e ) { + lastCamPos = new Vector3( float.MaxValue ); + lastYaw = float.MaxValue; + lastPitch = float.MaxValue; + } + void RecalcBooleans( bool sizeChanged ) { if( sizeChanged ) { usedTranslucent = new bool[_1DUsed]; @@ -246,12 +254,30 @@ namespace ClassicalSharp { int chunksTarget = 4; const double targetTime = (1.0 / 30) + 0.01; void UpdateChunks( double deltaTime ) { - int chunksUpdatedThisFrame = 0; + int chunkUpdates = 0; int viewDist = game.ViewDistance < 16 ? 16 : game.ViewDistance; int adjViewDistSqr = (viewDist + 24) * (viewDist + 24); chunksTarget += deltaTime < targetTime ? 1 : -1; // build more chunks if 30 FPS or over, otherwise slowdown. Utils.Clamp( ref chunksTarget, 4, 12 ); + LocalPlayer p = game.LocalPlayer; + Vector3 cameraPos = game.CurrentCameraPos; + bool samePos = cameraPos == lastCamPos && p.HeadYawDegrees == lastYaw + && p.PitchDegrees == lastPitch; + if( samePos ) + UpdateChunksStill( deltaTime, ref chunkUpdates, adjViewDistSqr ); + else + UpdateChunksAndVisibility( deltaTime, ref chunkUpdates, adjViewDistSqr ); + + lastCamPos = cameraPos; + lastYaw = p.HeadYawDegrees; lastPitch = p.PitchDegrees; + if( !samePos || chunkUpdates != 0 ) + RecalcBooleans( false ); + } + Vector3 lastCamPos; + float lastYaw, lastPitch; + + void UpdateChunksAndVisibility( double deltaTime, ref int chunkUpdats, int adjViewDistSqr ) { for( int i = 0; i < chunks.Length; i++ ) { ChunkInfo info = chunks[i]; if( info.Empty ) continue; @@ -259,84 +285,40 @@ namespace ClassicalSharp { bool inRange = distSqr <= adjViewDistSqr; if( info.NormalParts == null && info.TranslucentParts == null ) { - if( inRange && chunksUpdatedThisFrame < chunksTarget ) { - game.ChunkUpdates++; - builder.GetDrawInfo( info.CentreX - 8, info.CentreY - 8, info.CentreZ - 8, - ref info.NormalParts, ref info.TranslucentParts, ref info.OcclusionFlags ); - - if( info.NormalParts == null && info.TranslucentParts == null ) - info.Empty = true; - chunksUpdatedThisFrame++; - } + if( inRange && chunkUpdats < chunksTarget ) + BuildChunk( info, ref chunkUpdats ); } info.Visible = inRange && game.Culling.SphereInFrustum( info.CentreX, info.CentreY, info.CentreZ, 14 ); // 14 ~ sqrt(3 * 8^2) } - - LocalPlayer p = game.LocalPlayer; - Vector3 cameraPos = game.CurrentCameraPos; - if( chunksUpdatedThisFrame == 0 && cameraPos == lastCamPos - && p.HeadYawDegrees == lastYaw && p.PitchDegrees == lastPitch ) return; - - lastCamPos = cameraPos; - lastYaw = p.HeadYawDegrees; lastPitch = p.PitchDegrees; - RecalcBooleans( false ); - } - Vector3 lastCamPos; - float lastYaw, lastPitch; - - // Render solid and fully transparent to fill depth buffer. - // These blocks are treated as having an alpha value of either none or full. - void RenderNormal() { - int[] texIds = game.TerrainAtlas1D.TexIds; - api.SetBatchFormat( VertexFormat.Pos3fTex2fCol4b ); - api.Texturing = true; - api.AlphaTest = true; - - for( int batch = 0; batch < _1DUsed; batch++ ) { - if( pendingNormal[batch] || usedNormal[batch] ) { - api.BindTexture( texIds[batch] ); - RenderNormalBatch( batch ); - pendingNormal[batch] = false; - } - } - api.AlphaTest = false; - api.Texturing = false; - DebugPickedPos(); } - // Render translucent(liquid) blocks. These 'blend' into other blocks. - void RenderTranslucent() { - Block block = game.LocalPlayer.BlockAtHead; - drawAllFaces = block == Block.Water || block == Block.StillWater; - // First fill depth buffer - int[] texIds = game.TerrainAtlas1D.TexIds; - api.SetBatchFormat( VertexFormat.Pos3fTex2fCol4b ); - api.Texturing = false; - api.AlphaBlending = false; - api.ColourWrite = false; - for( int batch = 0; batch < _1DUsed; batch++ ) { - if( pendingTranslucent[batch] || usedTranslucent[batch] ) { - RenderTranslucentBatchDepthPass( batch ); - pendingTranslucent[batch] = false; - } + void UpdateChunksStill( double deltaTime, ref int chunkUpdates, int adjViewDistSqr ) { + for( int i = 0; i < chunks.Length; i++ ) { + ChunkInfo info = chunks[i]; + if( info.Empty ) continue; + int distSqr = distances[i]; + bool inRange = distSqr <= adjViewDistSqr; + + if( info.NormalParts == null && info.TranslucentParts == null ) { + if( inRange && chunkUpdates < chunksTarget ) { + BuildChunk( info, ref chunkUpdates ); + // only need to update the visibility of chunks in range. + info.Visible = inRange && + game.Culling.SphereInFrustum( info.CentreX, info.CentreY, info.CentreZ, 14 ); // 14 ~ sqrt(3 * 8^2) + } + } } + } + + void BuildChunk( ChunkInfo info, ref int chunkUpdates ) { + game.ChunkUpdates++; + builder.GetDrawInfo( info.CentreX - 8, info.CentreY - 8, info.CentreZ - 8, + ref info.NormalParts, ref info.TranslucentParts, ref info.OcclusionFlags ); - // Then actually draw the transluscent blocks - api.AlphaBlending = true; - api.Texturing = true; - api.ColourWrite = true; - api.DepthWrite = false; // we already calculated depth values in depth pass - - for( int batch = 0; batch < _1DUsed; batch++ ) { - if( !usedTranslucent[batch] ) continue; - api.BindTexture( texIds[batch] ); - RenderTranslucentBatch( batch ); - } - api.DepthWrite = true; - api.AlphaTest = false; - api.AlphaBlending = false; - api.Texturing = false; + if( info.NormalParts == null && info.TranslucentParts == null ) + info.Empty = true; + chunkUpdates++; } } } \ No newline at end of file