2026-03-01 12:16:08 +08:00
# include "stdafx.h"
# include "Chunk.h"
# include "TileRenderer.h"
# include "TileEntityRenderDispatcher.h"
# include "..\Minecraft.World\net.minecraft.world.level.h"
# include "..\Minecraft.World\net.minecraft.world.level.chunk.h"
# include "..\Minecraft.World\net.minecraft.world.level.tile.h"
# include "..\Minecraft.World\net.minecraft.world.level.tile.entity.h"
# include "LevelRenderer.h"
# ifdef __PS3__
# include "PS3\SPU_Tasks\ChunkUpdate\ChunkRebuildData.h"
# include "PS3\SPU_Tasks\ChunkUpdate\TileRenderer_SPU.h"
# include "PS3\SPU_Tasks\CompressedTile\CompressedTileStorage_SPU.h"
# include "C4JThread_SPU.h"
# include "C4JSpursJob.h"
# endif
int Chunk : : updates = 0 ;
# ifdef _LARGE_WORLDS
DWORD Chunk : : tlsIdx = TlsAlloc ( ) ;
void Chunk : : CreateNewThreadStorage ( )
{
unsigned char * tileIds = new unsigned char [ 16 * 16 * Level : : maxBuildHeight ] ;
TlsSetValue ( tlsIdx , tileIds ) ;
}
void Chunk : : ReleaseThreadStorage ( )
{
unsigned char * tileIds = ( unsigned char * ) TlsGetValue ( tlsIdx ) ;
delete tileIds ;
}
unsigned char * Chunk : : GetTileIdsStorage ( )
{
unsigned char * tileIds = ( unsigned char * ) TlsGetValue ( tlsIdx ) ;
return tileIds ;
}
# else
// 4J Stu - Don't want this when multi-threaded
Tesselator * Chunk : : t = Tesselator : : getInstance ( ) ;
# endif
LevelRenderer * Chunk : : levelRenderer ;
// TODO - 4J see how input entity vector is set up and decide what way is best to pass this to the function
Chunk : : Chunk ( Level * level , LevelRenderer : : rteMap & globalRenderableTileEntities , CRITICAL_SECTION & globalRenderableTileEntities_cs , int x , int y , int z , ClipChunk * clipChunk )
: globalRenderableTileEntities ( & globalRenderableTileEntities ) , globalRenderableTileEntities_cs ( & globalRenderableTileEntities_cs )
{
clipChunk - > visible = false ;
bb = NULL ;
id = 0 ;
this - > level = level ;
//this->globalRenderableTileEntities = globalRenderableTileEntities;
assigned = false ;
this - > clipChunk = clipChunk ;
setPos ( x , y , z ) ;
}
void Chunk : : setPos ( int x , int y , int z )
{
if ( assigned & & ( x = = this - > x & & y = = this - > y & & z = = this - > z ) ) return ;
reset ( ) ;
this - > x = x ;
this - > y = y ;
this - > z = z ;
xm = x + XZSIZE / 2 ;
ym = y + SIZE / 2 ;
zm = z + XZSIZE / 2 ;
clipChunk - > xm = xm ;
clipChunk - > ym = ym ;
clipChunk - > zm = zm ;
clipChunk - > globalIdx = LevelRenderer : : getGlobalIndexForChunk ( x , y , z , level ) ;
# if 1
// 4J - we're not using offsetted renderlists anymore, so just set the full position of this chunk into x/y/zRenderOffs where
// it will be used directly in the renderlist of this chunk
xRenderOffs = x ;
yRenderOffs = y ;
zRenderOffs = z ;
xRender = 0 ;
yRender = 0 ;
zRender = 0 ;
# else
xRenderOffs = x & 1023 ;
yRenderOffs = y ;
zRenderOffs = z & 1023 ;
xRender = x - xRenderOffs ;
yRender = y - yRenderOffs ;
zRender = z - zRenderOffs ;
# endif
float g = 6.0f ;
// 4J - changed to just set the value rather than make a new one, if we've already created storage
if ( bb = = NULL )
{
bb = AABB : : newPermanent ( - g , - g , - g , XZSIZE + g , SIZE + g , XZSIZE + g ) ;
}
2026-03-02 15:58:20 +07:00
else
2026-03-01 12:16:08 +08:00
{
// 4J MGH - bounds are relative to the position now, so the AABB will be setup already, either above, or from the tesselator bounds.
// bb->set(-g, -g, -g, SIZE+g, SIZE+g, SIZE+g);
}
clipChunk - > aabb [ 0 ] = bb - > x0 + x ;
clipChunk - > aabb [ 1 ] = bb - > y0 + y ;
clipChunk - > aabb [ 2 ] = bb - > z0 + z ;
clipChunk - > aabb [ 3 ] = bb - > x1 + x ;
clipChunk - > aabb [ 4 ] = bb - > y1 + y ;
clipChunk - > aabb [ 5 ] = bb - > z1 + z ;
assigned = true ;
EnterCriticalSection ( & levelRenderer - > m_csDirtyChunks ) ;
unsigned char refCount = levelRenderer - > incGlobalChunkRefCount ( x , y , z , level ) ;
// printf("\t\t [inc] refcount %d at %d, %d, %d\n",refCount,x,y,z);
// int idx = levelRenderer->getGlobalIndexForChunk(x, y, z, level);
// If we're the first thing to be referencing this, mark it up as dirty to get rebuilt
if ( refCount = = 1 )
{
// printf("Setting %d %d %d dirty [%d]\n",x,y,z, idx);
// Chunks being made dirty in this way can be very numerous (eg the full visible area of the world at start up, or a whole edge of the world when moving).
// On account of this, don't want to stick them into our lock free queue that we would normally use for letting the render update thread know about this chunk.
// Instead, just set the flag to say this is dirty, and then pass a special value of 1 through to the lock free stack which lets that thread know that at least
// one chunk other than the ones in the stack itself have been made dirty.
levelRenderer - > setGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_DIRTY ) ;
# ifdef _XBOX
PIXSetMarker ( 0 , " Non-stack event pushed " ) ;
# else
PIXSetMarkerDeprecated ( 0 , " Non-stack event pushed " ) ;
# endif
}
LeaveCriticalSection ( & levelRenderer - > m_csDirtyChunks ) ;
2026-03-02 15:58:20 +07:00
2026-03-01 12:16:08 +08:00
}
void Chunk : : translateToPos ( )
{
glTranslatef ( ( float ) xRenderOffs , ( float ) yRenderOffs , ( float ) zRenderOffs ) ;
}
Chunk : : Chunk ( )
{
}
void Chunk : : makeCopyForRebuild ( Chunk * source )
{
this - > level = source - > level ;
this - > x = source - > x ;
this - > y = source - > y ;
this - > z = source - > z ;
this - > xRender = source - > xRender ;
this - > yRender = source - > yRender ;
this - > zRender = source - > zRender ;
this - > xRenderOffs = source - > xRenderOffs ;
this - > yRenderOffs = source - > yRenderOffs ;
this - > zRenderOffs = source - > zRenderOffs ;
this - > xm = source - > xm ;
this - > ym = source - > ym ;
this - > zm = source - > zm ;
this - > bb = source - > bb ;
this - > clipChunk = NULL ;
this - > id = source - > id ;
this - > globalRenderableTileEntities = source - > globalRenderableTileEntities ;
2026-03-02 15:58:20 +07:00
this - > globalRenderableTileEntities_cs = source - > globalRenderableTileEntities_cs ;
2026-03-01 12:16:08 +08:00
}
void Chunk : : rebuild ( )
{
PIXBeginNamedEvent ( 0 , " Rebuilding chunk %d, %d, %d " , x , y , z ) ;
# if defined __PS3__ && !defined DISABLE_SPU_CODE
rebuild_SPU ( ) ;
return ;
# endif // __PS3__
// if (!dirty) return;
PIXBeginNamedEvent ( 0 , " Rebuild section A " ) ;
2026-03-02 15:58:20 +07:00
2026-03-01 12:16:08 +08:00
# ifdef _LARGE_WORLDS
Tesselator * t = Tesselator : : getInstance ( ) ;
# else
Chunk : : t = Tesselator : : getInstance ( ) ; // 4J - added - static initialiser being set at the wrong time
# endif
updates + + ;
int x0 = x ;
int y0 = y ;
int z0 = z ;
int x1 = x + XZSIZE ;
int y1 = y + SIZE ;
int z1 = z + XZSIZE ;
LevelChunk : : touchedSky = false ;
2026-03-02 15:58:20 +07:00
// unordered_set<std::shared_ptr<TileEntity> > oldTileEntities(renderableTileEntities.begin(),renderableTileEntities.end()); // 4J removed this & next line
2026-03-01 12:16:08 +08:00
// renderableTileEntities.clear();
2026-03-02 15:58:20 +07:00
vector < std : : shared_ptr < TileEntity > > renderableTileEntities ; // 4J - added
2026-03-01 12:16:08 +08:00
int r = 1 ;
int lists = levelRenderer - > getGlobalIndexForChunk ( this - > x , this - > y , this - > z , level ) * 2 ;
lists + = levelRenderer - > chunkLists ;
PIXEndNamedEvent ( ) ;
PIXBeginNamedEvent ( 0 , " Rebuild section B " ) ;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 4J - optimisation begins.
// Get the data for the level chunk that this render chunk is it (level chunk is 16 x 16 x 128,
// render chunk is 16 x 16 x 16. We wouldn't have to actually get all of it if the data was ordered differently, but currently
// it is ordered by x then z then y so just getting a small range of y out of it would involve getting the whole thing into
2026-03-02 15:58:20 +07:00
// the cache anyway.
2026-03-01 12:16:08 +08:00
# ifdef _LARGE_WORLDS
unsigned char * tileIds = GetTileIdsStorage ( ) ;
# else
static unsigned char tileIds [ 16 * 16 * Level : : maxBuildHeight ] ;
# endif
byteArray tileArray = byteArray ( tileIds , 16 * 16 * Level : : maxBuildHeight ) ;
level - > getChunkAt ( x , z ) - > getBlockData ( tileArray ) ; // 4J - TODO - now our data has been re-arranged, we could just extra the vertical slice of this chunk rather than the whole thing
LevelSource * region = new Region ( level , x0 - r , y0 - r , z0 - r , x1 + r , y1 + r , z1 + r ) ;
TileRenderer * tileRenderer = new TileRenderer ( region , this - > x , this - > y , this - > z , tileIds ) ;
// AP - added a caching system for Chunk::rebuild to take advantage of
2026-03-02 15:58:20 +07:00
// Basically we're storing of copy of the tileIDs array inside the region so that calls to Region::getTile can grab data
// more quickly from this array rather than calling CompressedTileStorage. On the Vita the total thread time spent in
// Region::getTile went from 20% to 4%.
2026-03-01 12:16:08 +08:00
# ifdef __PSVITA__
int xc = x > > 4 ;
int zc = z > > 4 ;
( ( Region * ) region ) - > setCachedTiles ( tileIds , xc , zc ) ;
# endif
// We now go through the vertical section of this level chunk that we are interested in and try and establish
// (1) if it is completely empty
// (2) if any of the tiles can be quickly determined to not need rendering because they are in the middle of other tiles and
// so can't be seen. A large amount (> 60% in tests) of tiles that call tesselateInWorld in the unoptimised version
// of this function fall into this category. By far the largest category of these are tiles in solid regions of rock.
bool empty = true ;
for ( int yy = y0 ; yy < y1 ; yy + + )
{
for ( int zz = 0 ; zz < 16 ; zz + + )
{
for ( int xx = 0 ; xx < 16 ; xx + + )
{
// 4J Stu - tile data is ordered in 128 blocks of full width, lower 128 then upper 128
int indexY = yy ;
int offset = 0 ;
if ( indexY > = Level : : COMPRESSED_CHUNK_SECTION_HEIGHT )
{
indexY - = Level : : COMPRESSED_CHUNK_SECTION_HEIGHT ;
offset = Level : : COMPRESSED_CHUNK_SECTION_TILES ;
}
unsigned char tileId = tileIds [ offset + ( ( ( xx + 0 ) < < 11 ) | ( ( zz + 0 ) < < 7 ) | ( indexY + 0 ) ) ] ;
if ( tileId > 0 ) empty = false ;
// Don't bother trying to work out neighbours for this tile if we are at the edge of the chunk - apart from the very
// bottom of the world where we shouldn't ever be able to see
if ( yy = = ( Level : : maxBuildHeight - 1 ) ) continue ;
if ( ( xx = = 0 ) | | ( xx = = 15 ) ) continue ;
if ( ( zz = = 0 ) | | ( zz = = 15 ) ) continue ;
2026-03-02 15:58:20 +07:00
2026-03-01 12:16:08 +08:00
// Establish whether this tile and its neighbours are all made of rock, dirt, unbreakable tiles, or have already
// been determined to meet this criteria themselves and have a tile of 255 set.
if ( ! ( ( tileId = = Tile : : rock_Id ) | | ( tileId = = Tile : : dirt_Id ) | | ( tileId = = Tile : : unbreakable_Id ) | | ( tileId = = 255 ) ) ) continue ;
tileId = tileIds [ offset + ( ( ( xx - 1 ) < < 11 ) | ( ( zz + 0 ) < < 7 ) | ( indexY + 0 ) ) ] ;
if ( ! ( ( tileId = = Tile : : rock_Id ) | | ( tileId = = Tile : : dirt_Id ) | | ( tileId = = Tile : : unbreakable_Id ) | | ( tileId = = 255 ) ) ) continue ;
tileId = tileIds [ offset + ( ( ( xx + 1 ) < < 11 ) | ( ( zz + 0 ) < < 7 ) | ( indexY + 0 ) ) ] ;
if ( ! ( ( tileId = = Tile : : rock_Id ) | | ( tileId = = Tile : : dirt_Id ) | | ( tileId = = Tile : : unbreakable_Id ) | | ( tileId = = 255 ) ) ) continue ;
tileId = tileIds [ offset + ( ( ( xx + 0 ) < < 11 ) | ( ( zz - 1 ) < < 7 ) | ( indexY + 0 ) ) ] ;
if ( ! ( ( tileId = = Tile : : rock_Id ) | | ( tileId = = Tile : : dirt_Id ) | | ( tileId = = Tile : : unbreakable_Id ) | | ( tileId = = 255 ) ) ) continue ;
tileId = tileIds [ offset + ( ( ( xx + 0 ) < < 11 ) | ( ( zz + 1 ) < < 7 ) | ( indexY + 0 ) ) ] ;
if ( ! ( ( tileId = = Tile : : rock_Id ) | | ( tileId = = Tile : : dirt_Id ) | | ( tileId = = Tile : : unbreakable_Id ) | | ( tileId = = 255 ) ) ) continue ;
// Treat the bottom of the world differently - we shouldn't ever be able to look up at this, so consider tiles as invisible
// if they are surrounded on sides other than the bottom
if ( yy > 0 )
{
int indexYMinusOne = yy - 1 ;
int yMinusOneOffset = 0 ;
if ( indexYMinusOne > = Level : : COMPRESSED_CHUNK_SECTION_HEIGHT )
{
indexYMinusOne - = Level : : COMPRESSED_CHUNK_SECTION_HEIGHT ;
yMinusOneOffset = Level : : COMPRESSED_CHUNK_SECTION_TILES ;
}
tileId = tileIds [ yMinusOneOffset + ( ( ( xx + 0 ) < < 11 ) | ( ( zz + 0 ) < < 7 ) | indexYMinusOne ) ] ;
if ( ! ( ( tileId = = Tile : : rock_Id ) | | ( tileId = = Tile : : dirt_Id ) | | ( tileId = = Tile : : unbreakable_Id ) | | ( tileId = = 255 ) ) ) continue ;
}
int indexYPlusOne = yy + 1 ;
int yPlusOneOffset = 0 ;
if ( indexYPlusOne > = Level : : COMPRESSED_CHUNK_SECTION_HEIGHT )
{
indexYPlusOne - = Level : : COMPRESSED_CHUNK_SECTION_HEIGHT ;
yPlusOneOffset = Level : : COMPRESSED_CHUNK_SECTION_TILES ;
}
tileId = tileIds [ yPlusOneOffset + ( ( ( xx + 0 ) < < 11 ) | ( ( zz + 0 ) < < 7 ) | indexYPlusOne ) ] ;
if ( ! ( ( tileId = = Tile : : rock_Id ) | | ( tileId = = Tile : : dirt_Id ) | | ( tileId = = Tile : : unbreakable_Id ) | | ( tileId = = 255 ) ) ) continue ;
// This tile is surrounded. Flag it as not requiring to be rendered by setting its id to 255.
tileIds [ offset + ( ( ( xx + 0 ) < < 11 ) | ( ( zz + 0 ) < < 7 ) | ( indexY + 0 ) ) ] = 0xff ;
}
}
}
PIXEndNamedEvent ( ) ;
// Nothing at all to do for this chunk?
if ( empty )
{
// 4J - added - clear any renderer data associated with this
for ( int currentLayer = 0 ; currentLayer < 2 ; currentLayer + + )
{
levelRenderer - > setGlobalChunkFlag ( this - > x , this - > y , this - > z , level , LevelRenderer : : CHUNK_FLAG_EMPTY0 , currentLayer ) ;
RenderManager . CBuffClear ( lists + currentLayer ) ;
}
delete region ;
delete tileRenderer ;
return ;
}
// 4J - optimisation ends
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
PIXBeginNamedEvent ( 0 , " Rebuild section C " ) ;
Tesselator : : Bounds bounds ; // 4J MGH - added
{
2026-03-02 15:58:20 +07:00
// this was the old default clip bounds for the chunk, set in Chunk::setPos.
2026-03-01 12:16:08 +08:00
float g = 6.0f ;
bounds . boundingBox [ 0 ] = - g ;
bounds . boundingBox [ 1 ] = - g ;
bounds . boundingBox [ 2 ] = - g ;
bounds . boundingBox [ 3 ] = XZSIZE + g ;
bounds . boundingBox [ 4 ] = SIZE + g ;
bounds . boundingBox [ 5 ] = XZSIZE + g ;
}
for ( int currentLayer = 0 ; currentLayer < 2 ; currentLayer + + )
{
bool renderNextLayer = false ;
bool rendered = false ;
bool started = false ;
// 4J - changed loop order here to leave y as the innermost loop for better cache performance
for ( int z = z0 ; z < z1 ; z + + )
{
for ( int x = x0 ; x < x1 ; x + + )
{
for ( int y = y0 ; y < y1 ; y + + )
{
// 4J Stu - tile data is ordered in 128 blocks of full width, lower 128 then upper 128
int indexY = y ;
int offset = 0 ;
if ( indexY > = Level : : COMPRESSED_CHUNK_SECTION_HEIGHT )
{
indexY - = Level : : COMPRESSED_CHUNK_SECTION_HEIGHT ;
offset = Level : : COMPRESSED_CHUNK_SECTION_TILES ;
}
// 4J - get tile from those copied into our local array in earlier optimisation
unsigned char tileId = tileIds [ offset + ( ( ( x - x0 ) < < 11 ) | ( ( z - z0 ) < < 7 ) | indexY ) ] ;
// If flagged as not visible, drop out straight away
if ( tileId = = 0xff ) continue ;
// int tileId = region->getTile(x,y,z);
if ( tileId > 0 )
{
if ( ! started )
{
started = true ;
MemSect ( 31 ) ;
glNewList ( lists + currentLayer , GL_COMPILE ) ;
MemSect ( 0 ) ;
glPushMatrix ( ) ;
glDepthMask ( true ) ; // 4J added
t - > useCompactVertices ( true ) ; // 4J added
translateToPos ( ) ;
float ss = 1.000001f ;
// 4J - have removed this scale as I don't think we should need it, and have now optimised the vertex
// shader so it doesn't do anything other than translate with this matrix anyway
#if 0
glTranslatef ( - zs / 2.0f , - ys / 2.0f , - zs / 2.0f ) ;
glScalef ( ss , ss , ss ) ;
glTranslatef ( zs / 2.0f , ys / 2.0f , zs / 2.0f ) ;
# endif
t - > begin ( ) ;
t - > offset ( ( float ) ( - this - > x ) , ( float ) ( - this - > y ) , ( float ) ( - this - > z ) ) ;
}
2026-03-02 15:58:20 +07:00
2026-03-01 12:16:08 +08:00
Tile * tile = Tile : : tiles [ tileId ] ;
if ( currentLayer = = 0 & & tile - > isEntityTile ( ) )
{
2026-03-02 15:58:20 +07:00
std : : shared_ptr < TileEntity > et = region - > getTileEntity ( x , y , z ) ;
2026-03-01 12:16:08 +08:00
if ( TileEntityRenderDispatcher : : instance - > hasRenderer ( et ) )
{
renderableTileEntities . push_back ( et ) ;
}
}
int renderLayer = tile - > getRenderLayer ( ) ;
if ( renderLayer ! = currentLayer )
{
renderNextLayer = true ;
}
else if ( renderLayer = = currentLayer )
{
rendered | = tileRenderer - > tesselateInWorld ( tile , x , y , z ) ;
}
}
}
}
}
# ifdef __PSVITA__
if ( currentLayer = = 0 )
{
levelRenderer - > clearGlobalChunkFlag ( this - > x , this - > y , this - > z , level , LevelRenderer : : CHUNK_FLAG_CUT_OUT ) ;
}
# endif
if ( started )
{
# ifdef __PSVITA__
// AP - make sure we don't attempt to render chunks without cutout geometry
if ( t - > getCutOutFound ( ) )
{
levelRenderer - > setGlobalChunkFlag ( this - > x , this - > y , this - > z , level , LevelRenderer : : CHUNK_FLAG_CUT_OUT ) ;
}
# endif
t - > end ( ) ;
bounds . addBounds ( t - > bounds ) ; // 4J MGH - added
glPopMatrix ( ) ;
glEndList ( ) ;
t - > useCompactVertices ( false ) ; // 4J added
t - > offset ( 0 , 0 , 0 ) ;
}
else
{
rendered = false ;
}
if ( rendered )
{
levelRenderer - > clearGlobalChunkFlag ( this - > x , this - > y , this - > z , level , LevelRenderer : : CHUNK_FLAG_EMPTY0 , currentLayer ) ;
}
else
{
// 4J - added - clear any renderer data associated with this unused list
levelRenderer - > setGlobalChunkFlag ( this - > x , this - > y , this - > z , level , LevelRenderer : : CHUNK_FLAG_EMPTY0 , currentLayer ) ;
RenderManager . CBuffClear ( lists + currentLayer ) ;
}
if ( ( currentLayer = = 0 ) & & ( ! renderNextLayer ) )
{
levelRenderer - > setGlobalChunkFlag ( this - > x , this - > y , this - > z , level , LevelRenderer : : CHUNK_FLAG_EMPTY1 ) ;
RenderManager . CBuffClear ( lists + 1 ) ;
2026-03-02 15:58:20 +07:00
break ;
2026-03-01 12:16:08 +08:00
}
}
// 4J MGH - added this to take the bound from the value calc'd in the tesselator
if ( bb )
{
bb - > set ( bounds . boundingBox [ 0 ] , bounds . boundingBox [ 1 ] , bounds . boundingBox [ 2 ] ,
bounds . boundingBox [ 3 ] , bounds . boundingBox [ 4 ] , bounds . boundingBox [ 5 ] ) ;
}
delete tileRenderer ;
delete region ;
PIXEndNamedEvent ( ) ;
PIXBeginNamedEvent ( 0 , " Rebuild section D " ) ;
// 4J - have rewritten the way that tile entities are stored globally to make it work more easily with split screen. Chunks are now
// stored globally in the levelrenderer, in a hashmap with a special key made up from the dimension and chunk position (using same index
// as is used for global flags)
# if 1
int key = levelRenderer - > getGlobalIndexForChunk ( this - > x , this - > y , this - > z , level ) ;
EnterCriticalSection ( globalRenderableTileEntities_cs ) ;
if ( renderableTileEntities . size ( ) )
{
AUTO_VAR ( it , globalRenderableTileEntities - > find ( key ) ) ;
if ( it ! = globalRenderableTileEntities - > end ( ) )
{
// We've got some renderable tile entities that we want associated with this chunk, and an existing list of things that used to be.
// We need to flag any that we don't need any more to be removed, keep those that we do, and add any new ones
// First pass - flag everything already existing to be removed
for ( AUTO_VAR ( it2 , it - > second . begin ( ) ) ; it2 ! = it - > second . end ( ) ; it2 + + )
{
( * it2 ) - > setRenderRemoveStage ( TileEntity : : e_RenderRemoveStageFlaggedAtChunk ) ;
}
// Now go through the current list. If these are already in the list, then unflag the remove flag. If they aren't, then add
for ( int i = 0 ; i < renderableTileEntities . size ( ) ; i + + )
{
AUTO_VAR ( it2 , find ( it - > second . begin ( ) , it - > second . end ( ) , renderableTileEntities [ i ] ) ) ;
if ( it2 = = it - > second . end ( ) )
{
( * globalRenderableTileEntities ) [ key ] . push_back ( renderableTileEntities [ i ] ) ;
}
else
{
( * it2 ) - > setRenderRemoveStage ( TileEntity : : e_RenderRemoveStageKeep ) ;
}
}
}
else
{
// Easy case - nothing already existing for this chunk. Add them all in.
for ( int i = 0 ; i < renderableTileEntities . size ( ) ; i + + )
{
( * globalRenderableTileEntities ) [ key ] . push_back ( renderableTileEntities [ i ] ) ;
}
}
}
else
{
// Another easy case - we don't want any renderable tile entities associated with this chunk. Flag all to be removed.
AUTO_VAR ( it , globalRenderableTileEntities - > find ( key ) ) ;
if ( it ! = globalRenderableTileEntities - > end ( ) )
{
for ( AUTO_VAR ( it2 , it - > second . begin ( ) ) ; it2 ! = it - > second . end ( ) ; it2 + + )
{
( * it2 ) - > setRenderRemoveStage ( TileEntity : : e_RenderRemoveStageFlaggedAtChunk ) ;
}
}
}
LeaveCriticalSection ( globalRenderableTileEntities_cs ) ;
PIXEndNamedEvent ( ) ;
# else
// Find the removed ones:
// 4J - original code for this section:
/*
Set < TileEntity > newTileEntities = new HashSet < TileEntity > ( ) ;
newTileEntities . addAll ( renderableTileEntities ) ;
newTileEntities . removeAll ( oldTileEntities ) ;
globalRenderableTileEntities . addAll ( newTileEntities ) ;
oldTileEntities . removeAll ( renderableTileEntities ) ;
globalRenderableTileEntities . removeAll ( oldTileEntities ) ;
*/
2026-03-02 15:58:20 +07:00
unordered_set < std : : shared_ptr < TileEntity > > newTileEntities ( renderableTileEntities . begin ( ) , renderableTileEntities . end ( ) ) ;
2026-03-01 12:16:08 +08:00
AUTO_VAR ( endIt , oldTileEntities . end ( ) ) ;
2026-03-02 15:58:20 +07:00
for ( unordered_set < std : : shared_ptr < TileEntity > > : : iterator it = oldTileEntities . begin ( ) ; it ! = endIt ; it + + )
2026-03-01 12:16:08 +08:00
{
newTileEntities . erase ( * it ) ;
}
// 4J - newTileEntities is now renderableTileEntities with any old ones from oldTileEntitesRemoved (so just new things added)
EnterCriticalSection ( globalRenderableTileEntities_cs ) ;
endIt = newTileEntities . end ( ) ;
2026-03-02 15:58:20 +07:00
for ( unordered_set < std : : shared_ptr < TileEntity > > : : iterator it = newTileEntities . begin ( ) ; it ! = endIt ; it + + )
2026-03-01 12:16:08 +08:00
{
globalRenderableTileEntities - > push_back ( * it ) ;
}
// 4J - All these new things added to globalRenderableTileEntities
AUTO_VAR ( endItRTE , renderableTileEntities . end ( ) ) ;
2026-03-02 15:58:20 +07:00
for ( vector < std : : shared_ptr < TileEntity > > : : iterator it = renderableTileEntities . begin ( ) ; it ! = endItRTE ; it + + )
2026-03-01 12:16:08 +08:00
{
oldTileEntities . erase ( * it ) ;
}
// 4J - oldTileEntities is now the removed items
2026-03-02 15:58:20 +07:00
vector < std : : shared_ptr < TileEntity > > : : iterator it = globalRenderableTileEntities - > begin ( ) ;
2026-03-01 12:16:08 +08:00
while ( it ! = globalRenderableTileEntities - > end ( ) )
{
if ( oldTileEntities . find ( * it ) ! = oldTileEntities . end ( ) )
{
it = globalRenderableTileEntities - > erase ( it ) ;
}
else
{
+ + it ;
}
}
LeaveCriticalSection ( globalRenderableTileEntities_cs ) ;
# endif
// 4J - These removed items are now also removed from globalRenderableTileEntities
if ( LevelChunk : : touchedSky )
{
levelRenderer - > clearGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_NOTSKYLIT ) ;
}
else
{
levelRenderer - > setGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_NOTSKYLIT ) ;
}
levelRenderer - > setGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_COMPILED ) ;
PIXEndNamedEvent ( ) ;
return ;
}
# ifdef __PS3__
ChunkRebuildData g_rebuildDataIn __attribute__ ( ( __aligned__ ( 16 ) ) ) ;
ChunkRebuildData g_rebuildDataOut __attribute__ ( ( __aligned__ ( 16 ) ) ) ;
TileCompressData_SPU g_tileCompressDataIn __attribute__ ( ( __aligned__ ( 16 ) ) ) ;
unsigned char * g_tileCompressDataOut = ( unsigned char * ) & g_rebuildDataIn . m_tileIds ;
# define TILE_RENDER_SPU
void RunSPURebuild ( )
{
static C4JSpursJobQueue : : Port p ( " C4JSpursJob_ChunkUpdate " ) ;
C4JSpursJob_CompressedTile tileJob ( & g_tileCompressDataIn , g_tileCompressDataOut ) ;
C4JSpursJob_ChunkUpdate chunkJob ( & g_rebuildDataIn , & g_rebuildDataOut ) ;
if ( g_rebuildDataIn . m_currentLayer = = 0 ) // only need to create the tiles on the first layer
{
p . submitJob ( & tileJob ) ;
p . submitSync ( ) ;
}
p . submitJob ( & chunkJob ) ;
p . waitForCompletion ( ) ;
assert ( g_rebuildDataIn . m_x0 = = g_rebuildDataOut . m_x0 ) ;
}
void Chunk : : rebuild_SPU ( )
{
// if (!dirty) return;
Chunk : : t = Tesselator : : getInstance ( ) ; // 4J - added - static initialiser being set at the wrong time
updates + + ;
int x0 = x ;
int y0 = y ;
int z0 = z ;
int x1 = x + SIZE ;
int y1 = y + SIZE ;
int z1 = z + SIZE ;
LevelChunk : : touchedSky = false ;
2026-03-02 15:58:20 +07:00
// unordered_set<std::shared_ptr<TileEntity> > oldTileEntities(renderableTileEntities.begin(),renderableTileEntities.end()); // 4J removed this & next line
2026-03-01 12:16:08 +08:00
// renderableTileEntities.clear();
2026-03-02 15:58:20 +07:00
vector < std : : shared_ptr < TileEntity > > renderableTileEntities ; // 4J - added
2026-03-01 12:16:08 +08:00
// List<TileEntity> newTileEntities = new ArrayList<TileEntity>();
// newTileEntities.clear();
// renderableTileEntities.clear();
int r = 1 ;
Region region ( level , x0 - r , y0 - r , z0 - r , x1 + r , y1 + r , z1 + r ) ;
TileRenderer tileRenderer ( & region ) ;
int lists = levelRenderer - > getGlobalIndexForChunk ( this - > x , this - > y , this - > z , level ) * 2 ;
lists + = levelRenderer - > chunkLists ;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 4J - optimisation begins.
// Get the data for the level chunk that this render chunk is it (level chunk is 16 x 16 x 128,
// render chunk is 16 x 16 x 16. We wouldn't have to actually get all of it if the data was ordered differently, but currently
// it is ordered by x then z then y so just getting a small range of y out of it would involve getting the whole thing into
2026-03-02 15:58:20 +07:00
// the cache anyway.
2026-03-01 12:16:08 +08:00
ChunkRebuildData * pOutData = NULL ;
g_rebuildDataIn . buildForChunk ( & region , level , x0 , y0 , z0 ) ;
Tesselator : : Bounds bounds ;
{
2026-03-02 15:58:20 +07:00
// this was the old default clip bounds for the chunk, set in Chunk::setPos.
2026-03-01 12:16:08 +08:00
float g = 6.0f ;
bounds . boundingBox [ 0 ] = - g ;
bounds . boundingBox [ 1 ] = - g ;
bounds . boundingBox [ 2 ] = - g ;
bounds . boundingBox [ 3 ] = SIZE + g ;
bounds . boundingBox [ 4 ] = SIZE + g ;
bounds . boundingBox [ 5 ] = SIZE + g ;
}
for ( int currentLayer = 0 ; currentLayer < 2 ; currentLayer + + )
{
bool rendered = false ;
{
glNewList ( lists + currentLayer , GL_COMPILE ) ;
MemSect ( 0 ) ;
glPushMatrix ( ) ;
glDepthMask ( true ) ; // 4J added
t - > useCompactVertices ( true ) ; // 4J added
translateToPos ( ) ;
float ss = 1.000001f ;
// 4J - have removed this scale as I don't think we should need it, and have now optimised the vertex
// shader so it doesn't do anything other than translate with this matrix anyway
#if 0
glTranslatef ( - zs / 2.0f , - ys / 2.0f , - zs / 2.0f ) ;
glScalef ( ss , ss , ss ) ;
glTranslatef ( zs / 2.0f , ys / 2.0f , zs / 2.0f ) ;
# endif
t - > begin ( ) ;
t - > offset ( ( float ) ( - this - > x ) , ( float ) ( - this - > y ) , ( float ) ( - this - > z ) ) ;
}
g_rebuildDataIn . copyFromTesselator ( ) ;
intArray_SPU tesselatorArray ( ( unsigned int * ) g_rebuildDataIn . m_tesselator . m_PPUArray ) ;
g_rebuildDataIn . m_tesselator . _array = & tesselatorArray ;
g_rebuildDataIn . m_currentLayer = currentLayer ;
# ifdef TILE_RENDER_SPU
g_tileCompressDataIn . setForChunk ( & region , x0 , y0 , z0 ) ;
RunSPURebuild ( ) ;
g_rebuildDataOut . storeInTesselator ( ) ;
pOutData = & g_rebuildDataOut ;
# else
g_rebuildDataIn . disableUnseenTiles ( ) ;
TileRenderer_SPU * pTileRenderer = new TileRenderer_SPU ( & g_rebuildDataIn ) ;
g_rebuildDataIn . tesselateAllTiles ( pTileRenderer ) ;
g_rebuildDataIn . storeInTesselator ( ) ;
pOutData = & g_rebuildDataIn ;
# endif
if ( pOutData - > m_flags & ChunkRebuildData : : e_flag_Rendered )
rendered = true ;
// 4J - changed loop order here to leave y as the innermost loop for better cache performance
for ( int z = z0 ; z < z1 ; z + + )
{
for ( int x = x0 ; x < x1 ; x + + )
{
for ( int y = y0 ; y < y1 ; y + + )
{
// 4J - get tile from those copied into our local array in earlier optimisation
unsigned char tileId = pOutData - > getTile ( x , y , z ) ;
if ( tileId > 0 )
{
if ( currentLayer = = 0 & & Tile : : tiles [ tileId ] - > isEntityTile ( ) )
{
2026-03-02 15:58:20 +07:00
std : : shared_ptr < TileEntity > et = region . getTileEntity ( x , y , z ) ;
2026-03-01 12:16:08 +08:00
if ( TileEntityRenderDispatcher : : instance - > hasRenderer ( et ) )
{
renderableTileEntities . push_back ( et ) ;
}
}
int flags = pOutData - > getFlags ( x , y , z ) ;
if ( flags & ChunkRebuildData : : e_flag_SPURenderCodeMissing )
{
Tile * tile = Tile : : tiles [ tileId ] ;
int renderLayer = tile - > getRenderLayer ( ) ;
if ( renderLayer ! = currentLayer )
{
// renderNextLayer = true;
}
else if ( renderLayer = = currentLayer )
{
//if(currentLayer == 0)
// numRenderedLayer0++;
rendered | = tileRenderer . tesselateInWorld ( tile , x , y , z ) ;
}
}
}
}
}
}
{
t - > end ( ) ;
bounds . addBounds ( t - > bounds ) ;
glPopMatrix ( ) ;
glEndList ( ) ;
t - > useCompactVertices ( false ) ; // 4J added
t - > offset ( 0 , 0 , 0 ) ;
}
if ( rendered )
{
levelRenderer - > clearGlobalChunkFlag ( this - > x , this - > y , this - > z , level , LevelRenderer : : CHUNK_FLAG_EMPTY0 , currentLayer ) ;
}
else
{
// 4J - added - clear any renderer data associated with this unused list
levelRenderer - > setGlobalChunkFlag ( this - > x , this - > y , this - > z , level , LevelRenderer : : CHUNK_FLAG_EMPTY0 , currentLayer ) ;
RenderManager . CBuffClear ( lists + currentLayer ) ;
}
}
if ( bb )
{
bb - > set ( bounds . boundingBox [ 0 ] , bounds . boundingBox [ 1 ] , bounds . boundingBox [ 2 ] ,
bounds . boundingBox [ 3 ] , bounds . boundingBox [ 4 ] , bounds . boundingBox [ 5 ] ) ;
}
if ( pOutData - > m_flags & ChunkRebuildData : : e_flag_TouchedSky )
LevelChunk : : touchedSky = true ;
// 4J - have rewritten the way that tile entities are stored globally to make it work more easily with split screen. Chunks are now
// stored globally in the levelrenderer, in a hashmap with a special key made up from the dimension and chunk position (using same index
// as is used for global flags)
# if 1
int key = levelRenderer - > getGlobalIndexForChunk ( this - > x , this - > y , this - > z , level ) ;
EnterCriticalSection ( globalRenderableTileEntities_cs ) ;
if ( renderableTileEntities . size ( ) )
{
AUTO_VAR ( it , globalRenderableTileEntities - > find ( key ) ) ;
if ( it ! = globalRenderableTileEntities - > end ( ) )
{
// We've got some renderable tile entities that we want associated with this chunk, and an existing list of things that used to be.
// We need to flag any that we don't need any more to be removed, keep those that we do, and add any new ones
// First pass - flag everything already existing to be removed
for ( AUTO_VAR ( it2 , it - > second . begin ( ) ) ; it2 ! = it - > second . end ( ) ; it2 + + )
{
( * it2 ) - > setRenderRemoveStage ( TileEntity : : e_RenderRemoveStageFlaggedAtChunk ) ;
}
// Now go through the current list. If these are already in the list, then unflag the remove flag. If they aren't, then add
for ( int i = 0 ; i < renderableTileEntities . size ( ) ; i + + )
{
AUTO_VAR ( it2 , find ( it - > second . begin ( ) , it - > second . end ( ) , renderableTileEntities [ i ] ) ) ;
if ( it2 = = it - > second . end ( ) )
{
( * globalRenderableTileEntities ) [ key ] . push_back ( renderableTileEntities [ i ] ) ;
}
else
{
( * it2 ) - > setRenderRemoveStage ( TileEntity : : e_RenderRemoveStageKeep ) ;
}
}
}
else
{
// Easy case - nothing already existing for this chunk. Add them all in.
for ( int i = 0 ; i < renderableTileEntities . size ( ) ; i + + )
{
( * globalRenderableTileEntities ) [ key ] . push_back ( renderableTileEntities [ i ] ) ;
}
}
}
else
{
// Another easy case - we don't want any renderable tile entities associated with this chunk. Flag all to be removed.
AUTO_VAR ( it , globalRenderableTileEntities - > find ( key ) ) ;
if ( it ! = globalRenderableTileEntities - > end ( ) )
{
for ( AUTO_VAR ( it2 , it - > second . begin ( ) ) ; it2 ! = it - > second . end ( ) ; it2 + + )
{
( * it2 ) - > setRenderRemoveStage ( TileEntity : : e_RenderRemoveStageFlaggedAtChunk ) ;
}
}
}
LeaveCriticalSection ( globalRenderableTileEntities_cs ) ;
# else
// Find the removed ones:
// 4J - original code for this section:
/*
Set < TileEntity > newTileEntities = new HashSet < TileEntity > ( ) ;
newTileEntities . addAll ( renderableTileEntities ) ;
newTileEntities . removeAll ( oldTileEntities ) ;
globalRenderableTileEntities . addAll ( newTileEntities ) ;
oldTileEntities . removeAll ( renderableTileEntities ) ;
globalRenderableTileEntities . removeAll ( oldTileEntities ) ;
*/
2026-03-02 15:58:20 +07:00
unordered_set < std : : shared_ptr < TileEntity > > newTileEntities ( renderableTileEntities . begin ( ) , renderableTileEntities . end ( ) ) ;
2026-03-01 12:16:08 +08:00
AUTO_VAR ( endIt , oldTileEntities . end ( ) ) ;
2026-03-02 15:58:20 +07:00
for ( unordered_set < std : : shared_ptr < TileEntity > > : : iterator it = oldTileEntities . begin ( ) ; it ! = endIt ; it + + )
2026-03-01 12:16:08 +08:00
{
newTileEntities . erase ( * it ) ;
}
// 4J - newTileEntities is now renderableTileEntities with any old ones from oldTileEntitesRemoved (so just new things added)
EnterCriticalSection ( globalRenderableTileEntities_cs ) ;
endIt = newTileEntities . end ( ) ;
2026-03-02 15:58:20 +07:00
for ( unordered_set < std : : shared_ptr < TileEntity > > : : iterator it = newTileEntities . begin ( ) ; it ! = endIt ; it + + )
2026-03-01 12:16:08 +08:00
{
globalRenderableTileEntities . push_back ( * it ) ;
}
// 4J - All these new things added to globalRenderableTileEntities
AUTO_VAR ( endItRTE , renderableTileEntities . end ( ) ) ;
2026-03-02 15:58:20 +07:00
for ( vector < std : : shared_ptr < TileEntity > > : : iterator it = renderableTileEntities . begin ( ) ; it ! = endItRTE ; it + + )
2026-03-01 12:16:08 +08:00
{
oldTileEntities . erase ( * it ) ;
}
// 4J - oldTileEntities is now the removed items
2026-03-02 15:58:20 +07:00
vector < std : : shared_ptr < TileEntity > > : : iterator it = globalRenderableTileEntities - > begin ( ) ;
2026-03-01 12:16:08 +08:00
while ( it ! = globalRenderableTileEntities - > end ( ) )
{
if ( oldTileEntities . find ( * it ) ! = oldTileEntities . end ( ) )
{
it = globalRenderableTileEntities - > erase ( it ) ;
}
else
{
+ + it ;
}
}
LeaveCriticalSection ( globalRenderableTileEntities_cs ) ;
# endif
// 4J - These removed items are now also removed from globalRenderableTileEntities
if ( LevelChunk : : touchedSky )
{
levelRenderer - > clearGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_NOTSKYLIT ) ;
}
else
{
levelRenderer - > setGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_NOTSKYLIT ) ;
}
levelRenderer - > setGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_COMPILED ) ;
return ;
}
# endif // _PS3_
2026-03-02 15:58:20 +07:00
float Chunk : : distanceToSqr ( std : : shared_ptr < Entity > player ) const
2026-03-01 12:16:08 +08:00
{
float xd = ( float ) ( player - > x - xm ) ;
float yd = ( float ) ( player - > y - ym ) ;
float zd = ( float ) ( player - > z - zm ) ;
return xd * xd + yd * yd + zd * zd ;
}
2026-03-02 15:58:20 +07:00
float Chunk : : squishedDistanceToSqr ( std : : shared_ptr < Entity > player )
2026-03-01 12:16:08 +08:00
{
float xd = ( float ) ( player - > x - xm ) ;
float yd = ( float ) ( player - > y - ym ) * 2 ;
float zd = ( float ) ( player - > z - zm ) ;
return xd * xd + yd * yd + zd * zd ;
}
void Chunk : : reset ( )
{
if ( assigned )
{
EnterCriticalSection ( & levelRenderer - > m_csDirtyChunks ) ;
unsigned char refCount = levelRenderer - > decGlobalChunkRefCount ( x , y , z , level ) ;
assigned = false ;
// printf("\t\t [dec] refcount %d at %d, %d, %d\n",refCount,x,y,z);
if ( refCount = = 0 )
{
int lists = levelRenderer - > getGlobalIndexForChunk ( x , y , z , level ) * 2 ;
if ( lists > = 0 )
{
lists + = levelRenderer - > chunkLists ;
for ( int i = 0 ; i < 2 ; i + + )
{
// 4J - added - clear any renderer data associated with this unused list
RenderManager . CBuffClear ( lists + i ) ;
}
levelRenderer - > setGlobalChunkFlags ( x , y , z , level , 0 ) ;
}
}
LeaveCriticalSection ( & levelRenderer - > m_csDirtyChunks ) ;
}
clipChunk - > visible = false ;
}
void Chunk : : _delete ( )
{
reset ( ) ;
level = NULL ;
}
int Chunk : : getList ( int layer )
{
if ( ! clipChunk - > visible ) return - 1 ;
int lists = levelRenderer - > getGlobalIndexForChunk ( x , y , z , level ) * 2 ;
lists + = levelRenderer - > chunkLists ;
bool empty = levelRenderer - > getGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_EMPTY0 , layer ) ;
if ( ! empty ) return lists + layer ;
return - 1 ;
}
void Chunk : : cull ( Culler * culler )
{
clipChunk - > visible = culler - > isVisible ( bb ) ;
}
void Chunk : : renderBB ( )
{
// glCallList(lists + 2); // 4J - removed - TODO put back in
}
bool Chunk : : isEmpty ( )
{
if ( ! levelRenderer - > getGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_COMPILED ) ) return false ;
return levelRenderer - > getGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_EMPTYBOTH ) ;
}
void Chunk : : setDirty ( )
{
// 4J - not used, but if this starts being used again then we'll need to investigate how best to handle it.
__debugbreak ( ) ;
levelRenderer - > setGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_DIRTY ) ;
}
void Chunk : : clearDirty ( )
{
levelRenderer - > clearGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_DIRTY ) ;
# ifdef _CRITICAL_CHUNKS
levelRenderer - > clearGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_CRITICAL ) ;
# endif
}
Chunk : : ~ Chunk ( )
{
delete bb ;
}
bool Chunk : : emptyFlagSet ( int layer )
{
return levelRenderer - > getGlobalChunkFlag ( x , y , z , level , LevelRenderer : : CHUNK_FLAG_EMPTY0 , layer ) ;
}