From 5ecbbe296bc0b5364c762c34ef209f31b4edd066 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Wed, 1 Jul 2026 13:29:01 -0500 Subject: [PATCH] Chunk load skin --- src/dusk/rpg/overworld/map.c | 153 +++++++++++++--------- src/dusk/rpg/overworld/map.h | 12 +- src/dusk/rpg/overworld/worldpos.h | 10 ++ src/dusk/scene/overworld/sceneoverworld.c | 3 +- 4 files changed, 109 insertions(+), 69 deletions(-) diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index fcb42024..ed6d5978 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -19,23 +19,30 @@ map_t MAP; errorret_t mapInit() { memoryZero(&MAP, sizeof(map_t)); - MAP.loaded = true; - int32_t i = 0; - for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) { - for(chunkunit_t y = 0; y < MAP_CHUNK_HEIGHT; y++) { - for(chunkunit_t x = 0; x < MAP_CHUNK_WIDTH; x++) { - chunk_t *chunk = &MAP.chunks[i]; - chunk->position.x = x + MAP.chunkPosition.x; - chunk->position.y = y + MAP.chunkPosition.y; - chunk->position.z = z + MAP.chunkPosition.z; - MAP.chunkOrder[i] = chunk; + + MAP.loadPosition = (chunkpos_t){ + -(MAP_CHUNK_SKIN), + -(MAP_CHUNK_SKIN), + -(MAP_CHUNK_SKIN) + }; + + chunkindex_t i = 0; + for(chunkunit_t z = 0; z < MAP_LOADED_CHUNK_DEPTH; z++) { + for(chunkunit_t y = 0; y < MAP_LOADED_CHUNK_HEIGHT; y++) { + for(chunkunit_t x = 0; x < MAP_LOADED_CHUNK_WIDTH; x++) { + chunk_t *chunk = &MAP.chunks[i++]; + chunk->position = (chunkpos_t){ + MAP.loadPosition.x + (chunkunit_t)x, + MAP.loadPosition.y + (chunkunit_t)y, + MAP.loadPosition.z + (chunkunit_t)z + }; errorChain(mapChunkLoad(chunk)); - i++; } } } + mapRebuildChunkOrder(); errorOk(); } @@ -45,72 +52,77 @@ bool_t mapIsLoaded() { errorret_t mapPositionSet(const chunkpos_t newPos) { if(!mapIsLoaded()) errorThrow("No map loaded"); + if(chunkPositionIsEqual(newPos, MAP.chunkPosition)) errorOk(); - const chunkpos_t curPos = MAP.chunkPosition; - if(chunkPositionIsEqual(curPos, newPos)) { + // If the new render window still fits inside the current loaded area, + // just remap chunkOrder — no asset I/O needed. + const chunkpos_t lp = MAP.loadPosition; + if( + newPos.x >= lp.x && + newPos.y >= lp.y && + newPos.z >= lp.z && + newPos.x + MAP_CHUNK_WIDTH <= lp.x + MAP_LOADED_CHUNK_WIDTH && + newPos.y + MAP_CHUNK_HEIGHT <= lp.y + MAP_LOADED_CHUNK_HEIGHT && + newPos.z + MAP_CHUNK_DEPTH <= lp.z + MAP_LOADED_CHUNK_DEPTH + ) { + MAP.chunkPosition = newPos; + mapRebuildChunkOrder(); errorOk(); } - chunkindex_t chunksRemaining[MAP_CHUNK_COUNT] = {0}; - chunkindex_t chunksFreed[MAP_CHUNK_COUNT] = {0}; + // Render window fell outside the loaded area — re-centre the load + // window on the new render position (skin buffer on every side). + const chunkpos_t newLoadPos = { + newPos.x - MAP_CHUNK_SKIN, + newPos.y - MAP_CHUNK_SKIN, + newPos.z - MAP_CHUNK_SKIN + }; - uint32_t remainingCount = 0; + // Separate loaded chunks into "keep" and "free" buckets. + chunkindex_t chunksFreed[MAP_LOADED_CHUNK_COUNT]; uint32_t freedCount = 0; - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { + // Use a boolean grid so the inner load loop can check O(1). + bool_t posLoaded[MAP_LOADED_CHUNK_WIDTH][MAP_LOADED_CHUNK_HEIGHT] + [MAP_LOADED_CHUNK_DEPTH]; + memoryZero(posLoaded, sizeof(posLoaded)); + + for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) { chunk_t *chunk = &MAP.chunks[i]; + chunkunit_t rx = chunk->position.x - newLoadPos.x; + chunkunit_t ry = chunk->position.y - newLoadPos.y; + chunkunit_t rz = chunk->position.z - newLoadPos.z; if( - chunk->position.x >= newPos.x && - chunk->position.x < newPos.x + MAP_CHUNK_WIDTH && - - chunk->position.y >= newPos.y && - chunk->position.y < newPos.y + MAP_CHUNK_HEIGHT && - - chunk->position.z >= newPos.z && - chunk->position.z < newPos.z + MAP_CHUNK_DEPTH + rx >= 0 && rx < MAP_LOADED_CHUNK_WIDTH && + ry >= 0 && ry < MAP_LOADED_CHUNK_HEIGHT && + rz >= 0 && rz < MAP_LOADED_CHUNK_DEPTH ) { - chunksRemaining[remainingCount++] = i; - continue; + posLoaded[rx][ry][rz] = true; + } else { + mapChunkUnload(chunk); + chunksFreed[freedCount++] = i; } - - chunksFreed[freedCount++] = i; } - for(chunkindex_t i = 0; i < freedCount; i++) { - chunk_t *chunk = &MAP.chunks[chunksFreed[i]]; - mapChunkUnload(chunk); - } - - chunkindex_t orderIndex = 0; - for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) { - for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) { - for(chunkunit_t xOff = 0; xOff < MAP_CHUNK_WIDTH; xOff++) { - const chunkpos_t newChunkPos = { - newPos.x + xOff, newPos.y + yOff, newPos.z + zOff + for(chunkunit_t z = 0; z < MAP_LOADED_CHUNK_DEPTH; z++) { + for(chunkunit_t y = 0; y < MAP_LOADED_CHUNK_HEIGHT; y++) { + for(chunkunit_t x = 0; x < MAP_LOADED_CHUNK_WIDTH; x++) { + if(posLoaded[x][y][z]) continue; + assertTrue(freedCount > 0, "No free chunk slot available."); + chunk_t *chunk = &MAP.chunks[chunksFreed[--freedCount]]; + chunk->position = (chunkpos_t){ + newLoadPos.x + (chunkunit_t)x, + newLoadPos.y + (chunkunit_t)y, + newLoadPos.z + (chunkunit_t)z }; - - chunkindex_t chunkIndex = -1; - for(chunkindex_t i = 0; i < remainingCount; i++) { - chunk_t *chunk = &MAP.chunks[chunksRemaining[i]]; - if(!chunkPositionIsEqual(chunk->position, newChunkPos)) continue; - chunkIndex = chunksRemaining[i]; - break; - } - - if(chunkIndex == -1) { - chunkIndex = chunksFreed[--freedCount]; - chunk_t *chunk = &MAP.chunks[chunkIndex]; - chunk->position = newChunkPos; - errorChain(mapChunkLoad(chunk)); - } - - MAP.chunkOrder[orderIndex++] = &MAP.chunks[chunkIndex]; + errorChain(mapChunkLoad(chunk)); } } } + MAP.loadPosition = newLoadPos; MAP.chunkPosition = newPos; - + mapRebuildChunkOrder(); errorOk(); } @@ -119,21 +131,16 @@ errorret_t mapUpdate() { } errorret_t mapDispose() { - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { + for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) { mapChunkUnload(&MAP.chunks[i]); } errorOk(); } void mapChunkUnload(chunk_t *chunk) { - uint8_t chunkIndex = (uint8_t)(chunk - MAP.chunks); for(uint8_t i = 0; i < CHUNK_ENTITY_COUNT_MAX; i++) { if(chunk->entities[i] == 0xFF) continue; entity_t *entity = &ENTITIES[chunk->entities[i]]; - assertTrue( - entity->chunkIndex == chunkIndex, - "Entity chunk index does not match chunk" - ); if(entity->type == ENTITY_TYPE_PLAYER) { entitySetChunk(entity, 0xFF); } else { @@ -235,6 +242,24 @@ tile_t mapGetTile(const worldpos_t position) { return chunk->tiles[tileIndex]; } +void mapRebuildChunkOrder() { + memoryZero(MAP.chunkOrder, sizeof(MAP.chunkOrder)); + for(chunkindex_t i = 0; i < MAP_LOADED_CHUNK_COUNT; i++) { + chunk_t *chunk = &MAP.chunks[i]; + const chunkpos_t rel = { + chunk->position.x - MAP.chunkPosition.x, + chunk->position.y - MAP.chunkPosition.y, + chunk->position.z - MAP.chunkPosition.z + }; + if( + rel.x < 0 || rel.x >= MAP_CHUNK_WIDTH || + rel.y < 0 || rel.y >= MAP_CHUNK_HEIGHT || + rel.z < 0 || rel.z >= MAP_CHUNK_DEPTH + ) continue; + MAP.chunkOrder[chunkPosToIndex(&rel)] = chunk; + } +} + void mapChunkLoadError(void *params, void *user) { assertNotNull(params, "mapChunkLoadError: params cannot be NULL"); assertNotNull(user, "mapChunkLoadError: user cannot be NULL"); diff --git a/src/dusk/rpg/overworld/map.h b/src/dusk/rpg/overworld/map.h index 61fa2750..10d54460 100644 --- a/src/dusk/rpg/overworld/map.h +++ b/src/dusk/rpg/overworld/map.h @@ -12,14 +12,12 @@ #define MAP_FILE_PATH_MAX 128 typedef struct map_s { - // char_t filePath[MAP_FILE_PATH_MAX]; - // char_t dirPath[MAP_FILE_PATH_MAX]; - // char_t fileName[MAP_FILE_PATH_MAX]; bool_t loaded; - chunk_t chunks[MAP_CHUNK_COUNT]; + chunk_t chunks[MAP_LOADED_CHUNK_COUNT]; chunk_t *chunkOrder[MAP_CHUNK_COUNT]; chunkpos_t chunkPosition; + chunkpos_t loadPosition; } map_t; extern map_t MAP; @@ -106,6 +104,12 @@ void mapChunkLoadError(void *params, void *user); */ void mapChunkLoaded(void *params, void *user); +/** + * Rebuilds chunkOrder from the loaded chunks that fall within the + * current render window. Called whenever chunkPosition changes. + */ +void mapRebuildChunkOrder(); + /** * Gets the index of a chunk, within the world, at the given position. * diff --git a/src/dusk/rpg/overworld/worldpos.h b/src/dusk/rpg/overworld/worldpos.h index 889e16e6..4e3f8c2d 100644 --- a/src/dusk/rpg/overworld/worldpos.h +++ b/src/dusk/rpg/overworld/worldpos.h @@ -22,6 +22,16 @@ #define MAP_CHUNK_DEPTH 4 #define MAP_CHUNK_COUNT (MAP_CHUNK_WIDTH * MAP_CHUNK_HEIGHT * MAP_CHUNK_DEPTH) +// Extra chunks loaded on every side beyond the render window. +// The render window can drift MAP_CHUNK_SKIN chunks in any direction +// before a load/unload cycle is triggered. +#define MAP_CHUNK_SKIN 1 +#define MAP_LOADED_CHUNK_WIDTH (MAP_CHUNK_WIDTH + MAP_CHUNK_SKIN) +#define MAP_LOADED_CHUNK_HEIGHT (MAP_CHUNK_HEIGHT + MAP_CHUNK_SKIN) +#define MAP_LOADED_CHUNK_DEPTH (MAP_CHUNK_DEPTH + MAP_CHUNK_SKIN) +#define MAP_LOADED_CHUNK_COUNT \ + (MAP_LOADED_CHUNK_WIDTH * MAP_LOADED_CHUNK_HEIGHT * MAP_LOADED_CHUNK_DEPTH) + #define ENTITY_COUNT 32 typedef int16_t worldunit_t; diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c index fc3bcca6..c602699b 100644 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ b/src/dusk/scene/overworld/sceneoverworld.c @@ -120,7 +120,8 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { errorret_t sceneOverworldDrawChunks() { for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - chunk_t *chunk = &MAP.chunks[i]; + chunk_t *chunk = MAP.chunkOrder[i]; + if(chunk == NULL) continue; if(chunk->meshCount == 0) continue; for(uint8_t m = 0; m < chunk->meshCount; m++) {