From bb020c36c1251b6402961146bc05213eeca2e6c9 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 27 Jun 2026 17:19:44 -0500 Subject: [PATCH] CHUNK STUFF --- assets/chunks/0_0_0.dcf | Bin 63501 -> 32814 bytes assets/meshes/chunk_0_0_0_0.dmf | Bin 0 -> 30732 bytes src/dusk/asset/loader/CMakeLists.txt | 3 +- src/dusk/asset/loader/assetloader.c | 6 + src/dusk/asset/loader/assetloader.h | 5 + .../asset/loader/chunk/assetchunkloader.c | 26 +-- .../asset/loader/chunk/assetchunkloader.h | 9 +- src/dusk/asset/loader/dmf/CMakeLists.txt | 9 + src/dusk/asset/loader/dmf/assetdmfloader.c | 136 +++++++++++++++ src/dusk/asset/loader/dmf/assetdmfloader.h | 65 +++++++ src/dusk/rpg/overworld/chunk.h | 16 +- src/dusk/rpg/overworld/map.c | 149 +++++----------- src/dusk/rpg/overworld/map.h | 1 + src/dusk/scene/overworld/sceneoverworld.c | 53 +++--- tools/asset/chunk/__main__.py | 162 +++++++++++++----- tools/asset/chunk/hillgen.py | 160 ----------------- tools/asset/dmf/__init__.py | 4 + tools/asset/dmf/__main__.py | 69 ++++++++ 18 files changed, 514 insertions(+), 359 deletions(-) create mode 100644 assets/meshes/chunk_0_0_0_0.dmf create mode 100644 src/dusk/asset/loader/dmf/CMakeLists.txt create mode 100644 src/dusk/asset/loader/dmf/assetdmfloader.c create mode 100644 src/dusk/asset/loader/dmf/assetdmfloader.h delete mode 100644 tools/asset/chunk/hillgen.py create mode 100644 tools/asset/dmf/__init__.py create mode 100644 tools/asset/dmf/__main__.py diff --git a/assets/chunks/0_0_0.dcf b/assets/chunks/0_0_0.dcf index 436721689c226de592b7839be2954b218b14a59f..c14c4c902686ef3f0c2cb4acb12f839699f04ddd 100644 GIT binary patch delta 57 xcmeDEz`U-3iPgo~je&Wi;LrMra@?x9sl^$o#rnw^rFq%$1`w#1lAFeW3;-SO4-EhS literal 63501 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 diff --git a/assets/meshes/chunk_0_0_0_0.dmf b/assets/meshes/chunk_0_0_0_0.dmf new file mode 100644 index 0000000000000000000000000000000000000000..ada49debcff2f6190ca39eae7ceddb79f3376064 GIT binary patch literal 30732 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 literal 0 HcmV?d00001 diff --git a/src/dusk/asset/loader/CMakeLists.txt b/src/dusk/asset/loader/CMakeLists.txt index 556ccd17..30f97bc1 100644 --- a/src/dusk/asset/loader/CMakeLists.txt +++ b/src/dusk/asset/loader/CMakeLists.txt @@ -15,4 +15,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} add_subdirectory(display) add_subdirectory(locale) add_subdirectory(json) -add_subdirectory(chunk) \ No newline at end of file +add_subdirectory(chunk) +add_subdirectory(dmf) \ No newline at end of file diff --git a/src/dusk/asset/loader/assetloader.c b/src/dusk/asset/loader/assetloader.c index 902731b6..96adaf56 100644 --- a/src/dusk/asset/loader/assetloader.c +++ b/src/dusk/asset/loader/assetloader.c @@ -45,4 +45,10 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = { .loadAsync = assetChunkLoaderAsync, .dispose = assetChunkDispose }, + + [ASSET_LOADER_TYPE_DMF] = { + .loadSync = assetDmfLoaderSync, + .loadAsync = assetDmfLoaderAsync, + .dispose = assetDmfDispose + }, }; diff --git a/src/dusk/asset/loader/assetloader.h b/src/dusk/asset/loader/assetloader.h index 61ba9696..09454560 100644 --- a/src/dusk/asset/loader/assetloader.h +++ b/src/dusk/asset/loader/assetloader.h @@ -12,6 +12,7 @@ #include "asset/loader/locale/assetlocaleloader.h" #include "asset/loader/json/assetjsonloader.h" #include "asset/loader/chunk/assetchunkloader.h" +#include "asset/loader/dmf/assetdmfloader.h" typedef enum { ASSET_LOADER_TYPE_NULL, @@ -22,6 +23,7 @@ typedef enum { ASSET_LOADER_TYPE_LOCALE, ASSET_LOADER_TYPE_JSON, ASSET_LOADER_TYPE_CHUNK, + ASSET_LOADER_TYPE_DMF, ASSET_LOADER_TYPE_COUNT } assetloadertype_t; @@ -33,6 +35,7 @@ typedef union { assetlocaleloaderinput_t locale; assetjsonloaderinput_t json; assetchunkloaderinput_t chunk; + assetdmfloaderinput_t dmf; } assetloaderinput_t; typedef union { @@ -42,6 +45,7 @@ typedef union { assetlocaleloaderloading_t locale; assetjsonloaderloading_t json; assetchunkloaderloading_t chunk; + assetdmfloaderloading_t dmf; } assetloaderloading_t; typedef union { @@ -51,6 +55,7 @@ typedef union { assetlocaleoutput_t locale; assetjsonoutput_t json; assetchunkoutput_t chunk; + assetdmfoutput_t dmf; } assetloaderoutput_t; typedef struct assetloading_s assetloading_t; diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.c b/src/dusk/asset/loader/chunk/assetchunkloader.c index 92a491d0..56fadf49 100644 --- a/src/dusk/asset/loader/chunk/assetchunkloader.c +++ b/src/dusk/asset/loader/chunk/assetchunkloader.c @@ -92,22 +92,22 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) { "Chunk mesh count exceeds maximum." ); - uint32_t poolOffset = 0; for(uint8_t m = 0; m < out->meshCount; m++) { - uint32_t vertCount = endianLittleToHost32(*(uint32_t *)(data + offset)); - offset += sizeof(uint32_t); - assertTrue( - poolOffset + vertCount <= CHUNK_VERTEX_COUNT, - "Chunk vertex data exceeds pool." - ); - out->meshVertCounts[m] = vertCount; + uint8_t nameLen = 0; + while( + data[offset + nameLen] != '\0' && + nameLen < CHUNK_MESH_NAME_MAX - 1 + ) { + nameLen++; + } + memoryCopy(out->meshNames[m], data + offset, nameLen); + out->meshNames[m][nameLen] = '\0'; + offset += nameLen + 1; + memoryCopy( - &out->vertices[poolOffset], - data + offset, - vertCount * sizeof(meshvertex_t) + out->meshOffsets[m], data + offset, sizeof(vec3) ); - offset += vertCount * sizeof(meshvertex_t); - poolOffset += vertCount; + offset += sizeof(vec3); } memoryFree(data); diff --git a/src/dusk/asset/loader/chunk/assetchunkloader.h b/src/dusk/asset/loader/chunk/assetchunkloader.h index ecedef40..8504b2c2 100644 --- a/src/dusk/asset/loader/chunk/assetchunkloader.h +++ b/src/dusk/asset/loader/chunk/assetchunkloader.h @@ -9,7 +9,7 @@ #include "asset/assetfile.h" #include "rpg/overworld/chunk.h" -#define ASSET_CHUNK_FILE_VERSION 2 +#define ASSET_CHUNK_FILE_VERSION 3 typedef struct assetloading_s assetloading_t; typedef struct assetentry_s assetentry_t; @@ -34,8 +34,8 @@ typedef struct { typedef struct { tile_t tiles[CHUNK_TILE_COUNT]; uint8_t meshCount; - uint32_t meshVertCounts[CHUNK_MESH_COUNT_MAX]; - meshvertex_t vertices[CHUNK_VERTEX_COUNT]; + char_t meshNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; + vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; } assetchunkoutput_t; /** @@ -50,7 +50,8 @@ errorret_t assetChunkLoaderAsync(assetloading_t *loading); /** * Synchronous loader for chunk assets. Validates the DCF binary previously - * read by the async phase and populates the output assetchunkoutput_t. + * read by the async phase and populates the output assetchunkoutput_t with + * tile data and DMF mesh names. * * @param loading Loading information for the asset being loaded. * @return Error code indicating success or failure of the load operation. diff --git a/src/dusk/asset/loader/dmf/CMakeLists.txt b/src/dusk/asset/loader/dmf/CMakeLists.txt new file mode 100644 index 00000000..4ffb6016 --- /dev/null +++ b/src/dusk/asset/loader/dmf/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + assetdmfloader.c +) diff --git a/src/dusk/asset/loader/dmf/assetdmfloader.c b/src/dusk/asset/loader/dmf/assetdmfloader.c new file mode 100644 index 00000000..3c217c43 --- /dev/null +++ b/src/dusk/asset/loader/dmf/assetdmfloader.c @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "assetdmfloader.h" +#include "assert/assert.h" +#include "util/memory.h" +#include "util/endian.h" +#include "asset/loader/assetloading.h" +#include "asset/loader/assetentry.h" + +errorret_t assetDmfLoaderAsync(assetloading_t *loading) { + assertNotNull(loading, "Loading cannot be NULL"); + assertNotMainThread("Should be called from an async thread."); + + if(loading->loading.dmf.state != ASSET_DMF_LOADING_STATE_READ_FILE) { + errorOk(); + } + + assertNull(loading->loading.dmf.data, "Data already defined?"); + + assetfile_t *file = &loading->loading.dmf.file; + assetLoaderErrorChain(loading, + assetFileInit(file, loading->entry->name, NULL, NULL) + ); + + uint8_t *data = memoryAllocate(file->size); + assetLoaderErrorChain(loading, assetFileOpen(file)); + assetLoaderErrorChain(loading, assetFileRead(file, data, file->size)); + assetLoaderErrorChain(loading, assetFileClose(file)); + assetLoaderErrorChain(loading, assetFileDispose(file)); + assertTrue( + file->lastRead == file->size, + "Failed to read entire DMF file." + ); + + loading->loading.dmf.data = data; + loading->loading.dmf.state = ASSET_DMF_LOADING_STATE_CREATE_MESH; + loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC; + errorOk(); +} + +errorret_t assetDmfLoaderSync(assetloading_t *loading) { + assertNotNull(loading, "Loading cannot be NULL"); + assertTrue(loading->type == ASSET_LOADER_TYPE_DMF, "Invalid type."); + assertIsMainThread("Must be called from the main thread."); + + switch(loading->loading.dmf.state) { + case ASSET_DMF_LOADING_STATE_INITIAL: + loading->loading.dmf.state = ASSET_DMF_LOADING_STATE_READ_FILE; + loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC; + errorOk(); + break; + + case ASSET_DMF_LOADING_STATE_CREATE_MESH: + break; + + default: + errorOk(); + } + + uint8_t *data = loading->loading.dmf.data; + assertNotNull(data, "DMF data should have been loaded by now."); + + if(data[0] != 'D' || data[1] != 'M' || data[2] != 'F') { + memoryFree(data); + assetLoaderErrorThrow(loading, "Invalid DMF file header"); + } + + uint32_t version = endianLittleToHost32(*(uint32_t *)(data + 4)); + if(version != ASSET_DMF_FILE_VERSION) { + memoryFree(data); + assetLoaderErrorThrow( + loading, "Unsupported DMF version %u", version + ); + } + + uint32_t vertCount = endianLittleToHost32(*(uint32_t *)(data + 8)); + assetdmfoutput_t *out = &loading->entry->data.dmf; + + if(vertCount == 0) { + memoryFree(data); + loading->loading.dmf.data = NULL; + loading->entry->state = ASSET_ENTRY_STATE_LOADED; + errorOk(); + } + + out->vertices = memoryAllocate(vertCount * sizeof(meshvertex_t)); + memoryCopy( + out->vertices, data + 12, vertCount * sizeof(meshvertex_t) + ); + memoryFree(data); + loading->loading.dmf.data = NULL; + + errorret_t ret = meshInit( + &out->mesh, + MESH_PRIMITIVE_TYPE_TRIANGLES, + (int32_t)vertCount, + out->vertices + ); + if(errorIsNotOk(ret)) { + loading->entry->state = ASSET_ENTRY_STATE_ERROR; + memoryFree(out->vertices); + out->vertices = NULL; + errorChain(ret); + } + + ret = meshFlush(&out->mesh, 0, (int32_t)vertCount); + if(errorIsNotOk(ret)) { + loading->entry->state = ASSET_ENTRY_STATE_ERROR; + meshDispose(&out->mesh); + memoryFree(out->vertices); + out->vertices = NULL; + errorChain(ret); + } + + loading->entry->state = ASSET_ENTRY_STATE_LOADED; + errorOk(); +} + +errorret_t assetDmfDispose(assetentry_t *entry) { + assertNotNull(entry, "Entry cannot be NULL"); + assertTrue(entry->type == ASSET_LOADER_TYPE_DMF, "Invalid type."); + assertIsMainThread("Must be called from the main thread."); + + assetdmfoutput_t *out = &entry->data.dmf; + if(out->vertices != NULL) { + errorChain(meshDispose(&out->mesh)); + memoryFree(out->vertices); + out->vertices = NULL; + } + errorOk(); +} diff --git a/src/dusk/asset/loader/dmf/assetdmfloader.h b/src/dusk/asset/loader/dmf/assetdmfloader.h new file mode 100644 index 00000000..f53954e1 --- /dev/null +++ b/src/dusk/asset/loader/dmf/assetdmfloader.h @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "asset/assetfile.h" +#include "display/mesh/mesh.h" + +#define ASSET_DMF_FILE_VERSION 1 + +typedef struct assetloading_s assetloading_t; +typedef struct assetentry_s assetentry_t; + +typedef struct { + void *nothing; +} assetdmfloaderinput_t; + +typedef enum { + ASSET_DMF_LOADING_STATE_INITIAL, + ASSET_DMF_LOADING_STATE_READ_FILE, + ASSET_DMF_LOADING_STATE_CREATE_MESH, + ASSET_DMF_LOADING_STATE_DONE +} assetdmfloadingstate_t; + +typedef struct { + assetfile_t file; + assetdmfloadingstate_t state; + uint8_t *data; +} assetdmfloaderloading_t; + +typedef struct { + mesh_t mesh; + meshvertex_t *vertices; +} assetdmfoutput_t; + +/** + * Asynchronous loader for DMF mesh assets. Reads the raw DMF file bytes + * into the loading buffer so the sync phase can parse without blocking + * the main thread on I/O. + * + * @param loading Loading information for the asset being loaded. + * @return Error code indicating success or failure. + */ +errorret_t assetDmfLoaderAsync(assetloading_t *loading); + +/** + * Synchronous loader for DMF mesh assets. Parses the DMF binary read by + * the async phase, then initializes and flushes the mesh to the GPU. + * + * @param loading Loading information for the asset being loaded. + * @return Error code indicating success or failure. + */ +errorret_t assetDmfLoaderSync(assetloading_t *loading); + +/** + * Disposer for DMF mesh assets. Disposes the mesh and frees the vertex + * buffer. + * + * @param entry Asset entry containing the DMF data to dispose. + * @return Error code indicating success or failure. + */ +errorret_t assetDmfDispose(assetentry_t *entry); diff --git a/src/dusk/rpg/overworld/chunk.h b/src/dusk/rpg/overworld/chunk.h index a701d544..4aa602b2 100644 --- a/src/dusk/rpg/overworld/chunk.h +++ b/src/dusk/rpg/overworld/chunk.h @@ -8,21 +8,21 @@ #pragma once #include "rpg/overworld/tile.h" #include "worldpos.h" -#include "display/mesh/mesh.h" -#include "display/spritebatch/spritebatch.h" #define CHUNK_MESH_COUNT_MAX 10 -#define CHUNK_VERTEX_COUNT 8192 +#define CHUNK_MESH_NAME_MAX 64 #define CHUNK_ENTITY_COUNT_MAX 10 +typedef struct assetentry_s assetentry_t; + typedef struct chunk_s { chunkpos_t position; tile_t tiles[CHUNK_TILE_COUNT]; uint8_t meshCount; - uint32_t meshVertCounts[CHUNK_MESH_COUNT_MAX]; - meshvertex_t vertices[CHUNK_VERTEX_COUNT]; - mesh_t meshes[CHUNK_MESH_COUNT_MAX]; + char_t meshNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX]; + vec3 meshOffsets[CHUNK_MESH_COUNT_MAX]; + assetentry_t *meshEntries[CHUNK_MESH_COUNT_MAX]; uint8_t entities[CHUNK_ENTITY_COUNT_MAX]; } chunk_t; @@ -37,9 +37,9 @@ uint32_t chunkGetTileIndex(const chunkpos_t position); /** * Checks if two chunk positions are equal. - * + * * @param a The first chunk position. * @param b The second chunk position. * @return true if equal, false otherwise. */ -bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b); \ No newline at end of file +bool_t chunkPositionIsEqual(const chunkpos_t a, const chunkpos_t b); diff --git a/src/dusk/rpg/overworld/map.c b/src/dusk/rpg/overworld/map.c index 2f7598dc..bbccccaa 100644 --- a/src/dusk/rpg/overworld/map.c +++ b/src/dusk/rpg/overworld/map.c @@ -1,6 +1,6 @@ /** * Copyright (c) 2025 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -9,6 +9,7 @@ #include "util/memory.h" #include "assert/assert.h" #include "asset/asset.h" +#include "asset/loader/assetloader.h" #include "rpg/entity/entity.h" #include "util/string.h" @@ -17,20 +18,6 @@ map_t MAP; errorret_t mapInit() { memoryZero(&MAP, sizeof(map_t)); - // Setup chunk meshes - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - chunk_t *chunk = &MAP.chunks[i]; - for(uint8_t j = 0; j < CHUNK_MESH_COUNT_MAX; j++) { - errorChain(meshInit( - &chunk->meshes[j], - MESH_PRIMITIVE_TYPE_TRIANGLES, - CHUNK_VERTEX_COUNT, - chunk->vertices - )); - } - } - - // Perform "initial load" MAP.loaded = true; int32_t i = 0; for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) { @@ -54,61 +41,6 @@ bool_t mapIsLoaded() { return MAP.loaded; } -// errorret_t mapLoad(const char_t *path, const chunkpos_t position) { -// assertStrLenMin(path, 1, "Map file path cannot be empty"); -// assertStrLenMax(path, MAP_FILE_PATH_MAX - 1, "Map file path too long"); - -// if(stringCompare(MAP.filePath, path) == 0) { -// // Same map, no need to reload -// errorOk(); -// } - -// chunkindex_t i; - -// // Unload all loaded chunks -// if(mapIsLoaded()) { -// for(i = 0; i < MAP_CHUNK_COUNT; i++) { -// mapChunkUnload(&MAP.chunks[i]); -// } -// } - -// // Store the map file path -// stringCopy(MAP.filePath, path, MAP_FILE_PATH_MAX); - -// // Determine directory path (it is dirname) -// stringCopy(MAP.dirPath, path, MAP_FILE_PATH_MAX); -// char_t *last = stringFindLastChar(MAP.dirPath, '/'); -// if(last == NULL) errorThrow("Invalid map file path"); - -// // Store filename, sans extension -// stringCopy(MAP.fileName, last + 1, MAP_FILE_PATH_MAX); -// *last = '\0'; // Terminate to get directory path - -// last = stringFindLastChar(MAP.fileName, '.'); -// if(last == NULL) errorThrow("Map file name has no extension"); -// *last = '\0'; // Terminate to remove extension - -// // Reset map position -// MAP.chunkPosition = position; - -// // Perform "initial load" -// 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 + position.x; -// chunk->position.y = y + position.y; -// chunk->position.z = z + position.z; -// MAP.chunkOrder[i] = chunk; -// errorChain(mapChunkLoad(chunk)); -// i++; -// } -// } -// } -// errorOk(); -// } - errorret_t mapPositionSet(const chunkpos_t newPos) { if(!mapIsLoaded()) errorThrow("No map loaded"); @@ -117,7 +49,6 @@ errorret_t mapPositionSet(const chunkpos_t newPos) { errorOk(); } - // Determine which chunks remain loaded chunkindex_t chunksRemaining[MAP_CHUNK_COUNT] = {0}; chunkindex_t chunksFreed[MAP_CHUNK_COUNT] = {0}; @@ -125,7 +56,6 @@ errorret_t mapPositionSet(const chunkpos_t newPos) { uint32_t freedCount = 0; for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - // Will this chunk remain loaded? chunk_t *chunk = &MAP.chunks[i]; if( chunk->position.x >= newPos.x && @@ -137,23 +67,18 @@ errorret_t mapPositionSet(const chunkpos_t newPos) { chunk->position.z >= newPos.z && chunk->position.z < newPos.z + MAP_CHUNK_DEPTH ) { - // Stays loaded chunksRemaining[remainingCount++] = i; continue; } - // Not remaining loaded chunksFreed[freedCount++] = i; } - // Unload the freed chunks for(chunkindex_t i = 0; i < freedCount; i++) { chunk_t *chunk = &MAP.chunks[chunksFreed[i]]; mapChunkUnload(chunk); } - // This can probably be optimized later, for now we check each chunk and see - // if it needs loading or not, and update the chunk order chunkindex_t orderIndex = 0; for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) { for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) { @@ -161,8 +86,7 @@ errorret_t mapPositionSet(const chunkpos_t newPos) { const chunkpos_t newChunkPos = { newPos.x + xOff, newPos.y + yOff, newPos.z + zOff }; - - // Is this chunk already loaded (was not unloaded earlier)? + chunkindex_t chunkIndex = -1; for(chunkindex_t i = 0; i < remainingCount; i++) { chunk_t *chunk = &MAP.chunks[chunksRemaining[i]]; @@ -171,9 +95,7 @@ errorret_t mapPositionSet(const chunkpos_t newPos) { break; } - // Need to load this chunk if(chunkIndex == -1) { - // Find a freed chunk to reuse chunkIndex = chunksFreed[--freedCount]; chunk_t *chunk = &MAP.chunks[chunkIndex]; chunk->position = newChunkPos; @@ -185,27 +107,23 @@ errorret_t mapPositionSet(const chunkpos_t newPos) { } } - // Update map position MAP.chunkPosition = newPos; errorOk(); } void mapUpdate() { - + } errorret_t mapDispose() { for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { mapChunkUnload(&MAP.chunks[i]); - for(uint8_t j = 0; j < CHUNK_MESH_COUNT_MAX; j++) { - errorChain(meshDispose(&MAP.chunks[i].meshes[j])); - } } errorOk(); } -void mapChunkUnload(chunk_t* chunk) { +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; @@ -220,10 +138,16 @@ void mapChunkUnload(chunk_t* chunk) { entity->type = ENTITY_TYPE_NULL; } } + + for(uint8_t m = 0; m < chunk->meshCount; m++) { + if(chunk->meshEntries[m] == NULL) continue; + assetUnlockEntry(chunk->meshEntries[m]); + chunk->meshEntries[m] = NULL; + } chunk->meshCount = 0; } -errorret_t mapChunkLoad(chunk_t* chunk) { +errorret_t mapChunkLoad(chunk_t *chunk) { if(!mapIsLoaded()) errorThrow("No map loaded"); memorySet(chunk->entities, 0xFF, sizeof(chunk->entities)); @@ -253,25 +177,42 @@ errorret_t mapChunkLoad(chunk_t* chunk) { } memoryCopy(chunk->tiles, entry->data.chunk.tiles, sizeof(chunk->tiles)); - memoryCopy( - chunk->vertices, entry->data.chunk.vertices, sizeof(chunk->vertices) - ); - memoryCopy( - chunk->meshVertCounts, - entry->data.chunk.meshVertCounts, - sizeof(chunk->meshVertCounts) - ); uint8_t meshCount = entry->data.chunk.meshCount; + for(uint8_t m = 0; m < meshCount; m++) { + stringCopy( + chunk->meshNames[m], + entry->data.chunk.meshNames[m], + CHUNK_MESH_NAME_MAX + ); + glm_vec3_copy(entry->data.chunk.meshOffsets[m], chunk->meshOffsets[m]); + } assetUnlockEntry(entry); - if(meshCount == 0) errorOk(); - chunk->meshCount = meshCount; for(uint8_t m = 0; m < meshCount; m++) { - if(chunk->meshVertCounts[m] == 0) continue; - errorChain(meshFlush( - &chunk->meshes[m], 0, (int32_t)chunk->meshVertCounts[m] - )); + assetentry_t *meshEntry = assetLock( + chunk->meshNames[m], ASSET_LOADER_TYPE_DMF, NULL + ); + if(meshEntry == NULL) { + for(uint8_t j = 0; j < m; j++) { + assetUnlockEntry(chunk->meshEntries[j]); + chunk->meshEntries[j] = NULL; + } + errorThrow("Failed to lock mesh: %s", chunk->meshNames[m]); + } + + ret = assetRequireLoaded(meshEntry); + if(errorIsNotOk(ret)) { + assetUnlockEntry(meshEntry); + for(uint8_t j = 0; j < m; j++) { + assetUnlockEntry(chunk->meshEntries[j]); + chunk->meshEntries[j] = NULL; + } + return ret; + } + + chunk->meshEntries[m] = meshEntry; } + chunk->meshCount = meshCount; errorOk(); } @@ -296,7 +237,7 @@ chunkindex_t mapGetChunkIndexAt(const chunkpos_t position) { return chunkPosToIndex(&relPos); } -chunk_t* mapGetChunk(const uint8_t index) { +chunk_t *mapGetChunk(const uint8_t index) { if(index >= MAP_CHUNK_COUNT) return NULL; if(!mapIsLoaded()) return NULL; return MAP.chunkOrder[index]; @@ -314,4 +255,4 @@ tile_t mapGetTile(const worldpos_t position) { assertNotNull(chunk, "Chunk pointer cannot be NULL"); chunktileindex_t tileIndex = worldPosToChunkTileIndex(&position); return chunk->tiles[tileIndex]; -} \ No newline at end of file +} diff --git a/src/dusk/rpg/overworld/map.h b/src/dusk/rpg/overworld/map.h index 78eb5682..87f44e7d 100644 --- a/src/dusk/rpg/overworld/map.h +++ b/src/dusk/rpg/overworld/map.h @@ -6,6 +6,7 @@ */ #pragma once +#include "error/error.h" #include "rpg/overworld/chunk.h" #define MAP_FILE_PATH_MAX 128 diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c index 97d9e26a..0f01870c 100644 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ b/src/dusk/scene/overworld/sceneoverworld.c @@ -1,6 +1,6 @@ /** * Copyright (c) 2026 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -19,6 +19,9 @@ #include "rpg/entity/entity.h" #include "rpg/rpgcamera.h" +#include "asset/loader/assetloader.h" +#include "asset/loader/assetentry.h" + #define TEXTURE_CHUNK_SIZE 16 static texture_t TEXTURE_CHUNK; @@ -73,7 +76,7 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { proj ); errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj)); - + // Camera Eye float_t pixelsPerUnit = TILE_SIZE_PIXELS; float_t worldH = (float_t)SCREEN.height / pixelsPerUnit; @@ -95,7 +98,7 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { eye ); errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, eye)); - + // Chunks errorChain(sceneOverworldDrawChunks()); @@ -140,35 +143,33 @@ errorret_t sceneOverworldDrawChunks() { } }; - // Pass 1: draw all base meshes with the shared chunk texture (no mid-loop - // texture swaps). - errorChain(shaderSetMaterial(&SHADER_UNLIT, &chunkMaterial)); for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { chunk_t *chunk = &MAP.chunks[i]; if(chunk->meshCount == 0) continue; - if(chunk->meshVertCounts[0] == 0) continue; - errorChain(meshDraw( - &chunk->meshes[0], 0, (int32_t)chunk->meshVertCounts[0] - )); - } - // Pass 2: draw each chunk's additional meshes (indices 1..meshCount-1). - // Vertices are packed sequentially in the pool, so accumulate the offset. - for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) { - chunk_t *chunk = &MAP.chunks[i]; - uint32_t vertOffset = chunk->meshVertCounts[0]; - for(uint8_t m = 1; m < chunk->meshCount; m++) { - if(chunk->meshVertCounts[m] > 0) { - errorChain(meshDraw( - &chunk->meshes[m], - (int32_t)vertOffset, - (int32_t)chunk->meshVertCounts[m] - )); - } - vertOffset += chunk->meshVertCounts[m]; + worldpos_t wp; + chunkPosToWorldPos(&chunk->position, &wp); + vec3 wpf = { (float_t)wp.x, (float_t)wp.y, (float_t)wp.z }; + + for(uint8_t m = 0; m < chunk->meshCount; m++) { + if(chunk->meshEntries[m] == NULL) continue; + + vec3 pos; + glm_vec3_add(wpf, chunk->meshOffsets[m], pos); + mat4 model; + glm_translate_make(model, pos); + errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model)); + errorChain(shaderSetMaterial(&SHADER_UNLIT, &chunkMaterial)); + errorChain(meshDraw(&chunk->meshEntries[m]->data.dmf.mesh, 0, -1)); } } + // Restore identity model so subsequent renders (e.g. entities) are + // not affected by the last chunk transform. + mat4 identity; + glm_mat4_identity(identity); + errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, identity)); + errorOk(); } @@ -178,4 +179,4 @@ errorret_t sceneOverworldDispose(scenedata_t *sceneData) { errorChain(textureDispose(&TEXTURE_CHUNK)); errorOk(); -} \ No newline at end of file +} diff --git a/tools/asset/chunk/__main__.py b/tools/asset/chunk/__main__.py index ce152a4d..8bc9c289 100644 --- a/tools/asset/chunk/__main__.py +++ b/tools/asset/chunk/__main__.py @@ -4,7 +4,8 @@ # https://opensource.org/licenses/MIT """ -Converts DCF chunk files from version 1 to version 2. +Converts DCF chunk files (version 1 or 2) to version 3 and generates the +companion DMF (Dusk Mesh Format) files that version 3 references. Version 1 format (after 8-byte header + tiles): uint32_t vertCount @@ -13,37 +14,51 @@ Version 1 format (after 8-byte header + tiles): Version 2 format (after 8-byte header + tiles): uint8_t meshCount for each mesh: - uint32_t vertCount + uint32_t vertCount meshvertex_t vertices[vertCount] +Version 3 format (after 8-byte header + tiles): + uint8_t meshCount + for each mesh: + null-terminated string (path to companion .dmf asset) + +DMF format: + Bytes 0-3: DMF\x00 + Bytes 4-7: uint32_t version = 1 (little-endian) + Bytes 8-11: uint32_t vertCount (little-endian) + Bytes 12+: meshvertex_t vertices[vertCount] + Usage: python3 -m tools.asset.chunk [output.dcf] If output is omitted the input file is updated in place. + DMF files are written to assets/meshes/ beside the chunks/ directory. """ import struct import sys import os -# Must match src/dusk/rpg/overworld/chunk.h -CHUNK_WIDTH = 16 -CHUNK_HEIGHT = 16 -CHUNK_DEPTH = 32 +CHUNK_WIDTH = 16 +CHUNK_HEIGHT = 16 +CHUNK_DEPTH = 32 CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH # 8192 CHUNK_MESH_COUNT_MAX = 10 CHUNK_VERTEX_COUNT = 8192 +CHUNK_MESH_NAME_MAX = 64 -# C enum (int) = 4 bytes; meshvertex_t = uv[2]+pos[3] floats = 20 bytes TILE_SIZE = 4 -VERTEX_SIZE = 20 # 2 floats UV + 3 floats pos, MESH_ENABLE_COLOR=0 +VERTEX_SIZE = 20 FILE_MAGIC = b'DCF' -VERSION_IN = 1 -VERSION_OUT = 2 +DMF_MAGIC = b'DMF\x00' +VERSION_OUT = 3 +DMF_VERSION = 1 -def read_v1(path): +def read_dcf(path): + """Read a v1 or v2 DCF file. Returns (tiles, meshes) where meshes is a + list of raw vertex byte strings, one per mesh.""" with open(path, 'rb') as f: data = f.read() @@ -51,34 +66,66 @@ def read_v1(path): raise ValueError(f"{path}: not a DCF file") version = struct.unpack_from(' 0: + meshes.append(verts) + else: + mesh_count = data[offset] + offset += 1 + for _ in range(mesh_count): + vert_count = struct.unpack_from(' 0: + meshes.append(verts) - return tiles, vert_count, verts + return tiles, meshes -def write_v2(path, tiles, vert_count, verts): - if vert_count > CHUNK_VERTEX_COUNT: - print( - f" Warning: {vert_count} vertices exceeds pool " - f"({CHUNK_VERTEX_COUNT}); truncating." - ) - vert_count = CHUNK_VERTEX_COUNT - verts = verts[:vert_count * VERTEX_SIZE] +def write_dmf(path, vertex_bytes): + vert_count = len(vertex_bytes) // VERTEX_SIZE + buf = bytearray() + buf += DMF_MAGIC + buf += struct.pack(' 0 else 0 + +def write_v3(dcf_path, tiles, mesh_names, mesh_offsets=None): + """Write a v3 DCF that references the given DMF asset paths. + + mesh_offsets is an optional list of (x, y, z) tuples, one per mesh. + Defaults to (0, 0, 0) for each mesh when omitted. + """ + mesh_count = len(mesh_names) + if mesh_offsets is None: + mesh_offsets = [(0.0, 0.0, 0.0)] * mesh_count buf = bytearray() buf += FILE_MAGIC @@ -86,34 +133,63 @@ def write_v2(path, tiles, vert_count, verts): buf += struct.pack(' 0: - buf += struct.pack('= CHUNK_MESH_NAME_MAX: + raise ValueError( + f"Mesh name too long (>= {CHUNK_MESH_NAME_MAX}): {name}" + ) + buf += encoded + b'\x00' + buf += struct.pack('<3f', offset[0], offset[1], offset[2]) + with open(dcf_path, 'wb') as f: f.write(buf) - print( - f" Wrote {path}: version {VERSION_OUT}, " - f"{mesh_count} mesh(es), {vert_count} vertices." + f' Wrote DCF {dcf_path}: ' + f'version {VERSION_OUT}, {mesh_count} mesh(es)' ) def main(): args = sys.argv[1:] if not args: - print("Usage: python3 -m tools.asset.chunk [output.dcf]") + print( + "Usage: python3 -m tools.asset.chunk " + " [output.dcf]" + ) sys.exit(1) src = args[0] dst = args[1] if len(args) > 1 else src print(f"Reading {src} ...") - tiles, vert_count, verts = read_v1(src) - print(f" tiles={CHUNK_TILE_COUNT}, vertices={vert_count}") + tiles, meshes = read_dcf(src) + print( + f" tiles={CHUNK_TILE_COUNT}, " + f"meshes={len(meshes)}, " + f"total_verts=" + f"{sum(len(m) // VERTEX_SIZE for m in meshes)}" + ) + + # Derive chunk base name from the DCF filename (e.g. "0_0_0" from + # "0_0_0.dcf") to name DMF files "chunk_0_0_0_0.dmf" etc. + base = os.path.splitext(os.path.basename(dst))[0] + + # assets/meshes/ sits beside assets/chunks/ (one dir up from the DCF). + meshes_dir = os.path.normpath( + os.path.join(os.path.dirname(os.path.abspath(dst)), '..', 'meshes') + ) + os.makedirs(meshes_dir, exist_ok=True) + + print(f"Writing DMF files to {meshes_dir} ...") + mesh_names = [] + for idx, verts in enumerate(meshes): + dmf_filename = f'chunk_{base}_{idx}.dmf' + dmf_path = os.path.join(meshes_dir, dmf_filename) + write_dmf(dmf_path, verts) + mesh_names.append(f'meshes/{dmf_filename}') print(f"Writing {dst} ...") - write_v2(dst, tiles, vert_count, verts) + write_v3(dst, tiles, mesh_names) if __name__ == '__main__': diff --git a/tools/asset/chunk/hillgen.py b/tools/asset/chunk/hillgen.py deleted file mode 100644 index 13ec593c..00000000 --- a/tools/asset/chunk/hillgen.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright (c) 2026 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -""" -Generates chunk 0_0_0.dcf with a small hill in the centre. - -Hill layout (tile coordinates, 0-based): - y=5: . . . . . . N N . . . . . . . . RAMP_NORTH (south slope) - y=6: . . . . . E H H W . . . . . . . Hill top (H), RAMP_EAST/WEST - y=7: . . . . . E H H W . . . . . . . - y=8: . . . . . . S S . . . . . . . . RAMP_SOUTH (north slope) - x=6 x=7 -""" - -import struct, os - -# Must match src/dusk/rpg/overworld/chunk.h and tile.h -CHUNK_WIDTH = 16 -CHUNK_HEIGHT = 16 -CHUNK_DEPTH = 32 -CHUNK_W_F = float(CHUNK_WIDTH) - -TILE_NULL = 0 -TILE_GROUND = 1 -TILE_RAMP_NORTH = 2 -TILE_RAMP_SOUTH = 3 -TILE_RAMP_EAST = 4 -TILE_RAMP_WEST = 5 - -TILE_SIZE = 4 # sizeof(tile_t) = sizeof(int) -VERT_SIZE = 20 # sizeof(meshvertex_t): uv[2] + pos[3] floats -FILE_VER = 2 - -# Hill geometry parameters -HILL_X = frozenset({6, 7}) -HILL_Y = frozenset({6, 7}) -HILL_H = 1.0 - - -def tile_idx(cx, cy, cz): - return cz * CHUNK_WIDTH * CHUNK_HEIGHT + cy * CHUNK_WIDTH + cx - - -def make_vert(u, v, px, py, pz): - return struct.pack('<5f', u, v, px, py, pz) - - -def quad_verts(cx, cy, z_sw, z_se, z_ne, z_nw): - """ - Build 6 vertices (2 triangles) for a tile quad. - Heights at each corner: SW=south-west, SE=south-east, - NE=north-east, NW=north-west. - UV formula (verified against existing DCF data): - u = (cy + within_x) / CHUNK_WIDTH where within_x in {0,1} - v = (cx + within_y) / CHUNK_HEIGHT where within_y in {0,1} - """ - u0 = cy / CHUNK_W_F - u1 = (cy + 1) / CHUNK_W_F - v0 = cx / CHUNK_W_F - v1 = (cx + 1) / CHUNK_W_F - x0, x1 = float(cx), float(cx + 1) - y0, y1 = float(cy), float(cy + 1) - - SW = make_vert(u0, v0, x0, y0, float(z_sw)) - SE = make_vert(u1, v0, x1, y0, float(z_se)) - NE = make_vert(u1, v1, x1, y1, float(z_ne)) - NW = make_vert(u0, v1, x0, y1, float(z_nw)) - - return SW + SE + NE + SW + NE + NW - - -def flat(cx, cy, z): - return quad_verts(cx, cy, z, z, z, z) - - -def ramp_north(cx, cy): - return quad_verts(cx, cy, 0, 0, HILL_H, HILL_H) - - -def ramp_south(cx, cy): - return quad_verts(cx, cy, HILL_H, HILL_H, 0, 0) - - -def ramp_east(cx, cy): - return quad_verts(cx, cy, 0, HILL_H, HILL_H, 0) - - -def ramp_west(cx, cy): - return quad_verts(cx, cy, HILL_H, 0, 0, HILL_H) - - -def generate(): - tiles = [TILE_GROUND] * (CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH) - - ramps_n = frozenset((cx, 5) for cx in HILL_X) - ramps_s = frozenset((cx, 8) for cx in HILL_X) - ramps_e = frozenset((5, cy) for cy in HILL_Y) - ramps_w = frozenset((8, cy) for cy in HILL_Y) - - for cx, cy in ramps_n: - tiles[tile_idx(cx, cy, 0)] = TILE_RAMP_NORTH - for cx, cy in ramps_s: - tiles[tile_idx(cx, cy, 0)] = TILE_RAMP_SOUTH - for cx, cy in ramps_e: - tiles[tile_idx(cx, cy, 0)] = TILE_RAMP_EAST - for cx, cy in ramps_w: - tiles[tile_idx(cx, cy, 0)] = TILE_RAMP_WEST - - for cx in HILL_X: - for cy in HILL_Y: - tiles[tile_idx(cx, cy, 1)] = TILE_GROUND - - verts = bytearray() - - for cx in range(CHUNK_WIDTH): - for cy in range(CHUNK_HEIGHT): - pos = (cx, cy) - if cx in HILL_X and cy in HILL_Y: - continue - if pos in ramps_n: - verts += ramp_north(cx, cy) - elif pos in ramps_s: - verts += ramp_south(cx, cy) - elif pos in ramps_e: - verts += ramp_east(cx, cy) - elif pos in ramps_w: - verts += ramp_west(cx, cy) - else: - verts += flat(cx, cy, 0) - - for cx in sorted(HILL_X): - for cy in sorted(HILL_Y): - verts += flat(cx, cy, HILL_H) - - vert_count = len(verts) // VERT_SIZE - tile_bytes = struct.pack(f'<{len(tiles)}i', *tiles) - - buf = bytearray() - buf += b'DCF\x00' - buf += struct.pack(' + Reads raw vertex bytes from vertices.bin and writes a DMF file. +""" + +import struct +import sys +import os + +MAGIC = b'DMF\x00' +VERSION = 1 +VERTEX_SIZE = 20 + + +def write_dmf(path, vertex_bytes): + if len(vertex_bytes) % VERTEX_SIZE != 0: + raise ValueError( + f"Vertex data size {len(vertex_bytes)} is not a " + f"multiple of {VERTEX_SIZE}" + ) + vert_count = len(vertex_bytes) // VERTEX_SIZE + buf = bytearray() + buf += MAGIC + buf += struct.pack(' " + ) + sys.exit(1) + + dst = args[0] + src = args[1] + + with open(src, 'rb') as f: + vertex_bytes = f.read() + + write_dmf(dst, vertex_bytes) + + +if __name__ == '__main__': + main()