diff --git a/src/Builder.c b/src/Builder.c index 038525d78..a2ee6dfbc 100644 --- a/src/Builder.c +++ b/src/Builder.c @@ -9,7 +9,6 @@ #include "ErrorHandler.h" #include "Drawer.h" #include "ExtMath.h" -#include "ChunkUpdater.h" #include "BlockID.h" #include "Block.h" #include "PackedCol.h" @@ -340,7 +339,7 @@ static bool Builder_BuildChunk(int x1, int y1, int z1, bool* allAir) { void Builder_MakeChunk(struct ChunkInfo* info) { int x = info->CentreX - 8, y = info->CentreY - 8, z = info->CentreZ - 8; - bool allAir, hasMesh, hasNorm, hasTrans; + bool allAir, hasMesh, hasNorm, hasTran; int totalVerts, partsIndex; int i, j, curIdx, offset; @@ -357,22 +356,22 @@ void Builder_MakeChunk(struct ChunkInfo* info) { #endif partsIndex = MapRenderer_Pack(x >> CHUNK_SHIFT, y >> CHUNK_SHIFT, z >> CHUNK_SHIFT); - offset = 0; - hasNorm = false; - hasTrans = false; + offset = 0; + hasNorm = false; + hasTran = false; for (i = 0; i < MapRenderer_1DUsedCount; i++) { j = i + ATLAS1D_MAX_ATLASES; curIdx = partsIndex + i * MapRenderer_ChunksCount; Builder_SetPartInfo(&Builder_Parts[i], &offset, &MapRenderer_PartsNormal[curIdx], &hasNorm); - Builder_SetPartInfo(&Builder_Parts[j], &offset, &MapRenderer_PartsTranslucent[curIdx], &hasTrans); + Builder_SetPartInfo(&Builder_Parts[j], &offset, &MapRenderer_PartsTranslucent[curIdx], &hasTran); } if (hasNorm) { info->NormalParts = &MapRenderer_PartsNormal[partsIndex]; } - if (hasTrans) { + if (hasTran) { info->TranslucentParts = &MapRenderer_PartsTranslucent[partsIndex]; } diff --git a/src/ChunkUpdater.c b/src/ChunkUpdater.c deleted file mode 100644 index 9da9ca6f1..000000000 --- a/src/ChunkUpdater.c +++ /dev/null @@ -1,523 +0,0 @@ -#include "ChunkUpdater.h" -#include "Constants.h" -#include "Event.h" -#include "ExtMath.h" -#include "Funcs.h" -#include "Game.h" -#include "Graphics.h" -#include "Entity.h" -#include "MapRenderer.h" -#include "Platform.h" -#include "TerrainAtlas.h" -#include "World.h" -#include "Builder.h" -#include "Utils.h" -#include "ErrorHandler.h" -#include "Camera.h" - -static Vector3I ChunkUpdater_ChunkPos; -static uint32_t* ChunkUpdater_Distances; - -void ChunkInfo_Reset(struct ChunkInfo* chunk, int x, int y, int z) { - chunk->CentreX = x + 8; chunk->CentreY = y + 8; chunk->CentreZ = z + 8; -#ifndef CC_BUILD_GL11 - chunk->Vb = GFX_NULL; -#endif - - chunk->Visible = true; chunk->Empty = false; - chunk->PendingDelete = false; chunk->AllAir = false; - chunk->DrawXMin = false; chunk->DrawXMax = false; chunk->DrawZMin = false; - chunk->DrawZMax = false; chunk->DrawYMin = false; chunk->DrawYMax = false; - - chunk->NormalParts = NULL; - chunk->TranslucentParts = NULL; -} - -static int cu_chunksTarget = 12; -#define cu_targetTime ((1.0 / 30) + 0.01) -static Vector3 cu_lastCamPos; -static float cu_lastHeadY, cu_lastHeadX; -static int cu_elementsPerBitmap; - -static void ChunkUpdater_EnvVariableChanged(void* obj, int envVar) { - if (envVar == ENV_VAR_SUN_COL || envVar == ENV_VAR_SHADOW_COL) { - ChunkUpdater_Refresh(); - } else if (envVar == ENV_VAR_EDGE_HEIGHT || envVar == ENV_VAR_SIDES_OFFSET) { - int oldClip = Builder_EdgeLevel; - Builder_SidesLevel = max(0, Env_SidesHeight); - Builder_EdgeLevel = max(0, Env_EdgeHeight); - - /* Only need to refresh chunks on map borders up to highest edge level.*/ - ChunkUpdater_RefreshBorders(max(oldClip, Builder_EdgeLevel)); - } -} - -static void ChunkUpdater_TerrainAtlasChanged(void* obj) { - if (MapRenderer_1DUsedCount) { - bool refreshRequired = cu_elementsPerBitmap != Atlas1D_TilesPerAtlas; - if (refreshRequired) ChunkUpdater_Refresh(); - } - - MapRenderer_1DUsedCount = Atlas1D_UsedAtlasesCount(); - cu_elementsPerBitmap = Atlas1D_TilesPerAtlas; - ChunkUpdater_ResetPartFlags(); -} - -static void ChunkUpdater_BlockDefinitionChanged(void* obj) { - ChunkUpdater_Refresh(); - MapRenderer_1DUsedCount = Atlas1D_UsedAtlasesCount(); - ChunkUpdater_ResetPartFlags(); -} - -static void ChunkUpdater_ProjectionChanged(void* obj) { - cu_lastCamPos = Vector3_BigPos(); -} - -static void ChunkUpdater_ViewDistanceChanged(void* obj) { - cu_lastCamPos = Vector3_BigPos(); -} - - -static void ChunkUpdater_FreePartsAllocations(void) { - Mem_Free(MapRenderer_PartsBuffer_Raw); - MapRenderer_PartsBuffer_Raw = NULL; - MapRenderer_PartsNormal = NULL; - MapRenderer_PartsTranslucent = NULL; -} - -static void ChunkUpdater_FreeAllocations(void) { - if (!MapRenderer_Chunks) return; - ChunkUpdater_FreePartsAllocations(); - - Mem_Free(MapRenderer_Chunks); - Mem_Free(MapRenderer_SortedChunks); - Mem_Free(MapRenderer_RenderChunks); - Mem_Free(ChunkUpdater_Distances); - - MapRenderer_Chunks = NULL; - MapRenderer_SortedChunks = NULL; - MapRenderer_RenderChunks = NULL; - ChunkUpdater_Distances = NULL; -} - -static void ChunkUpdater_PerformPartsAllocations(void) { - uint32_t count = MapRenderer_ChunksCount * MapRenderer_1DUsedCount; - MapRenderer_PartsBuffer_Raw = Mem_AllocCleared(count * 2, sizeof(struct ChunkPartInfo), "chunk parts"); - MapRenderer_PartsNormal = MapRenderer_PartsBuffer_Raw; - MapRenderer_PartsTranslucent = MapRenderer_PartsBuffer_Raw + count; -} - -static void ChunkUpdater_PerformAllocations(void) { - MapRenderer_Chunks = Mem_Alloc(MapRenderer_ChunksCount, sizeof(struct ChunkInfo), "chunk info"); - MapRenderer_SortedChunks = Mem_Alloc(MapRenderer_ChunksCount, sizeof(struct ChunkInfo*), "sorted chunk info"); - MapRenderer_RenderChunks = Mem_Alloc(MapRenderer_ChunksCount, sizeof(struct ChunkInfo*), "render chunk info"); - ChunkUpdater_Distances = Mem_Alloc(MapRenderer_ChunksCount, 4, "chunk distances"); - ChunkUpdater_PerformPartsAllocations(); -} - -void ChunkUpdater_Refresh(void) { - int oldCount; - ChunkUpdater_ChunkPos = Vector3I_MaxValue(); - - if (MapRenderer_Chunks && World_Blocks) { - ChunkUpdater_ClearChunkCache(); - ChunkUpdater_ResetChunkCache(); - - oldCount = MapRenderer_1DUsedCount; - MapRenderer_1DUsedCount = Atlas1D_UsedAtlasesCount(); - /* Need to reallocate parts array in this case */ - if (MapRenderer_1DUsedCount != oldCount) { - ChunkUpdater_FreePartsAllocations(); - ChunkUpdater_PerformPartsAllocations(); - } - } - ChunkUpdater_ResetPartCounts(); -} -static void ChunkUpdater_Refresh_Handler(void* obj) { - ChunkUpdater_Refresh(); -} - -void ChunkUpdater_RefreshBorders(int clipLevel) { - int cx, cy, cz; - bool onBorder; - - ChunkUpdater_ChunkPos = Vector3I_MaxValue(); - if (!MapRenderer_Chunks || !World_Blocks) return; - - for (cz = 0; cz < MapRenderer_ChunksZ; cz++) { - for (cy = 0; cy < MapRenderer_ChunksY; cy++) { - for (cx = 0; cx < MapRenderer_ChunksX; cx++) { - onBorder = cx == 0 || cz == 0 || cx == (MapRenderer_ChunksX - 1) || cz == (MapRenderer_ChunksZ - 1); - - if (onBorder && (cy * CHUNK_SIZE) < clipLevel) { - MapRenderer_RefreshChunk(cx, cy, cz); - } - } - } - } -} - - -void ChunkUpdater_ApplyMeshBuilder(void) { - if (Game_SmoothLighting) { - /* TODO: Implement advanced lighting builder.*/ - AdvBuilder_SetActive(); - } else { - NormalBuilder_SetActive(); - } -} - -static void ChunkUpdater_OnNewMap(void* obj) { - Game_ChunkUpdates = 0; - ChunkUpdater_ClearChunkCache(); - ChunkUpdater_ResetPartCounts(); - ChunkUpdater_FreeAllocations(); - ChunkUpdater_ChunkPos = Vector3I_MaxValue(); -} - -static void ChunkUpdater_OnNewMapLoaded(void* obj) { - int count; - MapRenderer_ChunksX = (World_Width + CHUNK_MAX) >> CHUNK_SHIFT; - MapRenderer_ChunksY = (World_Height + CHUNK_MAX) >> CHUNK_SHIFT; - MapRenderer_ChunksZ = (World_Length + CHUNK_MAX) >> CHUNK_SHIFT; - - count = MapRenderer_ChunksX * MapRenderer_ChunksY * MapRenderer_ChunksZ; - /* TODO: Only perform reallocation when map volume has changed */ - /*if (MapRenderer_ChunksCount != count) { */ - MapRenderer_ChunksCount = count; - ChunkUpdater_FreeAllocations(); - ChunkUpdater_PerformAllocations(); - /*}*/ - - ChunkUpdater_CreateChunkCache(); - Builder_OnNewMapLoaded(); - cu_lastCamPos = Vector3_BigPos(); -} - - -static int ChunkUpdater_AdjustViewDist(int dist) { - if (dist < CHUNK_SIZE) dist = CHUNK_SIZE; - dist = Utils_AdjViewDist(dist); - return (dist + 24) * (dist + 24); -} - -static int ChunkUpdater_UpdateChunksAndVisibility(int* chunkUpdates) { - int viewDistSqr = ChunkUpdater_AdjustViewDist(Game_ViewDistance); - int userDistSqr = ChunkUpdater_AdjustViewDist(Game_UserViewDistance); - - struct ChunkInfo* info; - int i, j = 0, distSqr; - bool noData; - - for (i = 0; i < MapRenderer_ChunksCount; i++) { - info = MapRenderer_SortedChunks[i]; - if (info->Empty) continue; - - distSqr = ChunkUpdater_Distances[i]; - noData = !info->NormalParts && !info->TranslucentParts; - - /* Unload chunks beyond visible range */ - if (!noData && distSqr >= userDistSqr + 32 * 16) { - ChunkUpdater_DeleteChunk(info); continue; - } - noData |= info->PendingDelete; - - if (noData && distSqr <= viewDistSqr && *chunkUpdates < cu_chunksTarget) { - ChunkUpdater_DeleteChunk(info); - ChunkUpdater_BuildChunk(info, chunkUpdates); - } - - info->Visible = distSqr <= viewDistSqr && - FrustumCulling_SphereInFrustum(info->CentreX, info->CentreY, info->CentreZ, 14); /* 14 ~ sqrt(3 * 8^2) */ - if (info->Visible && !info->Empty) { MapRenderer_RenderChunks[j] = info; j++; } - } - return j; -} - -static int ChunkUpdater_UpdateChunksStill(int* chunkUpdates) { - int viewDistSqr = ChunkUpdater_AdjustViewDist(Game_ViewDistance); - int userDistSqr = ChunkUpdater_AdjustViewDist(Game_UserViewDistance); - - struct ChunkInfo* info; - int i, j = 0, distSqr; - bool noData; - - for (i = 0; i < MapRenderer_ChunksCount; i++) { - info = MapRenderer_SortedChunks[i]; - if (info->Empty) continue; - - distSqr = ChunkUpdater_Distances[i]; - noData = !info->NormalParts && !info->TranslucentParts; - - /* Unload chunks beyond visible range */ - if (!noData && distSqr >= userDistSqr + 32 * 16) { - ChunkUpdater_DeleteChunk(info); continue; - } - noData |= info->PendingDelete; - - if (noData && distSqr <= userDistSqr && *chunkUpdates < cu_chunksTarget) { - ChunkUpdater_DeleteChunk(info); - ChunkUpdater_BuildChunk(info, chunkUpdates); - - /* only need to update the visibility of chunks in range. */ - info->Visible = distSqr <= viewDistSqr && - FrustumCulling_SphereInFrustum(info->CentreX, info->CentreY, info->CentreZ, 14); /* 14 ~ sqrt(3 * 8^2) */ - if (info->Visible && !info->Empty) { MapRenderer_RenderChunks[j] = info; j++; } - } else if (info->Visible) { - MapRenderer_RenderChunks[j] = info; j++; - } - } - return j; -} - -static void ChunkUpdater_UpdateChunks(double delta) { - struct LocalPlayer* p; - bool samePos; - int chunkUpdates = 0; - - /* Build more chunks if 30 FPS or over, otherwise slowdown */ - cu_chunksTarget += delta < cu_targetTime ? 1 : -1; - Math_Clamp(cu_chunksTarget, 4, Game_MaxChunkUpdates); - - p = &LocalPlayer_Instance; - samePos = Vector3_Equals(&Camera_CurrentPos, &cu_lastCamPos) - && p->Base.HeadX == cu_lastHeadX && p->Base.HeadY == cu_lastHeadY; - - MapRenderer_RenderChunksCount = samePos ? - ChunkUpdater_UpdateChunksStill(&chunkUpdates) : - ChunkUpdater_UpdateChunksAndVisibility(&chunkUpdates); - - cu_lastCamPos = Camera_CurrentPos; - cu_lastHeadX = p->Base.HeadX; - cu_lastHeadY = p->Base.HeadY; - - if (!samePos || chunkUpdates) { - ChunkUpdater_ResetPartFlags(); - } -} - - -void ChunkUpdater_ResetPartFlags(void) { - int i; - for (i = 0; i < ATLAS1D_MAX_ATLASES; i++) { - MapRenderer_CheckingNormalParts[i] = true; - MapRenderer_HasNormalParts[i] = false; - MapRenderer_CheckingTranslucentParts[i] = true; - MapRenderer_HasTranslucentParts[i] = false; - } -} - -void ChunkUpdater_ResetPartCounts(void) { - int i; - for (i = 0; i < ATLAS1D_MAX_ATLASES; i++) { - MapRenderer_NormalPartsCount[i] = 0; - MapRenderer_TranslucentPartsCount[i] = 0; - } -} - -void ChunkUpdater_CreateChunkCache(void) { - int x, y, z, index = 0; - for (z = 0; z < World_Length; z += CHUNK_SIZE) { - for (y = 0; y < World_Height; y += CHUNK_SIZE) { - for (x = 0; x < World_Width; x += CHUNK_SIZE) { - ChunkInfo_Reset(&MapRenderer_Chunks[index], x, y, z); - MapRenderer_SortedChunks[index] = &MapRenderer_Chunks[index]; - MapRenderer_RenderChunks[index] = &MapRenderer_Chunks[index]; - ChunkUpdater_Distances[index] = 0; - index++; - } - } - } -} - -void ChunkUpdater_ResetChunkCache(void) { - int x, y, z, index = 0; - for (z = 0; z < World_Length; z += CHUNK_SIZE) { - for (y = 0; y < World_Height; y += CHUNK_SIZE) { - for (x = 0; x < World_Width; x += CHUNK_SIZE) { - ChunkInfo_Reset(&MapRenderer_Chunks[index], x, y, z); - index++; - } - } - } -} - -void ChunkUpdater_ClearChunkCache(void) { - int i; - if (!MapRenderer_Chunks) return; - - for (i = 0; i < MapRenderer_ChunksCount; i++) { - ChunkUpdater_DeleteChunk(&MapRenderer_Chunks[i]); - } - ChunkUpdater_ResetPartCounts(); -} -static void ChunkUpdater_ClearChunkCache_Handler(void* obj) { - ChunkUpdater_ClearChunkCache(); -} - - -void ChunkUpdater_DeleteChunk(struct ChunkInfo* info) { - struct ChunkPartInfo* ptr; - int i; - - info->Empty = false; info->AllAir = false; -#ifdef OCCLUSION - info.OcclusionFlags = 0; - info.OccludedFlags = 0; -#endif -#ifndef CC_BUILD_GL11 - Gfx_DeleteVb(&info->Vb); -#endif - - if (info->NormalParts) { - ptr = info->NormalParts; - for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += MapRenderer_ChunksCount) { - if (ptr->Offset < 0) continue; - MapRenderer_NormalPartsCount[i]--; -#ifdef CC_BUILD_GL11 - Gfx_DeleteVb(&ptr->Vb); -#endif - } - info->NormalParts = NULL; - } - - if (info->TranslucentParts) { - ptr = info->TranslucentParts; - for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += MapRenderer_ChunksCount) { - if (ptr->Offset < 0) continue; - MapRenderer_TranslucentPartsCount[i]--; -#ifdef CC_BUILD_GL11 - Gfx_DeleteVb(&ptr->Vb); -#endif - } - info->TranslucentParts = NULL; - } -} - -void ChunkUpdater_BuildChunk(struct ChunkInfo* info, int* chunkUpdates) { - struct ChunkPartInfo* ptr; - int i; - - Game_ChunkUpdates++; - (*chunkUpdates)++; - info->PendingDelete = false; - Builder_MakeChunk(info); - - if (!info->NormalParts && !info->TranslucentParts) { - info->Empty = true; return; - } - - if (info->NormalParts) { - ptr = info->NormalParts; - for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += MapRenderer_ChunksCount) { - if (ptr->Offset >= 0) { MapRenderer_NormalPartsCount[i]++; } - } - } - - if (info->TranslucentParts) { - ptr = info->TranslucentParts; - for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += MapRenderer_ChunksCount) { - if (ptr->Offset >= 0) { MapRenderer_TranslucentPartsCount[i]++; } - } - } -} - -static void ChunkUpdater_QuickSort(int left, int right) { - struct ChunkInfo** values = MapRenderer_SortedChunks; struct ChunkInfo* value; - uint32_t* keys = ChunkUpdater_Distances; uint32_t key; - - while (left < right) { - int i = left, j = right; - uint32_t pivot = keys[(i + j) >> 1]; - - /* partition the list */ - while (i <= j) { - while (pivot > keys[i]) i++; - while (pivot < keys[j]) j--; - QuickSort_Swap_KV_Maybe(); - } - /* recurse into the smaller subset */ - QuickSort_Recurse(ChunkUpdater_QuickSort) - } -} - -static void ChunkUpdater_UpdateSortOrder(void) { - struct ChunkInfo* info; - Vector3I pos; - int dXMin, dXMax, dYMin, dYMax, dZMin, dZMax; - int i, dx, dy, dz; - - /* pos is centre coordinate of chunk camera is in */ - Vector3I_Floor(&pos, &Camera_CurrentPos); - pos.X = (pos.X & ~CHUNK_MASK) + HALF_CHUNK_SIZE; - pos.Y = (pos.Y & ~CHUNK_MASK) + HALF_CHUNK_SIZE; - pos.Z = (pos.Z & ~CHUNK_MASK) + HALF_CHUNK_SIZE; - - /* If in same chunk, don't need to recalculate sort order */ - if (Vector3I_Equals(&pos, &ChunkUpdater_ChunkPos)) return; - ChunkUpdater_ChunkPos = pos; - if (!MapRenderer_ChunksCount) return; - - for (i = 0; i < MapRenderer_ChunksCount; i++) { - info = MapRenderer_SortedChunks[i]; - /* Calculate distance to chunk centre */ - dx = info->CentreX - pos.X; dy = info->CentreY - pos.Y; dz = info->CentreZ - pos.Z; - ChunkUpdater_Distances[i] = dx * dx + dy * dy + dz * dz; - - /* Can work out distance to chunk faces as offset from distance to chunk centre on each axis */ - dXMin = dx - HALF_CHUNK_SIZE; dXMax = dx + HALF_CHUNK_SIZE; - dYMin = dy - HALF_CHUNK_SIZE; dYMax = dy + HALF_CHUNK_SIZE; - dZMin = dz - HALF_CHUNK_SIZE; dZMax = dz + HALF_CHUNK_SIZE; - - /* Back face culling: make sure that the chunk is definitely entirely back facing */ - info->DrawXMin = !(dXMin <= 0 && dXMax <= 0); - info->DrawXMax = !(dXMin >= 0 && dXMax >= 0); - info->DrawZMin = !(dZMin <= 0 && dZMax <= 0); - info->DrawZMax = !(dZMin >= 0 && dZMax >= 0); - info->DrawYMin = !(dYMin <= 0 && dYMax <= 0); - info->DrawYMax = !(dYMin >= 0 && dYMax >= 0); - } - - ChunkUpdater_QuickSort(0, MapRenderer_ChunksCount - 1); - ChunkUpdater_ResetPartFlags(); - /*SimpleOcclusionCulling();*/ -} - -void ChunkUpdater_Update(double deltaTime) { - if (!MapRenderer_Chunks) return; - ChunkUpdater_UpdateSortOrder(); - ChunkUpdater_UpdateChunks(deltaTime); -} - -void ChunkUpdater_Init(void) { - Event_RegisterVoid(&TextureEvents_AtlasChanged, NULL, ChunkUpdater_TerrainAtlasChanged); - Event_RegisterVoid(&WorldEvents_NewMap, NULL, ChunkUpdater_OnNewMap); - Event_RegisterVoid(&WorldEvents_MapLoaded, NULL, ChunkUpdater_OnNewMapLoaded); - Event_RegisterInt(&WorldEvents_EnvVarChanged, NULL, ChunkUpdater_EnvVariableChanged); - - Event_RegisterVoid(&BlockEvents_BlockDefChanged, NULL, ChunkUpdater_BlockDefinitionChanged); - Event_RegisterVoid(&GfxEvents_ViewDistanceChanged, NULL, ChunkUpdater_ViewDistanceChanged); - Event_RegisterVoid(&GfxEvents_ProjectionChanged, NULL, ChunkUpdater_ProjectionChanged); - Event_RegisterVoid(&GfxEvents_ContextLost, NULL, ChunkUpdater_ClearChunkCache_Handler); - Event_RegisterVoid(&GfxEvents_ContextRecreated, NULL, ChunkUpdater_Refresh_Handler); - - /* This = 87 fixes map being invisible when no textures */ - MapRenderer_1DUsedCount = 87; /* Atlas1D_UsedAtlasesCount(); */ - ChunkUpdater_ChunkPos = Vector3I_MaxValue(); - ChunkUpdater_ApplyMeshBuilder(); -} - -void ChunkUpdater_Free(void) { - Event_UnregisterVoid(&TextureEvents_AtlasChanged, NULL, ChunkUpdater_TerrainAtlasChanged); - Event_UnregisterVoid(&WorldEvents_NewMap, NULL, ChunkUpdater_OnNewMap); - Event_UnregisterVoid(&WorldEvents_MapLoaded, NULL, ChunkUpdater_OnNewMapLoaded); - Event_UnregisterInt(&WorldEvents_EnvVarChanged, NULL, ChunkUpdater_EnvVariableChanged); - - Event_UnregisterVoid(&BlockEvents_BlockDefChanged, NULL, ChunkUpdater_BlockDefinitionChanged); - Event_UnregisterVoid(&GfxEvents_ViewDistanceChanged, NULL, ChunkUpdater_ViewDistanceChanged); - Event_UnregisterVoid(&GfxEvents_ProjectionChanged, NULL, ChunkUpdater_ProjectionChanged); - Event_UnregisterVoid(&GfxEvents_ContextLost, NULL, ChunkUpdater_ClearChunkCache_Handler); - Event_UnregisterVoid(&GfxEvents_ContextRecreated, NULL, ChunkUpdater_Refresh_Handler); - - ChunkUpdater_OnNewMap(NULL); -} diff --git a/src/ChunkUpdater.h b/src/ChunkUpdater.h deleted file mode 100644 index 0c67ac017..000000000 --- a/src/ChunkUpdater.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef CC_CHUNKUPDATER_H -#define CC_CHUNKUPDATER_H -#include "Core.h" -#include "Constants.h" -/* Manages the process of building/deleting chunk meshes. - Also sorts chunks so nearest chunks are ordered first, and calculates chunk visibility. - Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -*/ - -/* Describes a portion of the data needed for rendering a chunk. */ -struct ChunkPartInfo { -#ifdef CC_BUILD_GL11 - GfxResourceID Vb; -#endif - int Offset; /* -1 if no vertices at all */ - int SpriteCount; /* Sprite vertices count */ - uint16_t Counts[FACE_COUNT]; /* Counts per face */ -}; - -/* Describes data necessary for rendering a chunk. */ -struct ChunkInfo { - uint16_t CentreX, CentreY, CentreZ; /* Centre coordinates of the chunk */ - - uint8_t Visible : 1; /* Whether chunk is visibile to the player */ - uint8_t Empty : 1; /* Whether the chunk is empty of data */ - uint8_t PendingDelete : 1; /* Whether chunk is pending deletion*/ - uint8_t AllAir : 1; /* Whether chunk is completely air */ - uint8_t : 0; /* pad to next byte*/ - - uint8_t DrawXMin : 1; - uint8_t DrawXMax : 1; - uint8_t DrawZMin : 1; - uint8_t DrawZMax : 1; - uint8_t DrawYMin : 1; - uint8_t DrawYMax : 1; - uint8_t : 0; /* pad to next byte */ -#ifdef OCCLUSION - public bool Visited = false, Occluded = false; - public byte OcclusionFlags, OccludedFlags, DistanceFlags; -#endif -#ifndef CC_BUILD_GL11 - GfxResourceID Vb; -#endif - struct ChunkPartInfo* NormalParts; - struct ChunkPartInfo* TranslucentParts; -}; - -void ChunkInfo_Reset(struct ChunkInfo* chunk, int x, int y, int z); - -void ChunkUpdater_Init(void); -void ChunkUpdater_Free(void); -void ChunkUpdater_Refresh(void); -void ChunkUpdater_RefreshBorders(int clipLevel); -void ChunkUpdater_ApplyMeshBuilder(void); -void ChunkUpdater_Update(double deltaTime); - -void ChunkUpdater_ResetPartFlags(void); -void ChunkUpdater_ResetPartCounts(void); -void ChunkUpdater_CreateChunkCache(void); -void ChunkUpdater_ResetChunkCache(void); -void ChunkUpdater_ClearChunkCache(void); - -void ChunkUpdater_DeleteChunk(struct ChunkInfo* info); -void ChunkUpdater_BuildChunk(struct ChunkInfo* info, int* chunkUpdates); -#endif diff --git a/src/ClassiCube.vcxproj b/src/ClassiCube.vcxproj index d18bec6d2..c0f290b7f 100644 --- a/src/ClassiCube.vcxproj +++ b/src/ClassiCube.vcxproj @@ -195,7 +195,6 @@ - @@ -259,7 +258,6 @@ - diff --git a/src/ClassiCube.vcxproj.filters b/src/ClassiCube.vcxproj.filters index 0814f759a..faba2819b 100644 --- a/src/ClassiCube.vcxproj.filters +++ b/src/ClassiCube.vcxproj.filters @@ -297,9 +297,6 @@ Header Files\Entities - - Header Files\Rendering - Header Files\Rendering @@ -467,9 +464,6 @@ Source Files\Entities - - Source Files\Rendering - Source Files\Rendering diff --git a/src/Game.c b/src/Game.c index 0143e9e08..351fbadb5 100644 --- a/src/Game.c +++ b/src/Game.c @@ -34,6 +34,7 @@ #include "Menus.h" #include "Audio.h" #include "Stream.h" +#include "TerrainAtlas.h" static struct IGameComponent Game_Components[26]; static int Game_ComponentsCount; @@ -321,7 +322,7 @@ static void Game_OnLowVRAMDetected(void* obj) { Game_UserViewDistance /= 2; Game_UserViewDistance = max(16, Game_UserViewDistance); - ChunkUpdater_Refresh(); + MapRenderer_Refresh(); Game_SetViewDistance(Game_UserViewDistance); Chat_AddRaw("&cOut of VRAM! Halving view distance.."); } @@ -478,7 +479,7 @@ void Game_Load(void) { LocalPlayer_MakeComponent(&comp); Game_AddComponent(&comp); Entities_List[ENTITIES_SELF_ID] = &LocalPlayer_Instance.Base; - ChunkUpdater_Init(); + MapRenderer_Init(); EnvRenderer_MakeComponent(&comp); Game_AddComponent(&comp); String_InitArray(renderType, renderTypeBuffer); Options_Get(OPT_RENDER_TYPE, &renderType, "normal"); @@ -591,7 +592,7 @@ static void Game_Render3D(double delta, float t) { EnvRenderer_RenderSky(delta); EnvRenderer_RenderClouds(delta); - ChunkUpdater_Update(delta); + MapRenderer_Update(delta); MapRenderer_RenderNormal(delta); EnvRenderer_RenderMapSides(delta); @@ -724,7 +725,7 @@ static void Game_RenderFrame(double delta) { void Game_Free(void* obj) { int i; - ChunkUpdater_Free(); + MapRenderer_Free(); Atlas2D_Free(); Atlas1D_Free(); ModelCache_Free(); diff --git a/src/GameStructs.h b/src/GameStructs.h index 3adcbf2ac..a25d10d2c 100644 --- a/src/GameStructs.h +++ b/src/GameStructs.h @@ -9,12 +9,12 @@ struct IGameComponent { /* Called when the game is being loaded. */ void (*Init)(void); - /* Called when the component is being freed, due to game being closed. */ + /* Called when the component is being freed. (e.g. due to game being closed) */ void (*Free)(void); - /* Called when the texture pack has been loaded and all components have been initalised. */ - void (*Ready)(void); - /* Called to reset the component's state when the user is reconnecting to a server */ + /* Called to reset the component's state. (e.g. reconnecting to server) */ void (*Reset)(void); + /* Called when the texture pack has been loaded and all components have been initialised. */ + void (*Ready)(void); /* Called to update the component's state when the user begins loading a new map. */ void (*OnNewMap)(void); /* Called to update the component's state when the user has finished loading a new map. */ diff --git a/src/MapRenderer.c b/src/MapRenderer.c index 7d34b1517..b7b2bbb7c 100644 --- a/src/MapRenderer.c +++ b/src/MapRenderer.c @@ -1,28 +1,71 @@ #include "MapRenderer.h" #include "Block.h" +#include "Builder.h" +#include "Camera.h" +#include "Entity.h" +#include "EnvRenderer.h" +#include "Event.h" +#include "ExtMath.h" +#include "Funcs.h" #include "Game.h" #include "Graphics.h" -#include "EnvRenderer.h" +#include "Platform.h" +#include "TerrainAtlas.h" +#include "Utils.h" #include "World.h" -#include "Camera.h" -#include "ChunkUpdater.h" + static bool inTranslucent; +static int elementsPerBitmap; +static Vector3I chunkPos; + +/* The number of non-empty Normal/Translucent ChunkPartInfos (across entire world) for each 1D atlas batch. */ +/* 1D atlas batches that do not have any ChunkPartInfos can be entirely skipped. */ +static int normPartsCount[ATLAS1D_MAX_ATLASES], tranPartsCount[ATLAS1D_MAX_ATLASES]; +/* Whether there are any visible Normal/Translucent ChunkPartInfos for each 1D atlas batch. */ +/* 1D atlas batches that do not have any visible ChunkPartInfos can be skipped. */ +static bool hasNormParts[ATLAS1D_MAX_ATLASES], hasTranParts[ATLAS1D_MAX_ATLASES]; +/* Whether renderer should check if there are any visible Normal/Translucent ChunkPartInfos for each 1D atlas batch. */ +static bool checkNormParts[ATLAS1D_MAX_ATLASES], checkTranParts[ATLAS1D_MAX_ATLASES]; + +/* Render info for all chunks in the world. Unsorted. */ +static struct ChunkInfo* mapChunks; +/* Pointers to render info for all chunks in the world, sorted by distance from the camera. */ +static struct ChunkInfo** sortedChunks; +/* Pointers to render info for all chunks in the world, sorted by distance from the camera. */ +/* Only chunks that can be rendered (i.e. not empty and are visible) are included in this. */ +static struct ChunkInfo** renderChunks; +/* Number of actually used pointers in the renderChunks array. Entries past this are ignored and skipped. */ +static int renderChunksCount; +/* Distance of each chunk from the camera. */ +static uint32_t* distances; + +/* Buffer for all chunk parts. There are (MapRenderer_ChunksCount * Atlas1D_Count) * 2 parts in the buffer, + with parts for 'normal' buffer being in lower half. */ +static struct ChunkPartInfo* partsBuffer_Raw; struct ChunkInfo* MapRenderer_GetChunk(int cx, int cy, int cz) { - return &MapRenderer_Chunks[MapRenderer_Pack(cx, cy, cz)]; + return &mapChunks[MapRenderer_Pack(cx, cy, cz)]; } -void MapRenderer_RefreshChunk(int cx, int cy, int cz) { - struct ChunkInfo* info; - if (cx < 0 || cy < 0 || cz < 0 || cx >= MapRenderer_ChunksX - || cy >= MapRenderer_ChunksY || cz >= MapRenderer_ChunksZ) return; +void ChunkInfo_Reset(struct ChunkInfo* chunk, int x, int y, int z) { + chunk->CentreX = x + 8; chunk->CentreY = y + 8; chunk->CentreZ = z + 8; +#ifndef CC_BUILD_GL11 + chunk->Vb = GFX_NULL; +#endif - info = &MapRenderer_Chunks[MapRenderer_Pack(cx, cy, cz)]; - if (info->AllAir) return; /* do not recreate chunks completely air */ - info->Empty = false; - info->PendingDelete = true; + chunk->Visible = true; chunk->Empty = false; + chunk->PendingDelete = false; chunk->AllAir = false; + chunk->DrawXMin = false; chunk->DrawXMax = false; chunk->DrawZMin = false; + chunk->DrawZMax = false; chunk->DrawYMin = false; chunk->DrawYMax = false; + + chunk->NormalParts = NULL; + chunk->TranslucentParts = NULL; } + +/*########################################################################################################################* +*-------------------------------------------------------Map rendering-----------------------------------------------------* +*#########################################################################################################################*/ static void MapRenderer_CheckWeather(double delta) { Vector3I pos; BlockID block; @@ -61,13 +104,13 @@ static void MapRenderer_RenderNormalBatch(int batch) { bool drawMin, drawMax; int i, offset, count; - for (i = 0; i < MapRenderer_RenderChunksCount; i++) { - info = MapRenderer_RenderChunks[i]; + for (i = 0; i < renderChunksCount; i++) { + info = renderChunks[i]; if (!info->NormalParts) continue; part = info->NormalParts[batchOffset]; if (part.Offset < 0) continue; - MapRenderer_HasNormalParts[batch] = true; + hasNormParts[batch] = true; #ifndef CC_BUILD_GL11 Gfx_BindVb(info->Vb); @@ -116,7 +159,7 @@ static void MapRenderer_RenderNormalBatch(int batch) { void MapRenderer_RenderNormal(double delta) { int batch; - if (!MapRenderer_Chunks) return; + if (!mapChunks) return; Gfx_SetBatchFormat(VERTEX_FORMAT_P3FT2FC4B); Gfx_SetTexturing(true); @@ -124,11 +167,11 @@ void MapRenderer_RenderNormal(double delta) { Gfx_EnableMipmaps(); for (batch = 0; batch < MapRenderer_1DUsedCount; batch++) { - if (MapRenderer_NormalPartsCount[batch] <= 0) continue; - if (MapRenderer_HasNormalParts[batch] || MapRenderer_CheckingNormalParts[batch]) { + if (normPartsCount[batch] <= 0) continue; + if (hasNormParts[batch] || checkNormParts[batch]) { Gfx_BindTexture(Atlas1D_TexIds[batch]); MapRenderer_RenderNormalBatch(batch); - MapRenderer_CheckingNormalParts[batch] = false; + checkNormParts[batch] = false; } } Gfx_DisableMipmaps(); @@ -160,13 +203,13 @@ static void MapRenderer_RenderTranslucentBatch(int batch) { bool drawMin, drawMax; int i, offset; - for (i = 0; i < MapRenderer_RenderChunksCount; i++) { - info = MapRenderer_RenderChunks[i]; + for (i = 0; i < renderChunksCount; i++) { + info = renderChunks[i]; if (!info->TranslucentParts) continue; part = info->TranslucentParts[batchOffset]; if (part.Offset < 0) continue; - MapRenderer_HasTranslucentParts[batch] = true; + hasTranParts[batch] = true; #ifndef CC_BUILD_GL11 Gfx_BindVb(info->Vb); @@ -193,7 +236,7 @@ static void MapRenderer_RenderTranslucentBatch(int batch) { void MapRenderer_RenderTranslucent(double delta) { int vertices, batch; - if (!MapRenderer_Chunks) return; + if (!mapChunks) return; /* First fill depth buffer */ vertices = Game_Vertices; @@ -203,10 +246,10 @@ void MapRenderer_RenderTranslucent(double delta) { Gfx_SetColWriteMask(false, false, false, false); for (batch = 0; batch < MapRenderer_1DUsedCount; batch++) { - if (MapRenderer_TranslucentPartsCount[batch] <= 0) continue; - if (MapRenderer_HasTranslucentParts[batch] || MapRenderer_CheckingTranslucentParts[batch]) { + if (tranPartsCount[batch] <= 0) continue; + if (hasTranParts[batch] || checkTranParts[batch]) { MapRenderer_RenderTranslucentBatch(batch); - MapRenderer_CheckingTranslucentParts[batch] = false; + checkTranParts[batch] = false; } } Game_Vertices = vertices; @@ -219,8 +262,8 @@ void MapRenderer_RenderTranslucent(double delta) { Gfx_EnableMipmaps(); for (batch = 0; batch < MapRenderer_1DUsedCount; batch++) { - if (MapRenderer_TranslucentPartsCount[batch] <= 0) continue; - if (!MapRenderer_HasTranslucentParts[batch]) continue; + if (tranPartsCount[batch] <= 0) continue; + if (!hasTranParts[batch]) continue; Gfx_BindTexture(Atlas1D_TexIds[batch]); MapRenderer_RenderTranslucentBatch(batch); } @@ -236,3 +279,498 @@ void MapRenderer_RenderTranslucent(double delta) { Gfx_SetAlphaBlending(false); Gfx_SetTexturing(false); } + + +/*########################################################################################################################* +*----------------------------------------------------Chunks mangagement---------------------------------------------------* +*#########################################################################################################################*/ +static void MapRenderer_FreeParts(void) { + Mem_Free(partsBuffer_Raw); + partsBuffer_Raw = NULL; + MapRenderer_PartsNormal = NULL; + MapRenderer_PartsTranslucent = NULL; +} + +static void MapRenderer_FreeChunks(void) { + Mem_Free(mapChunks); + Mem_Free(sortedChunks); + Mem_Free(renderChunks); + Mem_Free(distances); + + mapChunks = NULL; + sortedChunks = NULL; + renderChunks = NULL; + distances = NULL; +} + +static void MapRenderer_AllocateParts(void) { + uint32_t count = MapRenderer_ChunksCount * MapRenderer_1DUsedCount; + partsBuffer_Raw = Mem_AllocCleared(count * 2, sizeof(struct ChunkPartInfo), "chunk parts"); + MapRenderer_PartsNormal = partsBuffer_Raw; + MapRenderer_PartsTranslucent = partsBuffer_Raw + count; +} + +static void MapRenderer_AllocateChunks(void) { + mapChunks = Mem_Alloc(MapRenderer_ChunksCount, sizeof(struct ChunkInfo), "chunk info"); + sortedChunks = Mem_Alloc(MapRenderer_ChunksCount, sizeof(struct ChunkInfo*), "sorted chunk info"); + renderChunks = Mem_Alloc(MapRenderer_ChunksCount, sizeof(struct ChunkInfo*), "render chunk info"); + distances = Mem_Alloc(MapRenderer_ChunksCount, 4, "chunk distances"); +} + +static void MapRenderer_ResetPartFlags(void) { + int i; + for (i = 0; i < ATLAS1D_MAX_ATLASES; i++) { + checkNormParts[i] = true; + hasNormParts[i] = false; + checkTranParts[i] = true; + hasTranParts[i] = false; + } +} + +static void MapRenderer_ResetPartCounts(void) { + int i; + for (i = 0; i < ATLAS1D_MAX_ATLASES; i++) { + normPartsCount[i] = 0; + tranPartsCount[i] = 0; + } +} + +static void MapRenderer_InitChunks(void) { + int x, y, z, index = 0; + for (z = 0; z < World_Length; z += CHUNK_SIZE) { + for (y = 0; y < World_Height; y += CHUNK_SIZE) { + for (x = 0; x < World_Width; x += CHUNK_SIZE) { + ChunkInfo_Reset(&mapChunks[index], x, y, z); + sortedChunks[index] = &mapChunks[index]; + renderChunks[index] = &mapChunks[index]; + distances[index] = 0; + index++; + } + } + } +} + +static void MapRenderer_ResetChunks(void) { + int x, y, z, index = 0; + for (z = 0; z < World_Length; z += CHUNK_SIZE) { + for (y = 0; y < World_Height; y += CHUNK_SIZE) { + for (x = 0; x < World_Width; x += CHUNK_SIZE) { + ChunkInfo_Reset(&mapChunks[index], x, y, z); + index++; + } + } + } +} + +static void MapRenderer_DeleteChunks(void) { + int i; + if (!mapChunks) return; + + for (i = 0; i < MapRenderer_ChunksCount; i++) { + MapRenderer_DeleteChunk(&mapChunks[i]); + } + MapRenderer_ResetPartCounts(); +} + +void MapRenderer_Refresh(void) { + int oldCount; + chunkPos = Vector3I_MaxValue(); + + if (mapChunks && World_Blocks) { + MapRenderer_DeleteChunks(); + MapRenderer_ResetChunks(); + + oldCount = MapRenderer_1DUsedCount; + MapRenderer_1DUsedCount = Atlas1D_UsedAtlasesCount(); + /* Need to reallocate parts array in this case */ + if (MapRenderer_1DUsedCount != oldCount) { + MapRenderer_FreeParts(); + MapRenderer_AllocateParts(); + } + } + MapRenderer_ResetPartCounts(); +} + +void MapRenderer_RefreshBorders(int maxHeight) { + int cx, cy, cz; + bool onBorder; + + chunkPos = Vector3I_MaxValue(); + if (!mapChunks || !World_Blocks) return; + + for (cz = 0; cz < MapRenderer_ChunksZ; cz++) { + for (cy = 0; cy < MapRenderer_ChunksY; cy++) { + for (cx = 0; cx < MapRenderer_ChunksX; cx++) { + onBorder = cx == 0 || cz == 0 || cx == (MapRenderer_ChunksX - 1) || cz == (MapRenderer_ChunksZ - 1); + + if (onBorder && (cy * CHUNK_SIZE) < maxHeight) { + MapRenderer_RefreshChunk(cx, cy, cz); + } + } + } + } +} + + +/*########################################################################################################################* +*--------------------------------------------------Chunks updating/sorting------------------------------------------------* +*#########################################################################################################################*/ +#define CHUNK_TARGET_TIME ((1.0/30) + 0.01) +static int chunksTarget = 12; +static Vector3 lastCamPos; +static float lastHeadY, lastHeadX; + +static int MapRenderer_AdjustViewDist(int dist) { + if (dist < CHUNK_SIZE) dist = CHUNK_SIZE; + dist = Utils_AdjViewDist(dist); + return (dist + 24) * (dist + 24); +} + +static int MapRenderer_UpdateChunksAndVisibility(int* chunkUpdates) { + int viewDistSqr = MapRenderer_AdjustViewDist(Game_ViewDistance); + int userDistSqr = MapRenderer_AdjustViewDist(Game_UserViewDistance); + + struct ChunkInfo* info; + int i, j = 0, distSqr; + bool noData; + + for (i = 0; i < MapRenderer_ChunksCount; i++) { + info = sortedChunks[i]; + if (info->Empty) continue; + + distSqr = distances[i]; + noData = !info->NormalParts && !info->TranslucentParts; + + /* Unload chunks beyond visible range */ + if (!noData && distSqr >= userDistSqr + 32 * 16) { + MapRenderer_DeleteChunk(info); continue; + } + noData |= info->PendingDelete; + + if (noData && distSqr <= viewDistSqr && *chunkUpdates < chunksTarget) { + MapRenderer_DeleteChunk(info); + MapRenderer_BuildChunk(info, chunkUpdates); + } + + info->Visible = distSqr <= viewDistSqr && + FrustumCulling_SphereInFrustum(info->CentreX, info->CentreY, info->CentreZ, 14); /* 14 ~ sqrt(3 * 8^2) */ + if (info->Visible && !info->Empty) { renderChunks[j] = info; j++; } + } + return j; +} + +static int MapRenderer_UpdateChunksStill(int* chunkUpdates) { + int viewDistSqr = MapRenderer_AdjustViewDist(Game_ViewDistance); + int userDistSqr = MapRenderer_AdjustViewDist(Game_UserViewDistance); + + struct ChunkInfo* info; + int i, j = 0, distSqr; + bool noData; + + for (i = 0; i < MapRenderer_ChunksCount; i++) { + info = sortedChunks[i]; + if (info->Empty) continue; + + distSqr = distances[i]; + noData = !info->NormalParts && !info->TranslucentParts; + + /* Unload chunks beyond visible range */ + if (!noData && distSqr >= userDistSqr + 32 * 16) { + MapRenderer_DeleteChunk(info); continue; + } + noData |= info->PendingDelete; + + if (noData && distSqr <= userDistSqr && *chunkUpdates < chunksTarget) { + MapRenderer_DeleteChunk(info); + MapRenderer_BuildChunk(info, chunkUpdates); + + /* only need to update the visibility of chunks in range. */ + info->Visible = distSqr <= viewDistSqr && + FrustumCulling_SphereInFrustum(info->CentreX, info->CentreY, info->CentreZ, 14); /* 14 ~ sqrt(3 * 8^2) */ + if (info->Visible && !info->Empty) { renderChunks[j] = info; j++; } + } else if (info->Visible) { + renderChunks[j] = info; j++; + } + } + return j; +} + +static void MapRenderer_UpdateChunks(double delta) { + struct LocalPlayer* p; + bool samePos; + int chunkUpdates = 0; + + /* Build more chunks if 30 FPS or over, otherwise slowdown */ + chunksTarget += delta < CHUNK_TARGET_TIME ? 1 : -1; + Math_Clamp(chunksTarget, 4, Game_MaxChunkUpdates); + + p = &LocalPlayer_Instance; + samePos = Vector3_Equals(&Camera_CurrentPos, &lastCamPos) + && p->Base.HeadX == lastHeadX && p->Base.HeadY == lastHeadY; + + renderChunksCount = samePos ? + MapRenderer_UpdateChunksStill(&chunkUpdates) : + MapRenderer_UpdateChunksAndVisibility(&chunkUpdates); + + lastCamPos = Camera_CurrentPos; + lastHeadX = p->Base.HeadX; + lastHeadY = p->Base.HeadY; + + if (!samePos || chunkUpdates) { + MapRenderer_ResetPartFlags(); + } +} + +static void MapRenderer_QuickSort(int left, int right) { + struct ChunkInfo** values = sortedChunks; struct ChunkInfo* value; + uint32_t* keys = distances; uint32_t key; + + while (left < right) { + int i = left, j = right; + uint32_t pivot = keys[(i + j) >> 1]; + + /* partition the list */ + while (i <= j) { + while (pivot > keys[i]) i++; + while (pivot < keys[j]) j--; + QuickSort_Swap_KV_Maybe(); + } + /* recurse into the smaller subset */ + QuickSort_Recurse(MapRenderer_QuickSort) + } +} + +static void MapRenderer_UpdateSortOrder(void) { + struct ChunkInfo* info; + Vector3I pos; + int dXMin, dXMax, dYMin, dYMax, dZMin, dZMax; + int i, dx, dy, dz; + + /* pos is centre coordinate of chunk camera is in */ + Vector3I_Floor(&pos, &Camera_CurrentPos); + pos.X = (pos.X & ~CHUNK_MASK) + HALF_CHUNK_SIZE; + pos.Y = (pos.Y & ~CHUNK_MASK) + HALF_CHUNK_SIZE; + pos.Z = (pos.Z & ~CHUNK_MASK) + HALF_CHUNK_SIZE; + + /* If in same chunk, don't need to recalculate sort order */ + if (Vector3I_Equals(&pos, &chunkPos)) return; + chunkPos = pos; + if (!MapRenderer_ChunksCount) return; + + for (i = 0; i < MapRenderer_ChunksCount; i++) { + info = sortedChunks[i]; + /* Calculate distance to chunk centre */ + dx = info->CentreX - pos.X; dy = info->CentreY - pos.Y; dz = info->CentreZ - pos.Z; + distances[i] = dx * dx + dy * dy + dz * dz; + + /* Can work out distance to chunk faces as offset from distance to chunk centre on each axis */ + dXMin = dx - HALF_CHUNK_SIZE; dXMax = dx + HALF_CHUNK_SIZE; + dYMin = dy - HALF_CHUNK_SIZE; dYMax = dy + HALF_CHUNK_SIZE; + dZMin = dz - HALF_CHUNK_SIZE; dZMax = dz + HALF_CHUNK_SIZE; + + /* Back face culling: make sure that the chunk is definitely entirely back facing */ + info->DrawXMin = !(dXMin <= 0 && dXMax <= 0); + info->DrawXMax = !(dXMin >= 0 && dXMax >= 0); + info->DrawZMin = !(dZMin <= 0 && dZMax <= 0); + info->DrawZMax = !(dZMin >= 0 && dZMax >= 0); + info->DrawYMin = !(dYMin <= 0 && dYMax <= 0); + info->DrawYMax = !(dYMin >= 0 && dYMax >= 0); + } + + MapRenderer_QuickSort(0, MapRenderer_ChunksCount - 1); + MapRenderer_ResetPartFlags(); + /*SimpleOcclusionCulling();*/ +} + +void MapRenderer_Update(double deltaTime) { + if (!mapChunks) return; + MapRenderer_UpdateSortOrder(); + MapRenderer_UpdateChunks(deltaTime); +} + + +/*########################################################################################################################* +*---------------------------------------------------------General---------------------------------------------------------* +*#########################################################################################################################*/ +void MapRenderer_RefreshChunk(int cx, int cy, int cz) { + struct ChunkInfo* info; + if (cx < 0 || cy < 0 || cz < 0 || cx >= MapRenderer_ChunksX + || cy >= MapRenderer_ChunksY || cz >= MapRenderer_ChunksZ) return; + + info = &mapChunks[MapRenderer_Pack(cx, cy, cz)]; + if (info->AllAir) return; /* do not recreate chunks completely air */ + info->Empty = false; + info->PendingDelete = true; +} + +void MapRenderer_DeleteChunk(struct ChunkInfo* info) { + struct ChunkPartInfo* ptr; + int i; + + info->Empty = false; info->AllAir = false; +#ifdef OCCLUSION + info.OcclusionFlags = 0; + info.OccludedFlags = 0; +#endif +#ifndef CC_BUILD_GL11 + Gfx_DeleteVb(&info->Vb); +#endif + + if (info->NormalParts) { + ptr = info->NormalParts; + for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += MapRenderer_ChunksCount) { + if (ptr->Offset < 0) continue; + normPartsCount[i]--; +#ifdef CC_BUILD_GL11 + Gfx_DeleteVb(&ptr->Vb); +#endif + } + info->NormalParts = NULL; + } + + if (info->TranslucentParts) { + ptr = info->TranslucentParts; + for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += MapRenderer_ChunksCount) { + if (ptr->Offset < 0) continue; + tranPartsCount[i]--; +#ifdef CC_BUILD_GL11 + Gfx_DeleteVb(&ptr->Vb); +#endif + } + info->TranslucentParts = NULL; + } +} + +void MapRenderer_BuildChunk(struct ChunkInfo* info, int* chunkUpdates) { + struct ChunkPartInfo* ptr; + int i; + + Game_ChunkUpdates++; + (*chunkUpdates)++; + info->PendingDelete = false; + Builder_MakeChunk(info); + + if (!info->NormalParts && !info->TranslucentParts) { + info->Empty = true; return; + } + + if (info->NormalParts) { + ptr = info->NormalParts; + for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += MapRenderer_ChunksCount) { + if (ptr->Offset >= 0) normPartsCount[i]++; + } + } + + if (info->TranslucentParts) { + ptr = info->TranslucentParts; + for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += MapRenderer_ChunksCount) { + if (ptr->Offset >= 0) tranPartsCount[i]++; + } + } +} + +static void MapRenderer_EnvVariableChanged(void* obj, int envVar) { + if (envVar == ENV_VAR_SUN_COL || envVar == ENV_VAR_SHADOW_COL) { + MapRenderer_Refresh(); + } else if (envVar == ENV_VAR_EDGE_HEIGHT || envVar == ENV_VAR_SIDES_OFFSET) { + int oldClip = Builder_EdgeLevel; + Builder_SidesLevel = max(0, Env_SidesHeight); + Builder_EdgeLevel = max(0, Env_EdgeHeight); + + /* Only need to refresh chunks on map borders up to highest edge level.*/ + MapRenderer_RefreshBorders(max(oldClip, Builder_EdgeLevel)); + } +} + +static void MapRenderer_TerrainAtlasChanged(void* obj) { + if (MapRenderer_1DUsedCount) { + bool refreshRequired = elementsPerBitmap != Atlas1D_TilesPerAtlas; + if (refreshRequired) MapRenderer_Refresh(); + } + + MapRenderer_1DUsedCount = Atlas1D_UsedAtlasesCount(); + elementsPerBitmap = Atlas1D_TilesPerAtlas; + MapRenderer_ResetPartFlags(); +} + +static void MapRenderer_BlockDefinitionChanged(void* obj) { + MapRenderer_Refresh(); + MapRenderer_1DUsedCount = Atlas1D_UsedAtlasesCount(); + MapRenderer_ResetPartFlags(); +} + +static void MapRenderer_RecalcVisibility_(void* obj) { lastCamPos = Vector3_BigPos(); } +static void MapRenderer_DeleteChunks_(void* obj) { MapRenderer_DeleteChunks(); } +static void MapRenderer_Refresh_(void* obj) { MapRenderer_Refresh(); } + +void MapRenderer_ApplyMeshBuilder(void) { + if (Game_SmoothLighting) { + AdvBuilder_SetActive(); + } else { + NormalBuilder_SetActive(); + } +} + +static void MapRenderer_OnNewMap(void* obj) { + Game_ChunkUpdates = 0; + MapRenderer_DeleteChunks(); + MapRenderer_ResetPartCounts(); + + chunkPos = Vector3I_MaxValue(); + MapRenderer_FreeChunks(); + MapRenderer_FreeParts(); +} + +static void MapRenderer_OnNewMapLoaded(void* obj) { + int count; + MapRenderer_ChunksX = (World_Width + CHUNK_MAX) >> CHUNK_SHIFT; + MapRenderer_ChunksY = (World_Height + CHUNK_MAX) >> CHUNK_SHIFT; + MapRenderer_ChunksZ = (World_Length + CHUNK_MAX) >> CHUNK_SHIFT; + + count = MapRenderer_ChunksX * MapRenderer_ChunksY * MapRenderer_ChunksZ; + /* TODO: Only perform reallocation when map volume has changed */ + /*if (MapRenderer_ChunksCount != count) { */ + MapRenderer_ChunksCount = count; + MapRenderer_FreeChunks(); + MapRenderer_FreeParts(); + MapRenderer_AllocateChunks(); + MapRenderer_AllocateParts(); + /*}*/ + + MapRenderer_InitChunks(); + Builder_OnNewMapLoaded(); + lastCamPos = Vector3_BigPos(); +} + +void MapRenderer_Init(void) { + Event_RegisterVoid(&TextureEvents_AtlasChanged, NULL, MapRenderer_TerrainAtlasChanged); + Event_RegisterVoid(&WorldEvents_NewMap, NULL, MapRenderer_OnNewMap); + Event_RegisterVoid(&WorldEvents_MapLoaded, NULL, MapRenderer_OnNewMapLoaded); + Event_RegisterInt(&WorldEvents_EnvVarChanged, NULL, MapRenderer_EnvVariableChanged); + + Event_RegisterVoid(&BlockEvents_BlockDefChanged, NULL, MapRenderer_BlockDefinitionChanged); + Event_RegisterVoid(&GfxEvents_ViewDistanceChanged, NULL, MapRenderer_RecalcVisibility_); + Event_RegisterVoid(&GfxEvents_ProjectionChanged, NULL, MapRenderer_RecalcVisibility_); + Event_RegisterVoid(&GfxEvents_ContextLost, NULL, MapRenderer_DeleteChunks_); + Event_RegisterVoid(&GfxEvents_ContextRecreated, NULL, MapRenderer_Refresh_); + + /* This = 87 fixes map being invisible when no textures */ + MapRenderer_1DUsedCount = 87; /* Atlas1D_UsedAtlasesCount(); */ + chunkPos = Vector3I_MaxValue(); + MapRenderer_ApplyMeshBuilder(); +} + +void MapRenderer_Free(void) { + Event_UnregisterVoid(&TextureEvents_AtlasChanged, NULL, MapRenderer_TerrainAtlasChanged); + Event_UnregisterVoid(&WorldEvents_NewMap, NULL, MapRenderer_OnNewMap); + Event_UnregisterVoid(&WorldEvents_MapLoaded, NULL, MapRenderer_OnNewMapLoaded); + Event_UnregisterInt(&WorldEvents_EnvVarChanged, NULL, MapRenderer_EnvVariableChanged); + + Event_UnregisterVoid(&BlockEvents_BlockDefChanged, NULL, MapRenderer_BlockDefinitionChanged); + Event_UnregisterVoid(&GfxEvents_ViewDistanceChanged, NULL, MapRenderer_RecalcVisibility_); + Event_UnregisterVoid(&GfxEvents_ProjectionChanged, NULL, MapRenderer_RecalcVisibility_); + Event_UnregisterVoid(&GfxEvents_ContextLost, NULL, MapRenderer_DeleteChunks_); + Event_UnregisterVoid(&GfxEvents_ContextRecreated, NULL, MapRenderer_Refresh_); + + MapRenderer_OnNewMap(NULL); +} diff --git a/src/MapRenderer.h b/src/MapRenderer.h index 2c033d116..063d32e45 100644 --- a/src/MapRenderer.h +++ b/src/MapRenderer.h @@ -1,8 +1,10 @@ #ifndef CC_MAPRENDERER_H #define CC_MAPRENDERER_H -#include "TerrainAtlas.h" -#include "ChunkUpdater.h" +#include "Core.h" +#include "Constants.h" /* Renders the blocks of the world by subdividing it into chunks. + Also manages the process of building/deleting chunk meshes. + Also sorts chunks so nearest chunks are rendered first, and calculates chunk visibility. Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 */ @@ -10,45 +12,84 @@ int MapRenderer_ChunksX, MapRenderer_ChunksY, MapRenderer_ChunksZ; #define MapRenderer_Pack(cx, cy, cz) (((cz) * MapRenderer_ChunksY + (cy)) * MapRenderer_ChunksX + (cx)) /* TODO: Swap Y and Z? Make sure to update ChunkUpdater's ResetChunkCache and ClearChunkCache methods! */ -/* The count of actual used 1D atlases. (i.e. 1DIndex(maxTextureLoc) + 1 */ +/* Count of actual used 1D atlases. (i.e. 1DIndex(maxTextureLoc) + 1 */ int MapRenderer_1DUsedCount; -/* The number of non-empty Normal ChunkPartInfos (across entire world) for each 1D atlas batch. -1D atlas batches that do not have any ChunkPartInfos can be entirely skipped. */ -int MapRenderer_NormalPartsCount[ATLAS1D_MAX_ATLASES]; -/* The number of non-empty Translucent ChunkPartInfos (across entire world) for each 1D atlas batch. -1D atlas batches that do not have any ChunkPartInfos can be entirely skipped. */ -int MapRenderer_TranslucentPartsCount[ATLAS1D_MAX_ATLASES]; -/* Whether there are any visible Translucent ChunkPartInfos for each 1D atlas batch. -1D atlas batches that do not have any visible translucent ChunkPartInfos can be skipped. */ -bool MapRenderer_HasTranslucentParts[ATLAS1D_MAX_ATLASES]; -/* Whether there are any visible Normal ChunkPartInfos for each 1D atlas batch. -1D atlas batches that do not have any visible normal ChunkPartInfos can be skipped. */ -bool MapRenderer_HasNormalParts[ATLAS1D_MAX_ATLASES]; -/* Whether renderer should check if there are any visible Translucent ChunkPartInfos for each 1D atlas batch. */ -bool MapRenderer_CheckingTranslucentParts[ATLAS1D_MAX_ATLASES]; -/* Whether renderer should check if there are any visible Normal ChunkPartInfos for each 1D atlas batch. */ -bool MapRenderer_CheckingNormalParts[ATLAS1D_MAX_ATLASES]; - -/* Render info for all chunks in the world. Unsorted.*/ -struct ChunkInfo* MapRenderer_Chunks; -/* The number of chunks in the world, or ChunksX * ChunksY * ChunksZ */ +/* Number of chunks in the world, or ChunksX * ChunksY * ChunksZ */ int MapRenderer_ChunksCount; -/* Pointers to render info for all chunks in the world, sorted by distance from the camera. */ -struct ChunkInfo** MapRenderer_SortedChunks; -/* Pointers to render info for all chunks in the world, sorted by distance from the camera. -Chunks that can be rendered (not empty and are visible) are included in this array. */ -struct ChunkInfo** MapRenderer_RenderChunks; -/* The number of actually used pointers in the RenderChunks array. -Entries past this count should be ignored and skipped. */ -int MapRenderer_RenderChunksCount; -/* Buffer for all chunk parts. There are (MapRenderer_ChunksCount * Atlas1D_Count) * 2 parts in the buffer, - with parts for 'normal' buffer being in lower half. */ -struct ChunkPartInfo* MapRenderer_PartsBuffer_Raw; -struct ChunkPartInfo* MapRenderer_PartsNormal; + +/* Buffer for all chunk parts. There are (MapRenderer_ChunksCount * Atlas1D_Count) parts in the buffer, +with parts for 'normal' buffer being in lower half. */ +struct ChunkPartInfo* MapRenderer_PartsNormal; /* TODO: THAT DESC SUCKS */ struct ChunkPartInfo* MapRenderer_PartsTranslucent; -struct ChunkInfo* MapRenderer_GetChunk(int cx, int cy, int cz); -void MapRenderer_RefreshChunk(int cx, int cy, int cz); -void MapRenderer_RenderNormal(double delta); -void MapRenderer_RenderTranslucent(double delta); +/* Describes a portion of the data needed for rendering a chunk. */ +struct ChunkPartInfo { +#ifdef CC_BUILD_GL11 + GfxResourceID Vb; +#endif + int Offset; /* -1 if no vertices at all */ + int SpriteCount; /* Sprite vertices count */ + uint16_t Counts[FACE_COUNT]; /* Counts per face */ +}; + +/* Describes data necessary for rendering a chunk. */ +struct ChunkInfo { + uint16_t CentreX, CentreY, CentreZ; /* Centre coordinates of the chunk */ + + uint8_t Visible : 1; /* Whether chunk is visibile to the player */ + uint8_t Empty : 1; /* Whether the chunk is empty of data */ + uint8_t PendingDelete : 1; /* Whether chunk is pending deletion*/ + uint8_t AllAir : 1; /* Whether chunk is completely air */ + uint8_t : 0; /* pad to next byte*/ + + uint8_t DrawXMin : 1; + uint8_t DrawXMax : 1; + uint8_t DrawZMin : 1; + uint8_t DrawZMax : 1; + uint8_t DrawYMin : 1; + uint8_t DrawYMax : 1; + uint8_t : 0; /* pad to next byte */ +#ifdef OCCLUSION + public bool Visited = false, Occluded = false; + public byte OcclusionFlags, OccludedFlags, DistanceFlags; +#endif +#ifndef CC_BUILD_GL11 + GfxResourceID Vb; +#endif + struct ChunkPartInfo* NormalParts; + struct ChunkPartInfo* TranslucentParts; +}; + +void ChunkInfo_Reset(struct ChunkInfo* chunk, int x, int y, int z); +/* Gets the chunk at the given chunk coordinates in the world. */ +/* NOTE: Does NOT check coordinates are within bounds. */ +struct ChunkInfo* MapRenderer_GetChunk(int cx, int cy, int cz); +/* Renders the meshes of non-translucent blocks in visible chunks. */ +void MapRenderer_RenderNormal(double delta); +/* Renders the meshes of translucent blocks in visible chunks. */ +void MapRenderer_RenderTranslucent(double delta); +/* Potentially updates sort order of rendered chunks. */ +/* Potentially builds meshes for several nearby chunks. */ +/* NOTE: This should be called once per frame. */ +void MapRenderer_Update(double deltaTime); + +/* Marks the given chunk as needing to be rebuilt/redrawn. */ +/* NOTE: Coordinates outside the map are simply ignored. */ +void MapRenderer_RefreshChunk(int cx, int cy, int cz); +/* Deletes the vertex buffer associated with the given chunk. */ +/* NOTE: This method also adjusts internal state, so do not bypass this. */ +void MapRenderer_DeleteChunk(struct ChunkInfo* info); +/* Builds the mesh (and hence vertex buffer) for the given chunk. */ +/* NOTE: This method also adjusts internal state, so do not bypass this. */ +void MapRenderer_BuildChunk(struct ChunkInfo* info, int* chunkUpdates); + +/* Refreshes chunks on the border of the map. */ +/* NOTE: Only refreshes chunks whose y is less than 'maxHeight'. */ +void MapRenderer_RefreshBorders(int maxHeight); +/* Deletes all chunks and resets internal state. */ +void MapRenderer_Refresh(void); + +void MapRenderer_Init(void); +void MapRenderer_Free(void); +void MapRenderer_ApplyMeshBuilder(void); #endif diff --git a/src/Menus.c b/src/Menus.c index f1c142cf3..ccbfb503f 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -2393,8 +2393,8 @@ static void GraphicsOptionsScreen_SetViewDist(const String* v) { Game_UserSetVie static void GraphicsOptionsScreen_GetSmooth(String* v) { Menu_GetBool(v, Game_SmoothLighting); } static void GraphicsOptionsScreen_SetSmooth(const String* v) { Game_SmoothLighting = Menu_SetBool(v, OPT_SMOOTH_LIGHTING); - ChunkUpdater_ApplyMeshBuilder(); - ChunkUpdater_Refresh(); + MapRenderer_ApplyMeshBuilder(); + MapRenderer_Refresh(); } static void GraphicsOptionsScreen_GetNames(String* v) { String_AppendConst(v, NameMode_Names[Entities_NameMode]); } diff --git a/src/String.h b/src/String.h index b87d6551f..17edfa95f 100644 --- a/src/String.h +++ b/src/String.h @@ -75,7 +75,7 @@ CC_NOINLINE bool String_CaselessEquals(const String* a, const String* b); CC_NOINLINE bool String_CaselessEqualsConst(const String* a, const char* b); /* Breaks down an integer into an array of digits. */ /* NOTE: Digits are in reverse order, so e.g. '200' becomes '0','0','2' */ -CC_NOINLINE int String_MakeUInt32(uint32_t num, char* numBuffer); +CC_NOINLINE int String_MakeUInt32(uint32_t num, char* digits); /* Appends a character to the end of a string. */ /* Returns false when str->length == str->capcity, true otherwise. */ diff --git a/src/Window.c b/src/Window.c index 00253a158..244bd6cd9 100644 --- a/src/Window.c +++ b/src/Window.c @@ -989,7 +989,7 @@ int Window_GetWindowState(void) { return WINDOW_STATE_NORMAL; } -void Window_SendNetWMState(long op, Atom a1, Atom a2) { +static void Window_SendNetWMState(long op, Atom a1, Atom a2) { XEvent ev = { 0 }; ev.xclient.type = ClientMessage; ev.xclient.send_event = true; @@ -1087,7 +1087,7 @@ void Window_Destroy(void) { Window_Exists = false; } -void Window_ToggleKey(XKeyEvent* keyEvent, bool pressed) { +static void Window_ToggleKey(XKeyEvent* keyEvent, bool pressed) { KeySym keysym1 = XLookupKeysym(keyEvent, 0); KeySym keysym2 = XLookupKeysym(keyEvent, 1); @@ -1096,7 +1096,7 @@ void Window_ToggleKey(XKeyEvent* keyEvent, bool pressed) { if (key != KEY_NONE) Key_SetPressed(key, pressed); } -Atom Window_GetSelectionProperty(XEvent* e) { +static Atom Window_GetSelectionProperty(XEvent* e) { Atom prop = e->xselectionrequest.property; if (prop) return prop; @@ -1104,7 +1104,7 @@ Atom Window_GetSelectionProperty(XEvent* e) { return e->xselectionrequest.target; } -bool Window_GetPendingEvent(XEvent* e) { +static bool Window_GetPendingEvent(XEvent* e) { return XCheckWindowEvent(win_display, win_handle, win_eventMask, e) || XCheckTypedWindowEvent(win_display, win_handle, ClientMessage, e) || XCheckTypedWindowEvent(win_display, win_handle, SelectionNotify, e) ||