Fix batching logic and audio state management - v1.7.1

This commit is contained in:
Michael Howard 2026-04-23 16:21:36 -05:00
parent f4ce5aade6
commit bbeec34781
1 changed files with 90 additions and 114 deletions

View File

@ -67,10 +67,12 @@ struct ChunkPosHash {
struct Chunk { struct Chunk {
int blocks[CHUNK_SIZE][CHUNK_HEIGHT][CHUNK_SIZE]; int blocks[CHUNK_SIZE][CHUNK_HEIGHT][CHUNK_SIZE];
bool generated; int maxY = 0;
bool modified; bool generated = false;
int maxY; // Highest non-air Y in this chunk; limits the render loop bool modified = false;
Chunk() : generated(false), modified(false), maxY(0) {} bool dirty = true;
std::vector<Vector3> renderLists[16];
Chunk() : generated(false), modified(false), maxY(0), dirty(true) {}
}; };
// Global variables for persistence // Global variables for persistence
@ -78,6 +80,12 @@ std::unordered_map<ChunkPos, Chunk*, ChunkPosHash> worldChunks;
static unsigned int globalSeedHash = 0; static unsigned int globalSeedHash = 0;
static std::string currentWorldName = ""; static std::string currentWorldName = "";
// Forward Declarations
bool IsExposedOptimized(int lx, int ly, int lz, Chunk* chunk, Chunk* nxM, Chunk* nxP, Chunk* nzM, Chunk* nzP);
void RebuildChunkRenderList(Chunk* chunk, int cx, int cz);
void GenerateChunk(int cx, int cz);
void SetBlock(int x, int y, int z, int type);
// ---- Inventory System ---- // ---- Inventory System ----
struct InventorySlot { struct InventorySlot {
int blockType = AIR; int blockType = AIR;
@ -170,6 +178,37 @@ int GetBlock(int x, int y, int z) {
return 0; // Air if chunk not loaded return 0; // Air if chunk not loaded
} }
void RebuildChunkRenderList(Chunk* chunk, int cx, int cz) {
for (int i = 0; i < 16; i++) chunk->renderLists[i].clear();
// Neighbors for exposure check
auto itNM = worldChunks.find({cx-1, cz});
Chunk* nxM = (itNM != worldChunks.end()) ? itNM->second : nullptr;
auto itNP = worldChunks.find({cx+1, cz});
Chunk* nxP = (itNP != worldChunks.end()) ? itNP->second : nullptr;
auto itZM = worldChunks.find({cx, cz-1});
Chunk* nzM = (itZM != worldChunks.end()) ? itZM->second : nullptr;
auto itZP = worldChunks.find({cx, cz+1});
Chunk* nzP = (itZP != worldChunks.end()) ? itZP->second : nullptr;
int worldX = cx * CHUNK_SIZE;
int worldZ = cz * CHUNK_SIZE;
for (int lx = 0; lx < CHUNK_SIZE; lx++) {
for (int ly = 0; ly <= chunk->maxY; ly++) {
for (int lz = 0; lz < CHUNK_SIZE; lz++) {
int bt = chunk->blocks[lx][ly][lz];
if (bt == AIR) continue;
if (IsExposedOptimized(lx, ly, lz, chunk, nxM, nxP, nzM, nzP)) {
chunk->renderLists[bt].push_back((Vector3){(float)(worldX+lx), (float)ly, (float)(worldZ+lz)});
}
}
}
}
chunk->dirty = false;
}
void SetBlock(int x, int y, int z, int type) { void SetBlock(int x, int y, int z, int type) {
if (y < 0 || y >= CHUNK_HEIGHT) return; if (y < 0 || y >= CHUNK_HEIGHT) return;
int cx = (int)floorf((float)x / CHUNK_SIZE); int cx = (int)floorf((float)x / CHUNK_SIZE);
@ -180,9 +219,16 @@ void SetBlock(int x, int y, int z, int type) {
int lz = z - (cz * CHUNK_SIZE); int lz = z - (cz * CHUNK_SIZE);
worldChunks[key]->blocks[lx][y][lz] = type; worldChunks[key]->blocks[lx][y][lz] = type;
worldChunks[key]->modified = true; worldChunks[key]->modified = true;
// Keep maxY up to date when placing blocks above the current max worldChunks[key]->dirty = true;
if (type != AIR && y > worldChunks[key]->maxY)
worldChunks[key]->maxY = y; // Keep maxY up to date
if (type != AIR && y > worldChunks[key]->maxY) worldChunks[key]->maxY = y;
// Mark neighbors dirty as well since exposure changes
if (lx == 0) { auto n = worldChunks.find({cx-1, cz}); if (n != worldChunks.end()) n->second->dirty = true; }
if (lx == CHUNK_SIZE-1) { auto n = worldChunks.find({cx+1, cz}); if (n != worldChunks.end()) n->second->dirty = true; }
if (lz == 0) { auto n = worldChunks.find({cx, cz-1}); if (n != worldChunks.end()) n->second->dirty = true; }
if (lz == CHUNK_SIZE-1) { auto n = worldChunks.find({cx, cz+1}); if (n != worldChunks.end()) n->second->dirty = true; }
} }
} }
@ -768,12 +814,15 @@ int main(void)
{ {
// Update // Update
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Update ONLY active music streams to save CPU // Update ONLY active music streams and ensure others are fully stopped
if (currentState == MAIN_MENU || currentState == WORLD_SELECT || currentState == CREATE_WORLD) { if (currentState == MAIN_MENU || currentState == WORLD_SELECT || currentState == CREATE_WORLD) {
UpdateMusicStream(titleMusic); UpdateMusicStream(titleMusic);
if (IsMusicStreamPlaying(gameplayMusic)) StopMusicStream(gameplayMusic);
if (IsMusicStreamPlaying(nightMusic)) StopMusicStream(nightMusic);
} else { } else {
UpdateMusicStream(gameplayMusic); UpdateMusicStream(gameplayMusic);
UpdateMusicStream(nightMusic); UpdateMusicStream(nightMusic);
if (IsMusicStreamPlaying(titleMusic)) StopMusicStream(titleMusic);
} }
// Handle title music loop fading // Handle title music loop fading
@ -1099,8 +1148,8 @@ int main(void)
DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE); DrawTexturePro(titleTexture, sourceRec, destRec, origin, 0.0f, WHITE);
EndMode2D(); EndMode2D();
// Show Version Number (v1.7.0) in Red // Show Version Number (v1.7.1) in Red
DrawTextEx(customFont, "v1.7.0", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED); DrawTextEx(customFont, "v1.7.1", (Vector2){ (float)currentWidth - 140, (float)currentHeight - 40 }, 22, 1.0f, RED);
} }
Vector2 mousePos = GetMousePosition(); Vector2 mousePos = GetMousePosition();
@ -1574,124 +1623,51 @@ int main(void)
Vector3 camForward = Vector3Normalize(Vector3Subtract(camera3D.target, camera3D.position)); Vector3 camForward = Vector3Normalize(Vector3Subtract(camera3D.target, camera3D.position));
// Optimized Render Loop: Pre-fetch neighbor chunks and apply Frustum Culling // --- PERFORMANCE OPTIMIZED BATCHED RENDER LOOP ---
// 1. Identify chunks to render and rebuild dirty ones
std::vector<Chunk*> visibleChunks;
for (int cx = playerCX - RENDER_DISTANCE; cx <= playerCX + RENDER_DISTANCE; cx++) {
for (int cz = playerCZ - RENDER_DISTANCE; cz <= playerCZ + RENDER_DISTANCE; cz++) {
// FRUSTUM CULLING
Vector3 chunkCenter = { (float)(cx * CHUNK_SIZE + 8), 32.0f, (float)(cz * CHUNK_SIZE + 8) };
Vector3 toChunk = Vector3Normalize(Vector3Subtract(chunkCenter, camera3D.position));
if (Vector3Length(Vector3Subtract(chunkCenter, camera3D.position)) > 24.0f &&
Vector3DotProduct(toChunk, camForward) < 0.4f) continue;
auto it = worldChunks.find({cx, cz});
if (it == worldChunks.end()) continue;
Chunk* chunk = it->second;
if (chunk->dirty) RebuildChunkRenderList(chunk, cx, cz);
visibleChunks.push_back(chunk);
}
}
// 2. Batch render per type
for (int renderType = 1; renderType <= 13; renderType++) { for (int renderType = 1; renderType <= 13; renderType++) {
if (renderType == GRASS) { if (renderType == GRASS) {
for (int cx = playerCX - RENDER_DISTANCE; cx <= playerCX + RENDER_DISTANCE; cx++) { for (Chunk* chunk : visibleChunks) {
for (int cz = playerCZ - RENDER_DISTANCE; cz <= playerCZ + RENDER_DISTANCE; cz++) { for (auto& pos : chunk->renderLists[GRASS]) {
// FRUSTUM CULLING: Skip chunks not in view DrawGrassBlock(pos, blockTextures[GRASS].id, grassTopTexture.id, blockTextures[DIRT].id, blockTint);
Vector3 chunkCenter = { (float)(cx * CHUNK_SIZE + 8), 32.0f, (float)(cz * CHUNK_SIZE + 8) };
Vector3 toChunk = Vector3Normalize(Vector3Subtract(chunkCenter, camera3D.position));
if (Vector3Length(Vector3Subtract(chunkCenter, camera3D.position)) > 24.0f &&
Vector3DotProduct(toChunk, camForward) < 0.4f) continue;
auto it = worldChunks.find({cx, cz});
if (it == worldChunks.end()) continue;
Chunk* chunk = it->second;
auto itNM = worldChunks.find({cx-1, cz});
Chunk* nxM = (itNM != worldChunks.end()) ? itNM->second : nullptr;
auto itNP = worldChunks.find({cx+1, cz});
Chunk* nxP = (itNP != worldChunks.end()) ? itNP->second : nullptr;
auto itZM = worldChunks.find({cx, cz-1});
Chunk* nzM = (itZM != worldChunks.end()) ? itZM->second : nullptr;
auto itZP = worldChunks.find({cx, cz+1});
Chunk* nzP = (itZP != worldChunks.end()) ? itZP->second : nullptr;
int worldX = cx * CHUNK_SIZE;
int worldZ = cz * CHUNK_SIZE;
for (int lx = 0; lx < CHUNK_SIZE; lx++) {
for (int ly = 0; ly <= chunk->maxY; ly++) {
for (int lz = 0; lz < CHUNK_SIZE; lz++) {
if (chunk->blocks[lx][ly][lz] != GRASS) continue;
if (IsExposedOptimized(lx, ly, lz, chunk, nxM, nxP, nzM, nzP)) {
DrawGrassBlock((Vector3){(float)(worldX+lx), (float)ly, (float)(worldZ+lz)},
blockTextures[GRASS].id, grassTopTexture.id, blockTextures[DIRT].id, blockTint);
}
}
}
}
} }
} }
} else if (renderType == CRAFTING_TABLE) { } else if (renderType == CRAFTING_TABLE) {
for (int cx = playerCX - RENDER_DISTANCE; cx <= playerCX + RENDER_DISTANCE; cx++) { for (Chunk* chunk : visibleChunks) {
for (int cz = playerCZ - RENDER_DISTANCE; cz <= playerCZ + RENDER_DISTANCE; cz++) { for (auto& pos : chunk->renderLists[CRAFTING_TABLE]) {
// FRUSTUM CULLING DrawCraftingTable(pos, craftingSideTexture.id, craftingTopTexture.id, blockTextures[DIRT].id, blockTint);
Vector3 chunkCenter = { (float)(cx * CHUNK_SIZE + 8), 32.0f, (float)(cz * CHUNK_SIZE + 8) };
Vector3 toChunk = Vector3Normalize(Vector3Subtract(chunkCenter, camera3D.position));
if (Vector3Length(Vector3Subtract(chunkCenter, camera3D.position)) > 24.0f &&
Vector3DotProduct(toChunk, camForward) < 0.4f) continue;
auto it = worldChunks.find({cx, cz});
if (it == worldChunks.end()) continue;
Chunk* chunk = it->second;
auto itNM = worldChunks.find({cx-1, cz});
Chunk* nxM = (itNM != worldChunks.end()) ? itNM->second : nullptr;
auto itNP = worldChunks.find({cx+1, cz});
Chunk* nxP = (itNP != worldChunks.end()) ? itNP->second : nullptr;
auto itZM = worldChunks.find({cx, cz-1});
Chunk* nzM = (itZM != worldChunks.end()) ? itZM->second : nullptr;
auto itZP = worldChunks.find({cx, cz+1});
Chunk* nzP = (itZP != worldChunks.end()) ? itZP->second : nullptr;
int worldX = cx * CHUNK_SIZE;
int worldZ = cz * CHUNK_SIZE;
for (int lx = 0; lx < CHUNK_SIZE; lx++) {
for (int ly = 0; ly <= chunk->maxY; ly++) {
for (int lz = 0; lz < CHUNK_SIZE; lz++) {
if (chunk->blocks[lx][ly][lz] != CRAFTING_TABLE) continue;
if (!IsExposedOptimized(lx, ly, lz, chunk, nxM, nxP, nzM, nzP)) continue;
DrawCraftingTable((Vector3){(float)(worldX+lx), (float)ly, (float)(worldZ+lz)},
craftingSideTexture.id, craftingTopTexture.id, blockTextures[DIRT].id, blockTint);
}
}
}
} }
} }
} else { } else {
unsigned int currentTexId = blockTextures[renderType].id; rlSetTexture(blockTextures[renderType].id);
rlSetTexture(currentTexId);
rlBegin(RL_QUADS); rlBegin(RL_QUADS);
rlColor4ub(blockTint.r, blockTint.g, blockTint.b, 255); rlColor4ub(blockTint.r, blockTint.g, blockTint.b, 255);
if (renderType == LEAVES) { if (renderType == LEAVES) {
// Combined green and day/night tint
rlColor4ub((unsigned char)(blockTint.r * 0.2f), (unsigned char)(blockTint.g * 0.6f), (unsigned char)(blockTint.b * 0.2f), 255); rlColor4ub((unsigned char)(blockTint.r * 0.2f), (unsigned char)(blockTint.g * 0.6f), (unsigned char)(blockTint.b * 0.2f), 255);
} }
for (int cx = playerCX - RENDER_DISTANCE; cx <= playerCX + RENDER_DISTANCE; cx++) { for (Chunk* chunk : visibleChunks) {
for (int cz = playerCZ - RENDER_DISTANCE; cz <= playerCZ + RENDER_DISTANCE; cz++) { for (auto& pos : chunk->renderLists[renderType]) {
// FRUSTUM CULLING DrawCubeVertices(pos.x, pos.y, pos.z, 1.0f, 1.0f, 1.0f);
Vector3 chunkCenter = { (float)(cx * CHUNK_SIZE + 8), 32.0f, (float)(cz * CHUNK_SIZE + 8) };
Vector3 toChunk = Vector3Normalize(Vector3Subtract(chunkCenter, camera3D.position));
if (Vector3Length(Vector3Subtract(chunkCenter, camera3D.position)) > 24.0f &&
Vector3DotProduct(toChunk, camForward) < 0.4f) continue;
auto it = worldChunks.find({cx, cz});
if (it == worldChunks.end()) continue;
Chunk* chunk = it->second;
auto itNM = worldChunks.find({cx-1, cz});
Chunk* nxM = (itNM != worldChunks.end()) ? itNM->second : nullptr;
auto itNP = worldChunks.find({cx+1, cz});
Chunk* nxP = (itNP != worldChunks.end()) ? itNP->second : nullptr;
auto itZM = worldChunks.find({cx, cz-1});
Chunk* nzM = (itZM != worldChunks.end()) ? itZM->second : nullptr;
auto itZP = worldChunks.find({cx, cz+1});
Chunk* nzP = (itZP != worldChunks.end()) ? itZP->second : nullptr;
int worldX = cx * CHUNK_SIZE;
int worldZ = cz * CHUNK_SIZE;
for (int lx = 0; lx < CHUNK_SIZE; lx++) {
for (int ly = 0; ly <= chunk->maxY; ly++) {
for (int lz = 0; lz < CHUNK_SIZE; lz++) {
if (chunk->blocks[lx][ly][lz] != renderType) continue;
if (!IsExposedOptimized(lx, ly, lz, chunk, nxM, nxP, nzM, nzP)) continue;
DrawCubeVertices((float)(worldX+lx), (float)ly, (float)(worldZ+lz), 1.0f, 1.0f, 1.0f);
}
}
}
} }
} }
rlEnd(); rlEnd();