Combine ChunkUpdater and MapRenderer

This commit is contained in:
UnknownShadow200 2018-11-23 22:51:26 +11:00
parent 989a504cf6
commit 5fbd10b331
12 changed files with 668 additions and 685 deletions

View file

@ -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];
}

View file

@ -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);
}

View file

@ -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

View file

@ -195,7 +195,6 @@
<ClInclude Include="Builder.h" />
<ClInclude Include="Camera.h" />
<ClInclude Include="Chat.h" />
<ClInclude Include="ChunkUpdater.h" />
<ClInclude Include="Constants.h" />
<ClInclude Include="Bitmap.h" />
<ClInclude Include="Drawer.h" />
@ -259,7 +258,6 @@
<ClCompile Include="Block.c" />
<ClCompile Include="Builder.c" />
<ClCompile Include="Chat.c" />
<ClCompile Include="ChunkUpdater.c" />
<ClCompile Include="Bitmap.c" />
<ClCompile Include="Drawer.c" />
<ClCompile Include="Drawer2D.c" />

View file

@ -297,9 +297,6 @@
<ClInclude Include="Model.h">
<Filter>Header Files\Entities</Filter>
</ClInclude>
<ClInclude Include="ChunkUpdater.h">
<Filter>Header Files\Rendering</Filter>
</ClInclude>
<ClInclude Include="MapRenderer.h">
<Filter>Header Files\Rendering</Filter>
</ClInclude>
@ -467,9 +464,6 @@
<ClCompile Include="Model.c">
<Filter>Source Files\Entities</Filter>
</ClCompile>
<ClCompile Include="ChunkUpdater.c">
<Filter>Source Files\Rendering</Filter>
</ClCompile>
<ClCompile Include="MapRenderer.c">
<Filter>Source Files\Rendering</Filter>
</ClCompile>

View file

@ -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();

View file

@ -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. */

View file

@ -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);
}

View file

@ -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

View file

@ -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]); }

View file

@ -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. */

View file

@ -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) ||