mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-23 04:34:58 -04:00
327 lines
10 KiB
C#
327 lines
10 KiB
C#
//#define DEBUG_OCCLUSION
|
|
using System;
|
|
using ClassicalSharp.GraphicsAPI;
|
|
using OpenTK;
|
|
|
|
namespace ClassicalSharp {
|
|
|
|
/// <summary> Class responsible for converting a 16x16x16 into an optimised mesh of vertices. </summary>
|
|
/// <remarks> This class is heavily optimised and as such may suffer from slightly unreadable code. </remarks>
|
|
public partial class ChunkMeshBuilder {
|
|
|
|
int X, Y, Z;
|
|
float x1, y1, z1, x2, y2, z2;
|
|
byte tile;
|
|
BlockInfo info;
|
|
Map map;
|
|
Game game;
|
|
IGraphicsApi graphics;
|
|
const int chunkSize = 16, extChunkSize = 18;
|
|
const int chunkSize2 = 16 * 16, extChunkSize2 = 18 * 18;
|
|
const int chunkSize3 = 16 * 16 * 16, extChunkSize3 = 18 * 18 * 18;
|
|
|
|
public ChunkMeshBuilder( Game game ) {
|
|
this.game = game;
|
|
graphics = game.Graphics;
|
|
info = game.BlockInfo;
|
|
game.Events.TerrainAtlasChanged += TerrainAtlasChanged;
|
|
}
|
|
|
|
internal int width, length, height, clipLevel;
|
|
int maxX, maxY, maxZ;
|
|
byte[] counts = new byte[chunkSize3 * TileSide.Sides];
|
|
byte[] chunk = new byte[extChunkSize3];
|
|
|
|
bool BuildChunk( int x1, int y1, int z1 ) {
|
|
PreStretchTiles( x1, y1, z1 );
|
|
if( ReadChunkData( x1, y1, z1 ) ) return false;
|
|
|
|
Stretch( x1, y1, z1 );
|
|
PostStretchTiles( x1, y1, z1 );
|
|
|
|
int xMax = Math.Min( width, x1 + chunkSize );
|
|
int yMax = Math.Min( height, y1 + chunkSize );
|
|
int zMax = Math.Min( length, z1 + chunkSize );
|
|
for( int y = y1, yy = 0; y < yMax; y++, yy++ ) {
|
|
for( int z = z1, zz = 0; z < zMax; z++, zz++ ) {
|
|
|
|
int chunkIndex = (yy + 1) * extChunkSize2 + (zz + 1) * extChunkSize + (0 + 1);
|
|
for( int x = x1, xx = 0; x < xMax; x++, xx++ ) {
|
|
tile = chunk[chunkIndex];
|
|
if( !info.IsAir[tile] )
|
|
RenderTile( chunkIndex, xx, yy, zz, x, y, z );
|
|
chunkIndex++;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
unsafe bool ReadChunkData( int x1, int y1, int z1 ) {
|
|
bool allAir = true, allSolid = true;
|
|
fixed( byte* chunkPtr = chunk, mapPtr = map.mapData ) {
|
|
MemUtils.memset( (IntPtr)chunkPtr, 0, 0, extChunkSize3 );
|
|
|
|
for( int yy = -1; yy < 17; yy++ ) {
|
|
int y = yy + y1;
|
|
if( y < 0 ) continue;
|
|
if( y > maxY ) break;
|
|
for( int zz = -1; zz < 17; zz++ ) {
|
|
int z = zz + z1;
|
|
if( z < 0 ) continue;
|
|
if( z > maxZ ) break;
|
|
|
|
int index = (y * length + z) * width + (x1 - 1 - 1);
|
|
int chunkIndex = (yy + 1) * extChunkSize2 + (zz + 1) * extChunkSize + (-1 + 1) - 1;
|
|
|
|
for( int xx = -1; xx < 17; xx++ ) {
|
|
int x = xx + x1;
|
|
index++;
|
|
chunkIndex++;
|
|
if( x < 0 ) continue;
|
|
if( x > maxX ) break;
|
|
|
|
byte block = mapPtr[index];
|
|
if( block == 9 ) block = 8; // Still water --> Water
|
|
if( block == 11 ) block = 10; // Still lava --> Lava
|
|
|
|
if( allAir && block != 0 ) allAir = false;
|
|
if( allSolid && !info.IsOpaque[block] ) allSolid = false;
|
|
chunkPtr[chunkIndex] = block;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( x1 == 0 || y1 == 0 || z1 == 0 || x1 + chunkSize >= width ||
|
|
y1 + chunkSize >= height || z1 + chunkSize >= length ) allSolid = false;
|
|
if( !( allAir || allSolid ) ) {
|
|
map.HeightmapHint( x1 - 1, z1 - 1, mapPtr );
|
|
}
|
|
}
|
|
return allAir || allSolid;
|
|
}
|
|
|
|
public void GetDrawInfo( int x, int y, int z, ref ChunkPartInfo[] normalParts,
|
|
ref ChunkPartInfo[] translucentParts, ref byte occlusionFlags ) {
|
|
if( !BuildChunk( x, y, z ) )
|
|
return;
|
|
|
|
for( int i = 0; i < arraysCount; i++ ) {
|
|
SetPartInfo( drawInfoNormal[i], i, ref normalParts );
|
|
SetPartInfo( drawInfoTranslucent[i], i, ref translucentParts );
|
|
}
|
|
if( normalParts != null || translucentParts != null )
|
|
occlusionFlags = 0;//(byte)ComputeOcclusion();
|
|
}
|
|
|
|
Vector3 minBB, maxBB;
|
|
public void RenderTile( int chunkIndex, int xx, int yy, int zz, int x, int y, int z ) {
|
|
X = x; Y = y; Z = z;
|
|
int index = ((yy << 8) | (zz << 4) | xx) * TileSide.Sides;
|
|
|
|
if( info.IsSprite[tile] ) {
|
|
fullBright = info.FullBright[tile];
|
|
int count = counts[index + TileSide.Top];
|
|
if( count != 0 )
|
|
DrawSprite( count );
|
|
return;
|
|
}
|
|
|
|
int leftCount = counts[index++], rightCount = counts[index++],
|
|
frontCount = counts[index++], backCount = counts[index++],
|
|
bottomCount = counts[index++], topCount = counts[index++];
|
|
if( leftCount == 0 && rightCount == 0 && frontCount == 0 &&
|
|
backCount == 0 && bottomCount == 0 && topCount == 0 ) return;
|
|
|
|
Vector3 min = info.MinBB[tile], max = info.MaxBB[tile];
|
|
x1 = x + min.X; y1 = y + min.Y; z1 = z + min.Z;
|
|
x2 = x + max.X; y2 = y + max.Y; z2 = z + max.Z;
|
|
this.minBB = min; this.maxBB = max;
|
|
minBB.Y = 1 - minBB.Y; maxBB.Y = 1 - maxBB.Y;
|
|
|
|
fullBright = info.FullBright[tile];
|
|
isTranslucent = info.IsTranslucent[tile];
|
|
lightFlags = info.LightOffset[tile];
|
|
|
|
if( leftCount != 0 )
|
|
DrawLeftFace( leftCount );
|
|
if( rightCount != 0 )
|
|
DrawRightFace( rightCount );
|
|
if( frontCount != 0 )
|
|
DrawFrontFace( frontCount );
|
|
if( backCount != 0 )
|
|
DrawBackFace( backCount );
|
|
if( bottomCount != 0 )
|
|
DrawBottomFace( bottomCount );
|
|
if( topCount != 0 )
|
|
DrawTopFace( topCount );
|
|
}
|
|
|
|
unsafe void Stretch( int x1, int y1, int z1 ) {
|
|
fixed( byte* ptr = counts )
|
|
MemUtils.memset( (IntPtr)ptr, 1, 0, chunkSize3 * TileSide.Sides );
|
|
|
|
int xMax = Math.Min( width, x1 + chunkSize );
|
|
int yMax = Math.Min( height, y1 + chunkSize );
|
|
int zMax = Math.Min( length, z1 + chunkSize );
|
|
#if DEBUG_OCCLUSION
|
|
int flags = ComputeOcclusion();
|
|
FastColour col = new FastColour( 60, 60, 60, 255 );
|
|
if( (flags & 1) != 0 ) col.R = 255; // x
|
|
if( (flags & 4) != 0 ) col.G = 255; // y
|
|
if( (flags & 2) != 0 ) col.B = 255; // z
|
|
map.Sunlight = map.Shadowlight = col;
|
|
map.SunlightXSide = map.ShadowlightXSide = col;
|
|
map.SunlightZSide = map.ShadowlightZSide = col;
|
|
map.SunlightYBottom = map.ShadowlightYBottom = col;
|
|
#endif
|
|
|
|
for( int y = y1, yy = 0; y < yMax; y++, yy++ ) {
|
|
for( int z = z1, zz = 0; z < zMax; z++, zz++ ) {
|
|
|
|
int chunkIndex = (yy + 1) * extChunkSize2 + (zz + 1) * extChunkSize + (-1 + 1);
|
|
for( int x = x1, xx = 0; x < xMax; x++, xx++ ) {
|
|
chunkIndex++;
|
|
byte tile = chunk[chunkIndex];
|
|
if( info.IsAir[tile] ) continue;
|
|
int countIndex = ((yy << 8) + (zz << 4) + xx) * TileSide.Sides;
|
|
|
|
// Sprites only use one face to indicate stretching count, so we can take a shortcut here.
|
|
// Note that sprites are not drawn with any of the DrawXFace, they are drawn using DrawSprite.
|
|
if( info.IsSprite[tile] ) {
|
|
countIndex += TileSide.Top;
|
|
if( counts[countIndex] != 0 ) {
|
|
X = x; Y = y; Z = z;
|
|
AddSpriteVertices( tile, 1 );
|
|
counts[countIndex] = 1;
|
|
}
|
|
} else {
|
|
X = x; Y = y; Z = z;
|
|
fullBright = info.FullBright[tile];
|
|
TestAndStretchZ( zz, countIndex, tile, chunkIndex, x, 0, TileSide.Left, -1 );
|
|
TestAndStretchZ( zz, countIndex, tile, chunkIndex, x, maxX, TileSide.Right, 1 );
|
|
TestAndStretchX( xx, countIndex, tile, chunkIndex, z, 0, TileSide.Front, -extChunkSize );
|
|
TestAndStretchX( xx, countIndex, tile, chunkIndex, z, maxZ, TileSide.Back, extChunkSize );
|
|
|
|
if( y > 0 )
|
|
TestAndStretchX( xx, countIndex, tile, chunkIndex, y, 0, TileSide.Bottom, -extChunkSize2 );
|
|
else
|
|
counts[countIndex + TileSide.Bottom] = 0;
|
|
TestAndStretchX( xx, countIndex, tile, chunkIndex, y, maxY + 2, TileSide.Top, extChunkSize2 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestAndStretchX( int xx, int index, byte tile, int chunkIndex, int value, int test, int tileSide, int offset ) {
|
|
index += tileSide;
|
|
if( counts[index] != 0 ) {
|
|
if( (value == test && Y < clipLevel) ||
|
|
(value != test && info.IsFaceHidden( tile, chunk[chunkIndex + offset], tileSide )) ) {
|
|
counts[index] = 0;
|
|
} else {
|
|
int count = StretchX( xx, index, X, Y, Z, chunkIndex, tile, tileSide );
|
|
AddVertices( tile, count, tileSide );
|
|
counts[index] = (byte)count;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestAndStretchZ( int zz, int index, byte tile, int chunkIndex, int value, int test, int tileSide, int offset ) {
|
|
index += tileSide;
|
|
if( counts[index] != 0 ) {
|
|
if( (value == test && Y < clipLevel) ||
|
|
(value != test && info.IsFaceHidden( tile, chunk[chunkIndex + offset], tileSide )) ) {
|
|
counts[index] = 0;
|
|
} else {
|
|
int count = StretchZ( zz, index, X, Y, Z, chunkIndex, tile, tileSide );
|
|
AddVertices( tile, count, tileSide );
|
|
counts[index] = (byte)count;
|
|
}
|
|
}
|
|
}
|
|
|
|
byte GetNeighbour( int chunkIndex, int face ) {
|
|
switch( face ) {
|
|
case TileSide.Left:
|
|
return chunk[chunkIndex - 1]; // x - 1
|
|
|
|
case TileSide.Right:
|
|
return chunk[chunkIndex + 1]; // x + 1
|
|
|
|
case TileSide.Front:
|
|
return chunk[chunkIndex - 18]; // z - 1
|
|
|
|
case TileSide.Back:
|
|
return chunk[chunkIndex + 18]; // z + 1
|
|
|
|
case TileSide.Bottom:
|
|
return chunk[chunkIndex - 324]; // y - 1
|
|
|
|
case TileSide.Top:
|
|
return chunk[chunkIndex + 324]; // y + 1
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int StretchX( int xx, int countIndex, int x, int y, int z, int chunkIndex, byte tile, int face ) {
|
|
int count = 1;
|
|
x++;
|
|
chunkIndex++;
|
|
countIndex += TileSide.Sides;
|
|
int max = chunkSize - xx;
|
|
bool stretchTile = info.CanStretch[tile * TileSide.Sides + face];
|
|
|
|
while( count < max && x < width && stretchTile && CanStretch( tile, chunkIndex, x, y, z, face ) ) {
|
|
counts[countIndex] = 0;
|
|
count++;
|
|
x++;
|
|
chunkIndex++;
|
|
countIndex += TileSide.Sides;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int StretchZ( int zz, int countIndex, int x, int y, int z, int chunkIndex, byte tile, int face ) {
|
|
int count = 1;
|
|
z++;
|
|
chunkIndex += extChunkSize;
|
|
countIndex += chunkSize * TileSide.Sides;
|
|
int max = chunkSize - zz;
|
|
bool stretchTile = info.CanStretch[tile * TileSide.Sides + face];
|
|
|
|
while( count < max && z < length && stretchTile && CanStretch( tile, chunkIndex, x, y, z, face ) ) {
|
|
counts[countIndex] = 0;
|
|
count++;
|
|
z++;
|
|
chunkIndex += extChunkSize;
|
|
countIndex += chunkSize * TileSide.Sides;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
public void OnNewMap() {
|
|
}
|
|
|
|
public void OnNewMapLoaded() {
|
|
map = game.Map;
|
|
width = map.Width;
|
|
height = map.Height;
|
|
length = map.Length;
|
|
clipLevel = Math.Max( 0, game.Map.SidesHeight );
|
|
maxX = width - 1;
|
|
maxY = height - 1;
|
|
maxZ = length - 1;
|
|
}
|
|
}
|
|
|
|
public struct ChunkPartInfo {
|
|
|
|
public int VbId, IndicesCount;
|
|
public int leftIndex, rightIndex, frontIndex,
|
|
backIndex, bottomIndex, topIndex;
|
|
public ushort leftCount, rightCount, frontCount,
|
|
backCount, bottomCount, topCount, spriteCount;
|
|
}
|
|
} |