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