Fix batching logic and audio state management - v1.7.1
This commit is contained in:
parent
f4ce5aade6
commit
bbeec34781
204
src/main.cpp
204
src/main.cpp
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue