CHUNK STUFF
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -16,3 +16,4 @@ add_subdirectory(display)
|
|||||||
add_subdirectory(locale)
|
add_subdirectory(locale)
|
||||||
add_subdirectory(json)
|
add_subdirectory(json)
|
||||||
add_subdirectory(chunk)
|
add_subdirectory(chunk)
|
||||||
|
add_subdirectory(dmf)
|
||||||
@@ -45,4 +45,10 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
|
|||||||
.loadAsync = assetChunkLoaderAsync,
|
.loadAsync = assetChunkLoaderAsync,
|
||||||
.dispose = assetChunkDispose
|
.dispose = assetChunkDispose
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[ASSET_LOADER_TYPE_DMF] = {
|
||||||
|
.loadSync = assetDmfLoaderSync,
|
||||||
|
.loadAsync = assetDmfLoaderAsync,
|
||||||
|
.dispose = assetDmfDispose
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "asset/loader/locale/assetlocaleloader.h"
|
#include "asset/loader/locale/assetlocaleloader.h"
|
||||||
#include "asset/loader/json/assetjsonloader.h"
|
#include "asset/loader/json/assetjsonloader.h"
|
||||||
#include "asset/loader/chunk/assetchunkloader.h"
|
#include "asset/loader/chunk/assetchunkloader.h"
|
||||||
|
#include "asset/loader/dmf/assetdmfloader.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
ASSET_LOADER_TYPE_NULL,
|
ASSET_LOADER_TYPE_NULL,
|
||||||
@@ -22,6 +23,7 @@ typedef enum {
|
|||||||
ASSET_LOADER_TYPE_LOCALE,
|
ASSET_LOADER_TYPE_LOCALE,
|
||||||
ASSET_LOADER_TYPE_JSON,
|
ASSET_LOADER_TYPE_JSON,
|
||||||
ASSET_LOADER_TYPE_CHUNK,
|
ASSET_LOADER_TYPE_CHUNK,
|
||||||
|
ASSET_LOADER_TYPE_DMF,
|
||||||
|
|
||||||
ASSET_LOADER_TYPE_COUNT
|
ASSET_LOADER_TYPE_COUNT
|
||||||
} assetloadertype_t;
|
} assetloadertype_t;
|
||||||
@@ -33,6 +35,7 @@ typedef union {
|
|||||||
assetlocaleloaderinput_t locale;
|
assetlocaleloaderinput_t locale;
|
||||||
assetjsonloaderinput_t json;
|
assetjsonloaderinput_t json;
|
||||||
assetchunkloaderinput_t chunk;
|
assetchunkloaderinput_t chunk;
|
||||||
|
assetdmfloaderinput_t dmf;
|
||||||
} assetloaderinput_t;
|
} assetloaderinput_t;
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
@@ -42,6 +45,7 @@ typedef union {
|
|||||||
assetlocaleloaderloading_t locale;
|
assetlocaleloaderloading_t locale;
|
||||||
assetjsonloaderloading_t json;
|
assetjsonloaderloading_t json;
|
||||||
assetchunkloaderloading_t chunk;
|
assetchunkloaderloading_t chunk;
|
||||||
|
assetdmfloaderloading_t dmf;
|
||||||
} assetloaderloading_t;
|
} assetloaderloading_t;
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
@@ -51,6 +55,7 @@ typedef union {
|
|||||||
assetlocaleoutput_t locale;
|
assetlocaleoutput_t locale;
|
||||||
assetjsonoutput_t json;
|
assetjsonoutput_t json;
|
||||||
assetchunkoutput_t chunk;
|
assetchunkoutput_t chunk;
|
||||||
|
assetdmfoutput_t dmf;
|
||||||
} assetloaderoutput_t;
|
} assetloaderoutput_t;
|
||||||
|
|
||||||
typedef struct assetloading_s assetloading_t;
|
typedef struct assetloading_s assetloading_t;
|
||||||
|
|||||||
@@ -92,22 +92,22 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) {
|
|||||||
"Chunk mesh count exceeds maximum."
|
"Chunk mesh count exceeds maximum."
|
||||||
);
|
);
|
||||||
|
|
||||||
uint32_t poolOffset = 0;
|
|
||||||
for(uint8_t m = 0; m < out->meshCount; m++) {
|
for(uint8_t m = 0; m < out->meshCount; m++) {
|
||||||
uint32_t vertCount = endianLittleToHost32(*(uint32_t *)(data + offset));
|
uint8_t nameLen = 0;
|
||||||
offset += sizeof(uint32_t);
|
while(
|
||||||
assertTrue(
|
data[offset + nameLen] != '\0' &&
|
||||||
poolOffset + vertCount <= CHUNK_VERTEX_COUNT,
|
nameLen < CHUNK_MESH_NAME_MAX - 1
|
||||||
"Chunk vertex data exceeds pool."
|
) {
|
||||||
);
|
nameLen++;
|
||||||
out->meshVertCounts[m] = vertCount;
|
}
|
||||||
|
memoryCopy(out->meshNames[m], data + offset, nameLen);
|
||||||
|
out->meshNames[m][nameLen] = '\0';
|
||||||
|
offset += nameLen + 1;
|
||||||
|
|
||||||
memoryCopy(
|
memoryCopy(
|
||||||
&out->vertices[poolOffset],
|
out->meshOffsets[m], data + offset, sizeof(vec3)
|
||||||
data + offset,
|
|
||||||
vertCount * sizeof(meshvertex_t)
|
|
||||||
);
|
);
|
||||||
offset += vertCount * sizeof(meshvertex_t);
|
offset += sizeof(vec3);
|
||||||
poolOffset += vertCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryFree(data);
|
memoryFree(data);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "asset/assetfile.h"
|
#include "asset/assetfile.h"
|
||||||
#include "rpg/overworld/chunk.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 assetloading_s assetloading_t;
|
||||||
typedef struct assetentry_s assetentry_t;
|
typedef struct assetentry_s assetentry_t;
|
||||||
@@ -34,8 +34,8 @@ typedef struct {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
tile_t tiles[CHUNK_TILE_COUNT];
|
tile_t tiles[CHUNK_TILE_COUNT];
|
||||||
uint8_t meshCount;
|
uint8_t meshCount;
|
||||||
uint32_t meshVertCounts[CHUNK_MESH_COUNT_MAX];
|
char_t meshNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX];
|
||||||
meshvertex_t vertices[CHUNK_VERTEX_COUNT];
|
vec3 meshOffsets[CHUNK_MESH_COUNT_MAX];
|
||||||
} assetchunkoutput_t;
|
} assetchunkoutput_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +50,8 @@ errorret_t assetChunkLoaderAsync(assetloading_t *loading);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronous loader for chunk assets. Validates the DCF binary previously
|
* 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.
|
* @param loading Loading information for the asset being loaded.
|
||||||
* @return Error code indicating success or failure of the load operation.
|
* @return Error code indicating success or failure of the load operation.
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -8,21 +8,21 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "rpg/overworld/tile.h"
|
#include "rpg/overworld/tile.h"
|
||||||
#include "worldpos.h"
|
#include "worldpos.h"
|
||||||
#include "display/mesh/mesh.h"
|
|
||||||
#include "display/spritebatch/spritebatch.h"
|
|
||||||
|
|
||||||
#define CHUNK_MESH_COUNT_MAX 10
|
#define CHUNK_MESH_COUNT_MAX 10
|
||||||
#define CHUNK_VERTEX_COUNT 8192
|
#define CHUNK_MESH_NAME_MAX 64
|
||||||
#define CHUNK_ENTITY_COUNT_MAX 10
|
#define CHUNK_ENTITY_COUNT_MAX 10
|
||||||
|
|
||||||
|
typedef struct assetentry_s assetentry_t;
|
||||||
|
|
||||||
typedef struct chunk_s {
|
typedef struct chunk_s {
|
||||||
chunkpos_t position;
|
chunkpos_t position;
|
||||||
tile_t tiles[CHUNK_TILE_COUNT];
|
tile_t tiles[CHUNK_TILE_COUNT];
|
||||||
|
|
||||||
uint8_t meshCount;
|
uint8_t meshCount;
|
||||||
uint32_t meshVertCounts[CHUNK_MESH_COUNT_MAX];
|
char_t meshNames[CHUNK_MESH_COUNT_MAX][CHUNK_MESH_NAME_MAX];
|
||||||
meshvertex_t vertices[CHUNK_VERTEX_COUNT];
|
vec3 meshOffsets[CHUNK_MESH_COUNT_MAX];
|
||||||
mesh_t meshes[CHUNK_MESH_COUNT_MAX];
|
assetentry_t *meshEntries[CHUNK_MESH_COUNT_MAX];
|
||||||
|
|
||||||
uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
|
uint8_t entities[CHUNK_ENTITY_COUNT_MAX];
|
||||||
} chunk_t;
|
} chunk_t;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
|
#include "asset/loader/assetloader.h"
|
||||||
#include "rpg/entity/entity.h"
|
#include "rpg/entity/entity.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
|
||||||
@@ -17,20 +18,6 @@ map_t MAP;
|
|||||||
errorret_t mapInit() {
|
errorret_t mapInit() {
|
||||||
memoryZero(&MAP, sizeof(map_t));
|
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;
|
MAP.loaded = true;
|
||||||
int32_t i = 0;
|
int32_t i = 0;
|
||||||
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
for(chunkunit_t z = 0; z < MAP_CHUNK_DEPTH; z++) {
|
||||||
@@ -54,61 +41,6 @@ bool_t mapIsLoaded() {
|
|||||||
return MAP.loaded;
|
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) {
|
errorret_t mapPositionSet(const chunkpos_t newPos) {
|
||||||
if(!mapIsLoaded()) errorThrow("No map loaded");
|
if(!mapIsLoaded()) errorThrow("No map loaded");
|
||||||
|
|
||||||
@@ -117,7 +49,6 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine which chunks remain loaded
|
|
||||||
chunkindex_t chunksRemaining[MAP_CHUNK_COUNT] = {0};
|
chunkindex_t chunksRemaining[MAP_CHUNK_COUNT] = {0};
|
||||||
chunkindex_t chunksFreed[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;
|
uint32_t freedCount = 0;
|
||||||
|
|
||||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
// Will this chunk remain loaded?
|
|
||||||
chunk_t *chunk = &MAP.chunks[i];
|
chunk_t *chunk = &MAP.chunks[i];
|
||||||
if(
|
if(
|
||||||
chunk->position.x >= newPos.x &&
|
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 &&
|
||||||
chunk->position.z < newPos.z + MAP_CHUNK_DEPTH
|
chunk->position.z < newPos.z + MAP_CHUNK_DEPTH
|
||||||
) {
|
) {
|
||||||
// Stays loaded
|
|
||||||
chunksRemaining[remainingCount++] = i;
|
chunksRemaining[remainingCount++] = i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not remaining loaded
|
|
||||||
chunksFreed[freedCount++] = i;
|
chunksFreed[freedCount++] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unload the freed chunks
|
|
||||||
for(chunkindex_t i = 0; i < freedCount; i++) {
|
for(chunkindex_t i = 0; i < freedCount; i++) {
|
||||||
chunk_t *chunk = &MAP.chunks[chunksFreed[i]];
|
chunk_t *chunk = &MAP.chunks[chunksFreed[i]];
|
||||||
mapChunkUnload(chunk);
|
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;
|
chunkindex_t orderIndex = 0;
|
||||||
for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) {
|
for(chunkunit_t zOff = 0; zOff < MAP_CHUNK_DEPTH; zOff++) {
|
||||||
for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) {
|
for(chunkunit_t yOff = 0; yOff < MAP_CHUNK_HEIGHT; yOff++) {
|
||||||
@@ -162,7 +87,6 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
|||||||
newPos.x + xOff, newPos.y + yOff, newPos.z + zOff
|
newPos.x + xOff, newPos.y + yOff, newPos.z + zOff
|
||||||
};
|
};
|
||||||
|
|
||||||
// Is this chunk already loaded (was not unloaded earlier)?
|
|
||||||
chunkindex_t chunkIndex = -1;
|
chunkindex_t chunkIndex = -1;
|
||||||
for(chunkindex_t i = 0; i < remainingCount; i++) {
|
for(chunkindex_t i = 0; i < remainingCount; i++) {
|
||||||
chunk_t *chunk = &MAP.chunks[chunksRemaining[i]];
|
chunk_t *chunk = &MAP.chunks[chunksRemaining[i]];
|
||||||
@@ -171,9 +95,7 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to load this chunk
|
|
||||||
if(chunkIndex == -1) {
|
if(chunkIndex == -1) {
|
||||||
// Find a freed chunk to reuse
|
|
||||||
chunkIndex = chunksFreed[--freedCount];
|
chunkIndex = chunksFreed[--freedCount];
|
||||||
chunk_t *chunk = &MAP.chunks[chunkIndex];
|
chunk_t *chunk = &MAP.chunks[chunkIndex];
|
||||||
chunk->position = newChunkPos;
|
chunk->position = newChunkPos;
|
||||||
@@ -185,7 +107,6 @@ errorret_t mapPositionSet(const chunkpos_t newPos) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update map position
|
|
||||||
MAP.chunkPosition = newPos;
|
MAP.chunkPosition = newPos;
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
@@ -198,9 +119,6 @@ void mapUpdate() {
|
|||||||
errorret_t mapDispose() {
|
errorret_t mapDispose() {
|
||||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
mapChunkUnload(&MAP.chunks[i]);
|
mapChunkUnload(&MAP.chunks[i]);
|
||||||
for(uint8_t j = 0; j < CHUNK_MESH_COUNT_MAX; j++) {
|
|
||||||
errorChain(meshDispose(&MAP.chunks[i].meshes[j]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
@@ -220,6 +138,12 @@ void mapChunkUnload(chunk_t* chunk) {
|
|||||||
entity->type = ENTITY_TYPE_NULL;
|
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;
|
chunk->meshCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,25 +177,42 @@ errorret_t mapChunkLoad(chunk_t* chunk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
memoryCopy(chunk->tiles, entry->data.chunk.tiles, sizeof(chunk->tiles));
|
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;
|
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);
|
assetUnlockEntry(entry);
|
||||||
|
|
||||||
if(meshCount == 0) errorOk();
|
|
||||||
chunk->meshCount = meshCount;
|
|
||||||
for(uint8_t m = 0; m < meshCount; m++) {
|
for(uint8_t m = 0; m < meshCount; m++) {
|
||||||
if(chunk->meshVertCounts[m] == 0) continue;
|
assetentry_t *meshEntry = assetLock(
|
||||||
errorChain(meshFlush(
|
chunk->meshNames[m], ASSET_LOADER_TYPE_DMF, NULL
|
||||||
&chunk->meshes[m], 0, (int32_t)chunk->meshVertCounts[m]
|
);
|
||||||
));
|
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();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
#include "rpg/overworld/chunk.h"
|
#include "rpg/overworld/chunk.h"
|
||||||
|
|
||||||
#define MAP_FILE_PATH_MAX 128
|
#define MAP_FILE_PATH_MAX 128
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
#include "rpg/entity/entity.h"
|
#include "rpg/entity/entity.h"
|
||||||
#include "rpg/rpgcamera.h"
|
#include "rpg/rpgcamera.h"
|
||||||
|
|
||||||
|
#include "asset/loader/assetloader.h"
|
||||||
|
#include "asset/loader/assetentry.h"
|
||||||
|
|
||||||
#define TEXTURE_CHUNK_SIZE 16
|
#define TEXTURE_CHUNK_SIZE 16
|
||||||
|
|
||||||
static texture_t TEXTURE_CHUNK;
|
static texture_t TEXTURE_CHUNK;
|
||||||
@@ -140,34 +143,32 @@ 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++) {
|
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
||||||
chunk_t *chunk = &MAP.chunks[i];
|
chunk_t *chunk = &MAP.chunks[i];
|
||||||
if(chunk->meshCount == 0) continue;
|
if(chunk->meshCount == 0) continue;
|
||||||
if(chunk->meshVertCounts[0] == 0) continue;
|
|
||||||
errorChain(meshDraw(
|
worldpos_t wp;
|
||||||
&chunk->meshes[0], 0, (int32_t)chunk->meshVertCounts[0]
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 2: draw each chunk's additional meshes (indices 1..meshCount-1).
|
// Restore identity model so subsequent renders (e.g. entities) are
|
||||||
// Vertices are packed sequentially in the pool, so accumulate the offset.
|
// not affected by the last chunk transform.
|
||||||
for(chunkindex_t i = 0; i < MAP_CHUNK_COUNT; i++) {
|
mat4 identity;
|
||||||
chunk_t *chunk = &MAP.chunks[i];
|
glm_mat4_identity(identity);
|
||||||
uint32_t vertOffset = chunk->meshVertCounts[0];
|
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, identity));
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|||||||
+109
-33
@@ -4,7 +4,8 @@
|
|||||||
# https://opensource.org/licenses/MIT
|
# 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):
|
Version 1 format (after 8-byte header + tiles):
|
||||||
uint32_t vertCount
|
uint32_t vertCount
|
||||||
@@ -16,16 +17,27 @@ Version 2 format (after 8-byte header + tiles):
|
|||||||
uint32_t vertCount
|
uint32_t vertCount
|
||||||
meshvertex_t vertices[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:
|
Usage:
|
||||||
python3 -m tools.asset.chunk <input.dcf> [output.dcf]
|
python3 -m tools.asset.chunk <input.dcf> [output.dcf]
|
||||||
If output is omitted the input file is updated in place.
|
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 struct
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Must match src/dusk/rpg/overworld/chunk.h
|
|
||||||
CHUNK_WIDTH = 16
|
CHUNK_WIDTH = 16
|
||||||
CHUNK_HEIGHT = 16
|
CHUNK_HEIGHT = 16
|
||||||
CHUNK_DEPTH = 32
|
CHUNK_DEPTH = 32
|
||||||
@@ -33,17 +45,20 @@ CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH # 8192
|
|||||||
|
|
||||||
CHUNK_MESH_COUNT_MAX = 10
|
CHUNK_MESH_COUNT_MAX = 10
|
||||||
CHUNK_VERTEX_COUNT = 8192
|
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
|
TILE_SIZE = 4
|
||||||
VERTEX_SIZE = 20 # 2 floats UV + 3 floats pos, MESH_ENABLE_COLOR=0
|
VERTEX_SIZE = 20
|
||||||
|
|
||||||
FILE_MAGIC = b'DCF'
|
FILE_MAGIC = b'DCF'
|
||||||
VERSION_IN = 1
|
DMF_MAGIC = b'DMF\x00'
|
||||||
VERSION_OUT = 2
|
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:
|
with open(path, 'rb') as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
|
|
||||||
@@ -51,34 +66,66 @@ def read_v1(path):
|
|||||||
raise ValueError(f"{path}: not a DCF file")
|
raise ValueError(f"{path}: not a DCF file")
|
||||||
|
|
||||||
version = struct.unpack_from('<I', data, 4)[0]
|
version = struct.unpack_from('<I', data, 4)[0]
|
||||||
if version != VERSION_IN:
|
if version not in (1, 2):
|
||||||
raise ValueError(f"{path}: expected version {VERSION_IN}, got {version}")
|
raise ValueError(
|
||||||
|
f"{path}: expected version 1 or 2, got {version}"
|
||||||
|
)
|
||||||
|
|
||||||
offset = 8
|
offset = 8
|
||||||
tiles_bytes = CHUNK_TILE_COUNT * TILE_SIZE
|
tiles_size = CHUNK_TILE_COUNT * TILE_SIZE
|
||||||
tiles = data[offset:offset + tiles_bytes]
|
tiles = data[offset:offset + tiles_size]
|
||||||
offset += tiles_bytes
|
offset += tiles_size
|
||||||
|
|
||||||
|
meshes = []
|
||||||
|
|
||||||
|
if version == 1:
|
||||||
vert_count = struct.unpack_from('<I', data, offset)[0]
|
vert_count = struct.unpack_from('<I', data, offset)[0]
|
||||||
offset += 4
|
offset += 4
|
||||||
|
|
||||||
verts = data[offset:offset + vert_count * VERTEX_SIZE]
|
verts = data[offset:offset + vert_count * VERTEX_SIZE]
|
||||||
if len(verts) != vert_count * VERTEX_SIZE:
|
if len(verts) != vert_count * VERTEX_SIZE:
|
||||||
raise ValueError(f"{path}: truncated vertex data")
|
raise ValueError(f"{path}: truncated vertex data")
|
||||||
|
if vert_count > 0:
|
||||||
|
meshes.append(verts)
|
||||||
|
else:
|
||||||
|
mesh_count = data[offset]
|
||||||
|
offset += 1
|
||||||
|
for _ in range(mesh_count):
|
||||||
|
vert_count = struct.unpack_from('<I', data, offset)[0]
|
||||||
|
offset += 4
|
||||||
|
verts = data[offset:offset + vert_count * VERTEX_SIZE]
|
||||||
|
if len(verts) != vert_count * VERTEX_SIZE:
|
||||||
|
raise ValueError(f"{path}: truncated vertex data")
|
||||||
|
offset += vert_count * VERTEX_SIZE
|
||||||
|
if vert_count > 0:
|
||||||
|
meshes.append(verts)
|
||||||
|
|
||||||
return tiles, vert_count, verts
|
return tiles, meshes
|
||||||
|
|
||||||
|
|
||||||
def write_v2(path, tiles, vert_count, verts):
|
def write_dmf(path, vertex_bytes):
|
||||||
if vert_count > CHUNK_VERTEX_COUNT:
|
vert_count = len(vertex_bytes) // VERTEX_SIZE
|
||||||
|
buf = bytearray()
|
||||||
|
buf += DMF_MAGIC
|
||||||
|
buf += struct.pack('<I', DMF_VERSION)
|
||||||
|
buf += struct.pack('<I', vert_count)
|
||||||
|
buf += vertex_bytes
|
||||||
|
with open(path, 'wb') as f:
|
||||||
|
f.write(buf)
|
||||||
print(
|
print(
|
||||||
f" Warning: {vert_count} vertices exceeds pool "
|
f' Wrote DMF {path}: '
|
||||||
f"({CHUNK_VERTEX_COUNT}); truncating."
|
f'{vert_count} vertices, {len(buf)} bytes'
|
||||||
)
|
)
|
||||||
vert_count = CHUNK_VERTEX_COUNT
|
|
||||||
verts = verts[:vert_count * VERTEX_SIZE]
|
|
||||||
|
|
||||||
mesh_count = 1 if vert_count > 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 = bytearray()
|
||||||
buf += FILE_MAGIC
|
buf += FILE_MAGIC
|
||||||
@@ -86,34 +133,63 @@ def write_v2(path, tiles, vert_count, verts):
|
|||||||
buf += struct.pack('<I', VERSION_OUT)
|
buf += struct.pack('<I', VERSION_OUT)
|
||||||
buf += tiles
|
buf += tiles
|
||||||
buf += struct.pack('<B', mesh_count)
|
buf += struct.pack('<B', mesh_count)
|
||||||
if mesh_count > 0:
|
for name, offset in zip(mesh_names, mesh_offsets):
|
||||||
buf += struct.pack('<I', vert_count)
|
encoded = name.encode('ascii')
|
||||||
buf += verts
|
if len(encoded) >= CHUNK_MESH_NAME_MAX:
|
||||||
|
raise ValueError(
|
||||||
with open(path, 'wb') as f:
|
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)
|
f.write(buf)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f" Wrote {path}: version {VERSION_OUT}, "
|
f' Wrote DCF {dcf_path}: '
|
||||||
f"{mesh_count} mesh(es), {vert_count} vertices."
|
f'version {VERSION_OUT}, {mesh_count} mesh(es)'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
if not args:
|
if not args:
|
||||||
print("Usage: python3 -m tools.asset.chunk <input.dcf> [output.dcf]")
|
print(
|
||||||
|
"Usage: python3 -m tools.asset.chunk "
|
||||||
|
"<input.dcf> [output.dcf]"
|
||||||
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
src = args[0]
|
src = args[0]
|
||||||
dst = args[1] if len(args) > 1 else src
|
dst = args[1] if len(args) > 1 else src
|
||||||
|
|
||||||
print(f"Reading {src} ...")
|
print(f"Reading {src} ...")
|
||||||
tiles, vert_count, verts = read_v1(src)
|
tiles, meshes = read_dcf(src)
|
||||||
print(f" tiles={CHUNK_TILE_COUNT}, vertices={vert_count}")
|
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} ...")
|
print(f"Writing {dst} ...")
|
||||||
write_v2(dst, tiles, vert_count, verts)
|
write_v3(dst, tiles, mesh_names)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -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('<I', FILE_VER)
|
|
||||||
buf += tile_bytes
|
|
||||||
buf += struct.pack('<B', 1)
|
|
||||||
buf += struct.pack('<I', vert_count)
|
|
||||||
buf += verts
|
|
||||||
|
|
||||||
return buf, vert_count
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
out = os.path.join(
|
|
||||||
os.path.dirname(__file__), '..', '..', '..', 'assets', 'chunks',
|
|
||||||
'0_0_0.dcf'
|
|
||||||
)
|
|
||||||
out = os.path.normpath(out)
|
|
||||||
buf, vert_count = generate()
|
|
||||||
with open(out, 'wb') as f:
|
|
||||||
f.write(buf)
|
|
||||||
print(f'Wrote {out}: {vert_count} vertices, {len(buf)} bytes')
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Copyright (c) 2026 Dominic Masters
|
||||||
|
#
|
||||||
|
# This software is released under the MIT License.
|
||||||
|
# https://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Writes DMF (Dusk Mesh Format) files.
|
||||||
|
|
||||||
|
DMF format:
|
||||||
|
Bytes 0-3: DMF\x00 (magic)
|
||||||
|
Bytes 4-7: uint32_t version = 1 (little-endian)
|
||||||
|
Bytes 8-11: uint32_t vertCount (little-endian)
|
||||||
|
Bytes 12+: meshvertex_t vertices[vertCount]
|
||||||
|
Each vertex is 20 bytes: uv[2] + pos[3] (5 floats, LE)
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 -m tools.asset.dmf <output.dmf> <vertices.bin>
|
||||||
|
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('<I', VERSION)
|
||||||
|
buf += struct.pack('<I', vert_count)
|
||||||
|
buf += vertex_bytes
|
||||||
|
with open(path, 'wb') as f:
|
||||||
|
f.write(buf)
|
||||||
|
print(
|
||||||
|
f'Wrote {path}: {vert_count} vertices, {len(buf)} bytes'
|
||||||
|
)
|
||||||
|
return vert_count
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = sys.argv[1:]
|
||||||
|
if len(args) != 2:
|
||||||
|
print(
|
||||||
|
"Usage: python3 -m tools.asset.dmf "
|
||||||
|
"<output.dmf> <vertices.bin>"
|
||||||
|
)
|
||||||
|
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()
|
||||||
Reference in New Issue
Block a user