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:
UnknownShadow200 2016-01-17 21:56:22 +11:00
parent 599944d943
commit fd8e765b8b
4 changed files with 172 additions and 112 deletions

View File

@ -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();
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;
}
}
}
}
}

View File

@ -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;

View File

@ -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++ ) {
@ -42,16 +41,8 @@ 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 pivot = keys[(i + j) / 2];
@ -61,7 +52,8 @@ 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--;
}
}

View File

@ -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)
}
}
}
}
// 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
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 );
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++;
}
}
}