diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/MapRenderer.c |
initial commit
Diffstat (limited to 'src/MapRenderer.c')
-rw-r--r-- | src/MapRenderer.c | 809 |
1 files changed, 809 insertions, 0 deletions
diff --git a/src/MapRenderer.c b/src/MapRenderer.c new file mode 100644 index 0000000..d09e7d2 --- /dev/null +++ b/src/MapRenderer.c @@ -0,0 +1,809 @@ +#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 "Platform.h" +#include "TexturePack.h" +#include "Utils.h" +#include "World.h" +#include "Options.h" + +int MapRenderer_1DUsedCount; +struct ChunkPartInfo* MapRenderer_PartsNormal; +struct ChunkPartInfo* MapRenderer_PartsTranslucent; + +static cc_bool inTranslucent; +static IVec3 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 cc_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 cc_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 cc_uint32* distances; +/* Maximum number of chunk updates that can be performed in one frame. */ +static int maxChunkUpdates; +/* Cached number of chunks in the world */ +static int chunksCount; + +static void ChunkInfo_Reset(struct ChunkInfo* chunk, int x, int y, int z) { + chunk->centreX = x + HALF_CHUNK_SIZE; chunk->centreY = y + HALF_CHUNK_SIZE; + chunk->centreZ = z + HALF_CHUNK_SIZE; +#ifndef CC_BUILD_GL11 + chunk->vb = 0; +#endif + + chunk->visible = true; + chunk->empty = false; + chunk->dirty = false; + chunk->allAir = false; + chunk->noData = true; + + chunk->drawXMin = false; chunk->drawXMax = false; chunk->drawZMin = false; + chunk->drawZMax = false; chunk->drawYMin = false; chunk->drawYMax = false; + + chunk->normalParts = NULL; + chunk->translucentParts = NULL; +} + +/* Index of maximum used 1D atlas + 1 */ +CC_NOINLINE static int MapRenderer_UsedAtlases(void) { + TextureLoc maxLoc = 0; + int i; + + for (i = 0; i < Array_Elems(Blocks.Textures); i++) { + maxLoc = max(maxLoc, Blocks.Textures[i]); + } + return Atlas1D_Index(maxLoc) + 1; +} + + +/*########################################################################################################################* +*-------------------------------------------------------Map rendering-----------------------------------------------------* +*#########################################################################################################################*/ +static void CheckWeather(float delta) { + IVec3 pos; + BlockID block; + cc_bool outside; + IVec3_Floor(&pos, &Camera.CurrentPos); + + block = World_SafeGetBlock(pos.x, pos.y, pos.z); + outside = pos.y < 0 || !World_ContainsXZ(pos.x, pos.z); + inTranslucent = Blocks.Draw[block] == DRAW_TRANSLUCENT || (pos.y < Env.EdgeHeight && outside); + + /* If we are under water, render weather before to blend properly */ + if (!inTranslucent || Env.Weather == WEATHER_SUNNY) return; + Gfx_SetAlphaBlending(true); + EnvRenderer_RenderWeather(delta); + Gfx_SetAlphaBlending(false); +} + +#ifdef CC_BUILD_GL11 +#define DrawFace(face, ign) Gfx_BindVb(part.vbs[face]); Gfx_DrawIndexedTris_T2fC4b(0, 0); +#define DrawFaces(f1, f2, ign) DrawFace(f1, ign); DrawFace(f2, ign); +#else +#define DrawFace(face, offset) Gfx_DrawIndexedTris_T2fC4b(part.counts[face], offset); +#define DrawFaces(f1, f2, offset) Gfx_DrawIndexedTris_T2fC4b(part.counts[f1] + part.counts[f2], offset); +#endif + +#define DrawNormalFaces(minFace, maxFace) \ +if (drawMin && drawMax) { \ + Gfx_SetFaceCulling(true); \ + DrawFaces(minFace, maxFace, offset); \ + Gfx_SetFaceCulling(false); \ + Game_Vertices += (part.counts[minFace] + part.counts[maxFace]); \ +} else if (drawMin) { \ + DrawFace(minFace, offset); \ + Game_Vertices += part.counts[minFace]; \ +} else if (drawMax) { \ + DrawFace(maxFace, offset + part.counts[minFace]); \ + Game_Vertices += part.counts[maxFace]; \ +} + +static void RenderNormalBatch(int batch) { + int batchOffset = chunksCount * batch; + struct ChunkInfo* info; + struct ChunkPartInfo part; + cc_bool drawMin, drawMax; + int i, offset, count; + + for (i = 0; i < renderChunksCount; i++) { + info = renderChunks[i]; + if (!info->normalParts) continue; + + part = info->normalParts[batchOffset]; + if (part.offset < 0) continue; + hasNormParts[batch] = true; + +#ifndef CC_BUILD_GL11 + Gfx_BindVb_Textured(info->vb); +#endif + + offset = part.offset + part.spriteCount; + drawMin = info->drawXMin && part.counts[FACE_XMIN]; + drawMax = info->drawXMax && part.counts[FACE_XMAX]; + DrawNormalFaces(FACE_XMIN, FACE_XMAX); + + offset += part.counts[FACE_XMIN] + part.counts[FACE_XMAX]; + drawMin = info->drawZMin && part.counts[FACE_ZMIN]; + drawMax = info->drawZMax && part.counts[FACE_ZMAX]; + DrawNormalFaces(FACE_ZMIN, FACE_ZMAX); + + offset += part.counts[FACE_ZMIN] + part.counts[FACE_ZMAX]; + drawMin = info->drawYMin && part.counts[FACE_YMIN]; + drawMax = info->drawYMax && part.counts[FACE_YMAX]; + DrawNormalFaces(FACE_YMIN, FACE_YMAX); + + if (!part.spriteCount) continue; + offset = part.offset; + count = part.spriteCount >> 2; /* 4 per sprite */ + + Gfx_SetFaceCulling(true); + /* TODO: fix to not render them all */ +#ifdef CC_BUILD_GL11 + Gfx_BindVb(part.vbs[FACE_COUNT]); + Gfx_DrawIndexedTris_T2fC4b(0, 0); + Game_Vertices += count * 4; + Gfx_SetFaceCulling(false); + continue; +#endif + if (info->drawXMax || info->drawZMin) { + Gfx_DrawIndexedTris_T2fC4b(count, offset); Game_Vertices += count; + } offset += count; + + if (info->drawXMin || info->drawZMax) { + Gfx_DrawIndexedTris_T2fC4b(count, offset); Game_Vertices += count; + } offset += count; + + if (info->drawXMin || info->drawZMin) { + Gfx_DrawIndexedTris_T2fC4b(count, offset); Game_Vertices += count; + } offset += count; + + if (info->drawXMax || info->drawZMax) { + Gfx_DrawIndexedTris_T2fC4b(count, offset); Game_Vertices += count; + } + Gfx_SetFaceCulling(false); + } +} + +void MapRenderer_RenderNormal(float delta) { + int batch; + if (!mapChunks) return; + + Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED); + Gfx_SetAlphaTest(true); + + Gfx_EnableMipmaps(); + for (batch = 0; batch < MapRenderer_1DUsedCount; batch++) { + if (normPartsCount[batch] <= 0) continue; + if (hasNormParts[batch] || checkNormParts[batch]) { + Gfx_BindTexture(Atlas1D.TexIds[batch]); + RenderNormalBatch(batch); + checkNormParts[batch] = false; + } + } + Gfx_DisableMipmaps(); + + CheckWeather(delta); + Gfx_SetAlphaTest(false); +#if DEBUG_OCCLUSION + DebugPickedPos(); +#endif +} + +#define DrawTranslucentFaces(minFace, maxFace) \ +if (drawMin && drawMax) { \ + DrawFaces(minFace, maxFace, offset); \ + Game_Vertices += (part.counts[minFace] + part.counts[maxFace]); \ +} else if (drawMin) { \ + DrawFace(minFace, offset); \ + Game_Vertices += part.counts[minFace]; \ +} else if (drawMax) { \ + DrawFace(maxFace, offset + part.counts[minFace]); \ + Game_Vertices += part.counts[maxFace]; \ +} + +static void RenderTranslucentBatch(int batch) { + int batchOffset = chunksCount * batch; + struct ChunkInfo* info; + struct ChunkPartInfo part; + cc_bool drawMin, drawMax; + int i, offset; + + for (i = 0; i < renderChunksCount; i++) { + info = renderChunks[i]; + if (!info->translucentParts) continue; + + part = info->translucentParts[batchOffset]; + if (part.offset < 0) continue; + hasTranParts[batch] = true; + +#ifndef CC_BUILD_GL11 + Gfx_BindVb_Textured(info->vb); +#endif + + offset = part.offset; + drawMin = (inTranslucent || info->drawXMin) && part.counts[FACE_XMIN]; + drawMax = (inTranslucent || info->drawXMax) && part.counts[FACE_XMAX]; + DrawTranslucentFaces(FACE_XMIN, FACE_XMAX); + + offset += part.counts[FACE_XMIN] + part.counts[FACE_XMAX]; + drawMin = (inTranslucent || info->drawZMin) && part.counts[FACE_ZMIN]; + drawMax = (inTranslucent || info->drawZMax) && part.counts[FACE_ZMAX]; + DrawTranslucentFaces(FACE_ZMIN, FACE_ZMAX); + + offset += part.counts[FACE_ZMIN] + part.counts[FACE_ZMAX]; + drawMin = (inTranslucent || info->drawYMin) && part.counts[FACE_YMIN]; + drawMax = (inTranslucent || info->drawYMax) && part.counts[FACE_YMAX]; + DrawTranslucentFaces(FACE_YMIN, FACE_YMAX); + } +} + +void MapRenderer_RenderTranslucent(float delta) { + int vertices, batch; + if (!mapChunks) return; + + /* First fill depth buffer */ + vertices = Game_Vertices; + Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED); + Gfx_SetAlphaBlending(false); + Gfx_DepthOnlyRendering(true); + + for (batch = 0; batch < MapRenderer_1DUsedCount; batch++) { + if (tranPartsCount[batch] <= 0) continue; + if (hasTranParts[batch] || checkTranParts[batch]) { + RenderTranslucentBatch(batch); + checkTranParts[batch] = false; + } + } + Game_Vertices = vertices; + + /* Then actually draw the transluscent blocks */ + Gfx_SetAlphaBlending(true); + Gfx_DepthOnlyRendering(false); + Gfx_SetDepthWrite(false); /* already calculated depth values in depth pass */ + + Gfx_EnableMipmaps(); + for (batch = 0; batch < MapRenderer_1DUsedCount; batch++) { + if (tranPartsCount[batch] <= 0) continue; + if (!hasTranParts[batch]) continue; + Gfx_BindTexture(Atlas1D.TexIds[batch]); + RenderTranslucentBatch(batch); + } + Gfx_DisableMipmaps(); + + Gfx_SetDepthWrite(true); + /* If we weren't under water, render weather after to blend properly */ + if (!inTranslucent && Env.Weather != WEATHER_SUNNY) { + Gfx_SetAlphaTest(true); + EnvRenderer_RenderWeather(delta); + Gfx_SetAlphaTest(false); + } + Gfx_SetAlphaBlending(false); +} + + +/*########################################################################################################################* +*---------------------------------------------------Chunk functionality---------------------------------------------------* +*#########################################################################################################################*/ +/* Deletes vertex buffer associated with the given chunk and updates internal state */ +static void DeleteChunk(struct ChunkInfo* info) { + struct ChunkPartInfo* ptr; + int i; +#ifdef CC_BUILD_GL11 + int j; +#else + Gfx_DeleteVb(&info->vb); +#endif + + info->empty = false; + info->allAir = false; + info->noData = true; +#ifdef OCCLUSION + info.OcclusionFlags = 0; + info.OccludedFlags = 0; +#endif + + if (info->normalParts) { + ptr = info->normalParts; + for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += chunksCount) { + if (ptr->offset < 0) continue; + normPartsCount[i]--; +#ifdef CC_BUILD_GL11 + for (j = 0; j < CHUNKPART_MAX_VBS; j++) Gfx_DeleteVb(&ptr->vbs[j]); +#endif + } + info->normalParts = NULL; + } + + if (info->translucentParts) { + ptr = info->translucentParts; + for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += chunksCount) { + if (ptr->offset < 0) continue; + tranPartsCount[i]--; +#ifdef CC_BUILD_GL11 + for (j = 0; j < CHUNKPART_MAX_VBS; j++) Gfx_DeleteVb(&ptr->vbs[j]); +#endif + } + info->translucentParts = NULL; + } +} + +/* Builds the mesh (hence vertex buffer) for the given chunk, and updates internal state */ +static void BuildChunk(struct ChunkInfo* info, int* chunkUpdates) { + struct ChunkPartInfo* ptr; + int i; + + Game.ChunkUpdates++; + (*chunkUpdates)++; + Builder_MakeChunk(info); + + info->dirty = false; + info->noData = !info->normalParts && !info->translucentParts; + info->empty = info->noData; + if (info->empty) return; + + if (info->normalParts) { + ptr = info->normalParts; + for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += chunksCount) { + if (ptr->offset >= 0) normPartsCount[i]++; + } + } + + if (info->translucentParts) { + ptr = info->translucentParts; + for (i = 0; i < MapRenderer_1DUsedCount; i++, ptr += chunksCount) { + if (ptr->offset >= 0) tranPartsCount[i]++; + } + } +} + + +/*########################################################################################################################* +*----------------------------------------------------Chunks mangagement---------------------------------------------------* +*#########################################################################################################################*/ +static void FreeParts(void) { + Mem_Free(MapRenderer_PartsNormal); + MapRenderer_PartsNormal = NULL; + MapRenderer_PartsTranslucent = NULL; +} + +static void FreeChunks(void) { + Mem_Free(mapChunks); + Mem_Free(sortedChunks); + Mem_Free(renderChunks); + Mem_Free(distances); + + mapChunks = NULL; + sortedChunks = NULL; + renderChunks = NULL; + distances = NULL; +} + +static void AllocateParts(void) { + struct ChunkPartInfo* ptr; + cc_uint32 count = chunksCount * MapRenderer_1DUsedCount; + + ptr = (struct ChunkPartInfo*)Mem_AllocCleared(count * 2, sizeof(struct ChunkPartInfo), "chunk parts"); + MapRenderer_PartsNormal = ptr; + MapRenderer_PartsTranslucent = ptr + count; +} + +static void AllocateChunks(void) { + mapChunks = (struct ChunkInfo*) Mem_Alloc(chunksCount, sizeof(struct ChunkInfo), "chunk info"); + sortedChunks = (struct ChunkInfo**)Mem_Alloc(chunksCount, sizeof(struct ChunkInfo*), "sorted chunk info"); + renderChunks = (struct ChunkInfo**)Mem_Alloc(chunksCount, sizeof(struct ChunkInfo*), "render chunk info"); + distances = (cc_uint32*)Mem_Alloc(chunksCount, 4, "chunk distances"); +} + +static void 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 ResetPartCounts(void) { + int i; + for (i = 0; i < ATLAS1D_MAX_ATLASES; i++) { + normPartsCount[i] = 0; + tranPartsCount[i] = 0; + } +} + +static void 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 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 DeleteChunks(void) { + int i; + if (!mapChunks) return; + + for (i = 0; i < chunksCount; i++) { + DeleteChunk(&mapChunks[i]); + } + ResetPartCounts(); +} + +void MapRenderer_Refresh(void) { + int oldCount; + chunkPos = IVec3_MaxValue(); + + if (mapChunks && World.Blocks) { + DeleteChunks(); + ResetChunks(); + + oldCount = MapRenderer_1DUsedCount; + MapRenderer_1DUsedCount = MapRenderer_UsedAtlases(); + /* Need to reallocate parts array in this case */ + if (MapRenderer_1DUsedCount != oldCount) { + FreeParts(); + AllocateParts(); + } + } + ResetPartCounts(); +} + +/* Refreshes chunks on the border of the map whose y is less than 'maxHeight'. */ +static void RefreshBorderChunks(int maxHeight) { + int cx, cy, cz; + cc_bool onBorder; + + chunkPos = IVec3_MaxValue(); + if (!mapChunks || !World.Blocks) return; + + for (cz = 0; cz < World.ChunksZ; cz++) { + for (cy = 0; cy < World.ChunksY; cy++) { + for (cx = 0; cx < World.ChunksX; cx++) { + onBorder = cx == 0 || cz == 0 || cx == (World.ChunksX - 1) || cz == (World.ChunksZ - 1); + + if (onBorder && (cy * CHUNK_SIZE) < maxHeight) { + MapRenderer_RefreshChunk(cx, cy, cz); + } + } + } + } +} + + +/*########################################################################################################################* +*--------------------------------------------------Chunks updating/sorting------------------------------------------------* +*#########################################################################################################################*/ +#define CHUNK_TARGET_TIME ((1.0f/30) + 0.01f) +static int chunksTarget = 12; +static Vec3 lastCamPos; +static float lastYaw, lastPitch; +/* Max distance from camera that chunks are rendered within */ +/* This may differ from the view distance configured by the user */ +static int renderDistSquared; +/* Max distance from camera that chunks are built within */ +/* Chunks past this distance are automatically unloaded */ +static int buildDistSquared; + +static int AdjustDist(int dist) { + if (dist < CHUNK_SIZE) dist = CHUNK_SIZE; + dist = Utils_AdjViewDist(dist); + return (dist + 24) * (dist + 24); +} + +static void CalcViewDists(void) { + buildDistSquared = AdjustDist(Game_UserViewDistance); + renderDistSquared = AdjustDist(Game_ViewDistance); +} + +static int UpdateChunksAndVisibility(int* chunkUpdates) { + int renderDistSqr = renderDistSquared; + int buildDistSqr = buildDistSquared; + + struct ChunkInfo* info; + int i, j = 0, distSqr; + cc_bool noData; + + for (i = 0; i < chunksCount; i++) { + info = sortedChunks[i]; + if (info->empty) continue; + + distSqr = distances[i]; + noData = info->noData; + + /* Auto unload chunks far away chunks */ + if (!noData && distSqr >= buildDistSqr + 32 * 16) { + DeleteChunk(info); continue; + } + noData |= info->dirty; + + if (noData && distSqr <= buildDistSqr && *chunkUpdates < chunksTarget) { + DeleteChunk(info); + BuildChunk(info, chunkUpdates); + } + + info->visible = distSqr <= renderDistSqr && + 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 UpdateChunksStill(int* chunkUpdates) { + int renderDistSqr = renderDistSquared; + int buildDistSqr = buildDistSquared; + + struct ChunkInfo* info; + int i, j = 0, distSqr; + cc_bool noData; + + for (i = 0; i < chunksCount; i++) { + info = sortedChunks[i]; + if (info->empty) continue; + + distSqr = distances[i]; + noData = info->noData; + + /* Auto unload chunks far away chunks */ + if (!noData && distSqr >= buildDistSqr + 32 * 16) { + DeleteChunk(info); continue; + } + noData |= info->dirty; + + if (noData && distSqr <= buildDistSqr && *chunkUpdates < chunksTarget) { + DeleteChunk(info); + BuildChunk(info, chunkUpdates); + + /* only need to update the visibility of chunks in range. */ + info->visible = distSqr <= renderDistSqr && + 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 UpdateChunks(float delta) { + struct LocalPlayer* p; + cc_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, maxChunkUpdates); + + p = Entities.CurPlayer; + samePos = Vec3_Equals(&Camera.CurrentPos, &lastCamPos) + && p->Base.Pitch == lastPitch && p->Base.Yaw == lastYaw; + + renderChunksCount = samePos ? + UpdateChunksStill(&chunkUpdates) : + UpdateChunksAndVisibility(&chunkUpdates); + + lastCamPos = Camera.CurrentPos; + lastPitch = p->Base.Pitch; + lastYaw = p->Base.Yaw; + + if (!samePos || chunkUpdates) ResetPartFlags(); +} + +static void SortMapChunks(int left, int right) { + struct ChunkInfo** values = sortedChunks; struct ChunkInfo* value; + cc_uint32* keys = distances; cc_uint32 key; + + while (left < right) { + int i = left, j = right; + cc_uint32 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(SortMapChunks) + } +} + +static void UpdateSortOrder(void) { + struct ChunkInfo* info; + IVec3 pos; + int i, dx, dy, dz; + + /* pos is centre coordinate of chunk camera is in */ + IVec3_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 (pos.x == chunkPos.x && pos.y == chunkPos.y && pos.z == chunkPos.z) return; + chunkPos = pos; + if (!chunksCount) return; + + for (i = 0; i < 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; + + /* Consider these 3 chunks: */ + /* | X-1 | X | X+1 | */ + /* |################|########@########|################| */ + /* Assume the player is standing at @, then DrawXMin/XMax is calculated as this */ + /* X-1: DrawXMin = false, DrawXMax = true */ + /* X : DrawXMin = true, DrawXMax = true */ + /* X+1: DrawXMin = true, DrawXMax = false */ + + info->drawXMin = dx >= 0; info->drawXMax = dx <= 0; + info->drawZMin = dz >= 0; info->drawZMax = dz <= 0; + info->drawYMin = dy >= 0; info->drawYMax = dy <= 0; + } + + SortMapChunks(0, chunksCount - 1); + ResetPartFlags(); + /*SimpleOcclusionCulling();*/ +} + +void MapRenderer_Update(float delta) { + if (!mapChunks) return; + UpdateSortOrder(); + UpdateChunks(delta); +} + + +/*########################################################################################################################* +*---------------------------------------------------------General---------------------------------------------------------* +*#########################################################################################################################*/ +void MapRenderer_RefreshChunk(int cx, int cy, int cz) { + struct ChunkInfo* info; + if (cx < 0 || cy < 0 || cz < 0 || cx >= World.ChunksX || cy >= World.ChunksY || cz >= World.ChunksZ) return; + + info = &mapChunks[World_ChunkPack(cx, cy, cz)]; + if (info->allAir) return; /* do not recreate chunks completely air */ + info->empty = false; + info->dirty = true; +} + +void MapRenderer_OnBlockChanged(int x, int y, int z, BlockID block) { + int cx = x >> CHUNK_SHIFT, cy = y >> CHUNK_SHIFT, cz = z >> CHUNK_SHIFT; + struct ChunkInfo* chunk; + + chunk = &mapChunks[World_ChunkPack(cx, cy, cz)]; + chunk->allAir &= Blocks.Draw[block] == DRAW_GAS; + /* TODO: Don't lookup twice, refresh directly using chunk pointer */ + MapRenderer_RefreshChunk(cx, cy, cz); +} + +static void OnEnvVariableChanged(void* obj, int envVar) { + if (envVar == ENV_VAR_SUN_COLOR || envVar == ENV_VAR_SHADOW_COLOR) { + 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.*/ + RefreshBorderChunks(max(oldClip, Builder_EdgeLevel)); + } +} + +static void OnTerrainAtlasChanged(void* obj) { + static int tilesPerAtlas; + /* e.g. If old atlas was 256x256 and new is 256x256, don't need to refresh */ + if (MapRenderer_1DUsedCount && tilesPerAtlas != Atlas1D.TilesPerAtlas) { + MapRenderer_Refresh(); + } + + MapRenderer_1DUsedCount = MapRenderer_UsedAtlases(); + tilesPerAtlas = Atlas1D.TilesPerAtlas; + ResetPartFlags(); +} + +static void OnBlockDefinitionChanged(void* obj) { + MapRenderer_Refresh(); + MapRenderer_1DUsedCount = MapRenderer_UsedAtlases(); + ResetPartFlags(); +} + +static void OnVisibilityChanged(void* obj) { + lastCamPos = Vec3_BigPos(); + CalcViewDists(); +} +static void DeleteChunks_(void* obj) { DeleteChunks(); } +static void Refresh_(void* obj) { MapRenderer_Refresh(); } + +static void OnNewMap(void) { + Game.ChunkUpdates = 0; + DeleteChunks(); + ResetPartCounts(); + + chunkPos = IVec3_MaxValue(); + FreeChunks(); + FreeParts(); +} + +static void OnNewMapLoaded(void) { + chunksCount = World.ChunksCount; + /* TODO: Only perform reallocation when map volume has changed */ + /*if (chunksCount != World.ChunksCount) { */ + /* chunksCount = World.ChunksCount; */ + FreeChunks(); + FreeParts(); + AllocateChunks(); + AllocateParts(); + /*}*/ + + InitChunks(); + lastCamPos = Vec3_BigPos(); +} + +static void OnInit(void) { + Event_Register_(&TextureEvents.AtlasChanged, NULL, OnTerrainAtlasChanged); + Event_Register_(&WorldEvents.EnvVarChanged, NULL, OnEnvVariableChanged); + Event_Register_(&BlockEvents.BlockDefChanged, NULL, OnBlockDefinitionChanged); + + Event_Register_(&GfxEvents.ViewDistanceChanged, NULL, OnVisibilityChanged); + Event_Register_(&GfxEvents.ProjectionChanged, NULL, OnVisibilityChanged); + Event_Register_(&GfxEvents.ContextLost, NULL, DeleteChunks_); + Event_Register_(&GfxEvents.ContextRecreated, NULL, Refresh_); + + /* This = 87 fixes map being invisible when no textures */ + MapRenderer_1DUsedCount = 87; /* Atlas1D_UsedAtlasesCount(); */ + chunkPos = IVec3_MaxValue(); + maxChunkUpdates = Options_GetInt(OPT_MAX_CHUNK_UPDATES, 4, 1024, 30); + CalcViewDists(); +} + +struct IGameComponent MapRenderer_Component = { + OnInit, /* Init */ + OnNewMap, /* Free */ + OnNewMap, /* Reset */ + OnNewMap, /* OnNewMap */ + OnNewMapLoaded /* OnNewMapLoaded */ +}; |