mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-15 18:45:23 -04:00
Optimise big maps to be even faster, optimise double/triple jump with speed to be much faster and use much less memory.
This commit is contained in:
parent
599944d943
commit
fd8e765b8b
@ -5,7 +5,7 @@ using OpenTK;
|
||||
namespace ClassicalSharp {
|
||||
|
||||
/// <summary> Entity that performs collision detection. </summary>
|
||||
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<State> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -21,8 +21,7 @@ namespace ClassicalSharp {
|
||||
}
|
||||
// NOTE: Over 5x faster compared to normal comparison of IComparer<ChunkInfo>.Compare
|
||||
if( distances.Length > 1 )
|
||||
//FastSorter<ChunkInfo>.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<T> {
|
||||
|
||||
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
|
||||
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user