|
|
|
@@ -8,41 +8,288 @@
|
|
|
|
|
#include "assetchunkloader.h"
|
|
|
|
|
#include "assert/assert.h"
|
|
|
|
|
#include "util/memory.h"
|
|
|
|
|
#include "util/endian.h"
|
|
|
|
|
#include "util/string.h"
|
|
|
|
|
#include "asset/asset.h"
|
|
|
|
|
#include "asset/loader/assetloading.h"
|
|
|
|
|
#include "asset/loader/assetentry.h"
|
|
|
|
|
#include "asset/loader/assetloader.h"
|
|
|
|
|
#include "asset/asset.h"
|
|
|
|
|
#include "asset/loader/json/assetjsonloader.h"
|
|
|
|
|
#include "display/color.h"
|
|
|
|
|
#include "display/texture/texture.h"
|
|
|
|
|
#include "rpg/overworld/worldpos.h"
|
|
|
|
|
#include "yyjson.h"
|
|
|
|
|
|
|
|
|
|
#define ASSET_CHUNK_TERRAIN_TEXTURE "tiles.png"
|
|
|
|
|
|
|
|
|
|
// Per-shape corner Z offsets (sw, se, ne, nw) as offsets from a tile's own
|
|
|
|
|
// base Z, ported 1:1 from tools/asset/chunk/__main__.py's _RAMP_CORNERS.
|
|
|
|
|
// TILE_SHAPE_NULL's row is never read - assetChunkTilesHaveTerrain/
|
|
|
|
|
// assetChunkBuildTerrain skip those tiles outright.
|
|
|
|
|
static const float_t CHUNK_RAMP_CORNERS[TILE_SHAPE_COUNT][4] = {
|
|
|
|
|
[TILE_SHAPE_GROUND] = {0.0f, 0.0f, 0.0f, 0.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_NORTH] = {0.0f, 0.0f, 1.0f, 1.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_SOUTH] = {1.0f, 1.0f, 0.0f, 0.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_EAST] = {0.0f, 1.0f, 1.0f, 0.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_WEST] = {1.0f, 0.0f, 0.0f, 1.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_NORTHEAST] = {0.0f, 0.0f, 1.0f, 0.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_NORTHWEST] = {0.0f, 0.0f, 0.0f, 1.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_SOUTHEAST] = {0.0f, 1.0f, 0.0f, 0.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_SOUTHWEST] = {1.0f, 0.0f, 0.0f, 0.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_NORTHEAST_INNER] = {0.0f, 1.0f, 1.0f, 1.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_NORTHWEST_INNER] = {1.0f, 0.0f, 1.0f, 1.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_SOUTHEAST_INNER] = {1.0f, 1.0f, 1.0f, 0.0f},
|
|
|
|
|
[TILE_SHAPE_RAMP_SOUTHWEST_INNER] = {1.0f, 1.0f, 0.0f, 1.0f},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
errorret_t assetChunkLoaderAsync(assetloading_t *loading) {
|
|
|
|
|
assertNotNull(loading, "Loading cannot be NULL");
|
|
|
|
|
assertNotMainThread("Should be called from an async thread.");
|
|
|
|
|
errorOk();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(loading->loading.chunk.state != ASSET_CHUNK_LOADING_STATE_READ_FILE) {
|
|
|
|
|
errorOk();
|
|
|
|
|
static bool_t assetChunkTilesHaveTerrain(const tile_t *tiles) {
|
|
|
|
|
for(uint32_t i = 0; i < CHUNK_TILE_COUNT; i++) {
|
|
|
|
|
if(tiles[i].shape != TILE_SHAPE_NULL) return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reads the "tiles" array - one quad's worth of column data per entry,
|
|
|
|
|
// last one in the array wins per (x, y) column - matching
|
|
|
|
|
// tools/asset/chunk/__main__.py:390-403.
|
|
|
|
|
static void assetChunkParseTiles(yyjson_val *root, tile_t *tiles) {
|
|
|
|
|
memoryZero(tiles, CHUNK_TILE_COUNT * sizeof(tile_t));
|
|
|
|
|
|
|
|
|
|
yyjson_val *tilesArr = yyjson_obj_get(root, "tiles");
|
|
|
|
|
if(!yyjson_is_arr(tilesArr)) return;
|
|
|
|
|
|
|
|
|
|
size_t idx, len;
|
|
|
|
|
yyjson_val *tileVal;
|
|
|
|
|
yyjson_arr_foreach(tilesArr, idx, len, tileVal) {
|
|
|
|
|
yyjson_val *posArr = yyjson_obj_get(tileVal, "pos");
|
|
|
|
|
if(!yyjson_is_arr(posArr) || yyjson_arr_size(posArr) < 3) continue;
|
|
|
|
|
|
|
|
|
|
int32_t x = (int32_t)yyjson_get_int(yyjson_arr_get(posArr, 0));
|
|
|
|
|
int32_t y = (int32_t)yyjson_get_int(yyjson_arr_get(posArr, 1));
|
|
|
|
|
int32_t z = (int32_t)yyjson_get_int(yyjson_arr_get(posArr, 2));
|
|
|
|
|
if(x < 0 || x >= CHUNK_WIDTH || y < 0 || y >= CHUNK_HEIGHT) continue;
|
|
|
|
|
|
|
|
|
|
int32_t type = (int32_t)yyjson_get_int(yyjson_obj_get(tileVal, "type"));
|
|
|
|
|
tiles[x + y * CHUNK_WIDTH] = (tile_t){
|
|
|
|
|
.shape = (tileshape_t)type, .z = (uint8_t)z
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resolves "meshes" (external building references, by bare filename) into
|
|
|
|
|
// out->modelNames/meshOffsets starting at *ioIndex, advancing it as
|
|
|
|
|
// entries are written. Filenames are resolved as
|
|
|
|
|
// "models/buildings/<basename>.json" - a hardcoded stand-in for
|
|
|
|
|
// tools/asset/chunk/__main__.py's find_model() recursive search, valid
|
|
|
|
|
// as long as every building model lives flat under that one directory
|
|
|
|
|
// (true today - see the raw-json-maps plan notes for the tradeoff).
|
|
|
|
|
static void assetChunkParseMeshes(
|
|
|
|
|
yyjson_val *root, assetchunkoutput_t *out, uint8_t *ioIndex
|
|
|
|
|
) {
|
|
|
|
|
yyjson_val *meshesArr = yyjson_obj_get(root, "meshes");
|
|
|
|
|
if(!yyjson_is_arr(meshesArr)) return;
|
|
|
|
|
|
|
|
|
|
size_t idx, len;
|
|
|
|
|
yyjson_val *meshVal;
|
|
|
|
|
yyjson_arr_foreach(meshesArr, idx, len, meshVal) {
|
|
|
|
|
if(*ioIndex >= CHUNK_MESH_COUNT_MAX) break;
|
|
|
|
|
|
|
|
|
|
yyjson_val *fileVal = yyjson_obj_get(meshVal, "file");
|
|
|
|
|
if(!fileVal || !yyjson_is_str(fileVal)) continue;
|
|
|
|
|
|
|
|
|
|
char_t baseName[CHUNK_MESH_NAME_MAX];
|
|
|
|
|
stringCopy(baseName, yyjson_get_str(fileVal), sizeof(baseName));
|
|
|
|
|
char_t *dot = stringFindLastChar(baseName, '.');
|
|
|
|
|
if(dot != NULL) *dot = '\0';
|
|
|
|
|
|
|
|
|
|
uint8_t m = *ioIndex;
|
|
|
|
|
stringFormat(
|
|
|
|
|
out->modelNames[m], CHUNK_MESH_NAME_MAX,
|
|
|
|
|
"models/buildings/%s.json", baseName
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
vec3 offset = {0.0f, 0.0f, 0.0f};
|
|
|
|
|
yyjson_val *posArr = yyjson_obj_get(meshVal, "pos");
|
|
|
|
|
if(yyjson_is_arr(posArr) && yyjson_arr_size(posArr) >= 3) {
|
|
|
|
|
offset[0] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 0));
|
|
|
|
|
offset[1] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 1));
|
|
|
|
|
offset[2] = (float_t)yyjson_get_num(yyjson_arr_get(posArr, 2));
|
|
|
|
|
}
|
|
|
|
|
memoryCopy(out->meshOffsets[m], offset, sizeof(vec3));
|
|
|
|
|
|
|
|
|
|
(*ioIndex)++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void assetChunkParseEntities(yyjson_val *root, assetchunkoutput_t *out) {
|
|
|
|
|
out->entitySpawnCount = 0;
|
|
|
|
|
|
|
|
|
|
yyjson_val *arr = yyjson_obj_get(root, "entities");
|
|
|
|
|
if(!yyjson_is_arr(arr)) return;
|
|
|
|
|
|
|
|
|
|
size_t idx, len;
|
|
|
|
|
yyjson_val *val;
|
|
|
|
|
yyjson_arr_foreach(arr, idx, len, val) {
|
|
|
|
|
if(out->entitySpawnCount >= CHUNK_ENTITY_SPAWN_COUNT_MAX) break;
|
|
|
|
|
|
|
|
|
|
const char_t *type = yyjson_get_str(yyjson_obj_get(val, "type"));
|
|
|
|
|
yyjson_val *posArr = yyjson_obj_get(val, "pos");
|
|
|
|
|
if(type == NULL || !yyjson_is_arr(posArr) || yyjson_arr_size(posArr) < 3) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
worldpos_t pos = {
|
|
|
|
|
.x = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 0)),
|
|
|
|
|
.y = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 1)),
|
|
|
|
|
.z = (worldunit_t)yyjson_get_int(yyjson_arr_get(posArr, 2)),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
chunkentityspawn_t *spawn = &out->entitySpawns[out->entitySpawnCount];
|
|
|
|
|
if(stringEquals(type, "global")) {
|
|
|
|
|
spawn->kind = CHUNK_ENTITY_SPAWN_KIND_GLOBAL;
|
|
|
|
|
spawn->globalId =
|
|
|
|
|
(uint16_t)yyjson_get_int(yyjson_obj_get(val, "globalId"));
|
|
|
|
|
spawn->itemId = 0;
|
|
|
|
|
spawn->itemQuantity = 0;
|
|
|
|
|
} else if(stringEquals(type, "item")) {
|
|
|
|
|
spawn->kind = CHUNK_ENTITY_SPAWN_KIND_ITEM;
|
|
|
|
|
spawn->globalId = 0;
|
|
|
|
|
spawn->itemId = (uint16_t)yyjson_get_int(yyjson_obj_get(val, "itemId"));
|
|
|
|
|
spawn->itemQuantity =
|
|
|
|
|
(uint8_t)yyjson_get_int(yyjson_obj_get(val, "quantity"));
|
|
|
|
|
} else {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
spawn->position = pos;
|
|
|
|
|
out->entitySpawnCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void assetChunkParseAreas(yyjson_val *root, assetchunkoutput_t *out) {
|
|
|
|
|
out->areaSpawnCount = 0;
|
|
|
|
|
|
|
|
|
|
yyjson_val *arr = yyjson_obj_get(root, "areas");
|
|
|
|
|
if(!yyjson_is_arr(arr)) return;
|
|
|
|
|
|
|
|
|
|
size_t idx, len;
|
|
|
|
|
yyjson_val *val;
|
|
|
|
|
yyjson_arr_foreach(arr, idx, len, val) {
|
|
|
|
|
if(out->areaSpawnCount >= CHUNK_AREA_COUNT_MAX) break;
|
|
|
|
|
|
|
|
|
|
yyjson_val *minArr = yyjson_obj_get(val, "min");
|
|
|
|
|
yyjson_val *maxArr = yyjson_obj_get(val, "max");
|
|
|
|
|
if(
|
|
|
|
|
!yyjson_is_arr(minArr) || yyjson_arr_size(minArr) < 3 ||
|
|
|
|
|
!yyjson_is_arr(maxArr) || yyjson_arr_size(maxArr) < 3
|
|
|
|
|
) continue;
|
|
|
|
|
|
|
|
|
|
chunkareaspawn_t *area = &out->areaSpawns[out->areaSpawnCount];
|
|
|
|
|
area->min = (worldpos_t){
|
|
|
|
|
.x = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 0)),
|
|
|
|
|
.y = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 1)),
|
|
|
|
|
.z = (worldunit_t)yyjson_get_int(yyjson_arr_get(minArr, 2)),
|
|
|
|
|
};
|
|
|
|
|
area->max = (worldpos_t){
|
|
|
|
|
.x = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 0)),
|
|
|
|
|
.y = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 1)),
|
|
|
|
|
.z = (worldunit_t)yyjson_get_int(yyjson_arr_get(maxArr, 2)),
|
|
|
|
|
};
|
|
|
|
|
area->callbackId =
|
|
|
|
|
(uint16_t)yyjson_get_int(yyjson_obj_get(val, "callbackId"));
|
|
|
|
|
area->notify = (uint8_t)yyjson_get_int(yyjson_obj_get(val, "notify"));
|
|
|
|
|
area->trigger = (uint8_t)yyjson_get_int(yyjson_obj_get(val, "trigger"));
|
|
|
|
|
out->areaSpawnCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Six vertices (2 CCW triangles) for one tile's quad, matching
|
|
|
|
|
// tools/asset/chunk/__main__.py's _tile_quad. Corner order: SW=(x,y),
|
|
|
|
|
// SE=(x+1,y), NE=(x+1,y+1), NW=(x,y+1). NORTH=+Y, EAST=+X.
|
|
|
|
|
static void assetChunkAppendTileQuad(
|
|
|
|
|
meshvertex_t *verts, uint32_t *vertCount,
|
|
|
|
|
const uint32_t x, const uint32_t y,
|
|
|
|
|
const float_t sw, const float_t se, const float_t ne, const float_t nw
|
|
|
|
|
) {
|
|
|
|
|
const float_t u0 = (float_t)x / (float_t)CHUNK_WIDTH;
|
|
|
|
|
const float_t u1 = (float_t)(x + 1) / (float_t)CHUNK_WIDTH;
|
|
|
|
|
const float_t v0 = (float_t)y / (float_t)CHUNK_HEIGHT;
|
|
|
|
|
const float_t v1 = (float_t)(y + 1) / (float_t)CHUNK_HEIGHT;
|
|
|
|
|
const float_t fx = (float_t)x;
|
|
|
|
|
const float_t fy = (float_t)y;
|
|
|
|
|
|
|
|
|
|
meshvertex_t *v = verts + *vertCount;
|
|
|
|
|
v[0] = (meshvertex_t){ .uv = {u0, v0}, .pos = {fx, fy, sw} };
|
|
|
|
|
v[1] = (meshvertex_t){ .uv = {u1, v0}, .pos = {fx + 1, fy, se} };
|
|
|
|
|
v[2] = (meshvertex_t){ .uv = {u1, v1}, .pos = {fx + 1, fy + 1, ne} };
|
|
|
|
|
v[3] = (meshvertex_t){ .uv = {u0, v0}, .pos = {fx, fy, sw} };
|
|
|
|
|
v[4] = (meshvertex_t){ .uv = {u1, v1}, .pos = {fx + 1, fy + 1, ne} };
|
|
|
|
|
v[5] = (meshvertex_t){ .uv = {u0, v1}, .pos = {fx, fy + 1, nw} };
|
|
|
|
|
*vertCount += 6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Builds a mesh_t straight from the tile grid - one quad per non-null
|
|
|
|
|
// tile column, shaped by CHUNK_RAMP_CORNERS - the runtime equivalent of
|
|
|
|
|
// tools/asset/chunk/__main__.py's build_terrain_verts. Only called once
|
|
|
|
|
// assetChunkTilesHaveTerrain has confirmed at least one real tile exists.
|
|
|
|
|
// Mirrors assetMeshLoaderSync's meshInit/meshFlush/free-after-upload
|
|
|
|
|
// sequence (assetmeshloader.c) since this produces the same
|
|
|
|
|
// assetmeshoutput_t shape, just from generated verts instead of a file.
|
|
|
|
|
static errorret_t assetChunkBuildTerrain(
|
|
|
|
|
const tile_t *tiles, assetmeshoutput_t *out
|
|
|
|
|
) {
|
|
|
|
|
meshvertex_t *verts = memoryAllocate(
|
|
|
|
|
CHUNK_TILE_COUNT * 6 * sizeof(meshvertex_t)
|
|
|
|
|
);
|
|
|
|
|
uint32_t vertCount = 0;
|
|
|
|
|
|
|
|
|
|
for(uint32_t y = 0; y < CHUNK_HEIGHT; y++) {
|
|
|
|
|
for(uint32_t x = 0; x < CHUNK_WIDTH; x++) {
|
|
|
|
|
const tile_t *tile = &tiles[x + y * CHUNK_WIDTH];
|
|
|
|
|
if(tile->shape == TILE_SHAPE_NULL) continue;
|
|
|
|
|
|
|
|
|
|
const float_t *corners = CHUNK_RAMP_CORNERS[tile->shape];
|
|
|
|
|
const float_t fz = (float_t)tile->z;
|
|
|
|
|
assetChunkAppendTileQuad(
|
|
|
|
|
verts, &vertCount, x, y,
|
|
|
|
|
(fz + corners[0]) * WORLD_LAYER_HEIGHT,
|
|
|
|
|
(fz + corners[1]) * WORLD_LAYER_HEIGHT,
|
|
|
|
|
(fz + corners[2]) * WORLD_LAYER_HEIGHT,
|
|
|
|
|
(fz + corners[3]) * WORLD_LAYER_HEIGHT
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assertNull(loading->loading.chunk.data, "Data already defined?");
|
|
|
|
|
assertTrue(vertCount > 0, "Terrain build called with no terrain tiles.");
|
|
|
|
|
|
|
|
|
|
assetfile_t *file = &loading->loading.chunk.file;
|
|
|
|
|
assetLoaderErrorChain(loading,
|
|
|
|
|
assetFileInit(file, loading->entry->name, NULL, NULL)
|
|
|
|
|
out->vertices = verts;
|
|
|
|
|
errorret_t ret = meshInit(
|
|
|
|
|
&out->mesh, MESH_PRIMITIVE_TYPE_TRIANGLES, (int32_t)vertCount, verts
|
|
|
|
|
);
|
|
|
|
|
if(errorIsNotOk(ret)) {
|
|
|
|
|
memoryFree(out->vertices);
|
|
|
|
|
out->vertices = NULL;
|
|
|
|
|
errorChain(ret);
|
|
|
|
|
}
|
|
|
|
|
out->meshInitialized = true;
|
|
|
|
|
|
|
|
|
|
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 chunk file."
|
|
|
|
|
);
|
|
|
|
|
ret = meshFlush(&out->mesh, 0, (int32_t)vertCount);
|
|
|
|
|
if(errorIsNotOk(ret)) {
|
|
|
|
|
meshDispose(&out->mesh);
|
|
|
|
|
out->meshInitialized = false;
|
|
|
|
|
memoryFree(out->vertices);
|
|
|
|
|
out->vertices = NULL;
|
|
|
|
|
errorChain(ret);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if defined(DUSK_OPENGL) && !defined(DUSK_OPENGL_LEGACY)
|
|
|
|
|
memoryFree(out->vertices);
|
|
|
|
|
out->vertices = NULL;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
loading->loading.chunk.data = data;
|
|
|
|
|
loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_PARSE;
|
|
|
|
|
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
|
|
|
|
errorOk();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -55,8 +302,8 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) {
|
|
|
|
|
|
|
|
|
|
switch(loading->loading.chunk.state) {
|
|
|
|
|
case ASSET_CHUNK_LOADING_STATE_INITIAL:
|
|
|
|
|
loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_READ_FILE;
|
|
|
|
|
loading->entry->state = ASSET_ENTRY_STATE_PENDING_ASYNC;
|
|
|
|
|
loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_PARSE;
|
|
|
|
|
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
|
|
|
|
errorOk();
|
|
|
|
|
|
|
|
|
|
case ASSET_CHUNK_LOADING_STATE_PARSE:
|
|
|
|
@@ -69,9 +316,7 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) {
|
|
|
|
|
out->modelEntries[m] = assetLock(
|
|
|
|
|
out->modelNames[m], ASSET_LOADER_TYPE_MODEL, NULL
|
|
|
|
|
);
|
|
|
|
|
assertNotNull(
|
|
|
|
|
out->modelEntries[m], "Failed to lock model."
|
|
|
|
|
);
|
|
|
|
|
assertNotNull(out->modelEntries[m], "Failed to lock model.");
|
|
|
|
|
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
|
|
|
|
errorOk();
|
|
|
|
|
}
|
|
|
|
@@ -93,126 +338,103 @@ errorret_t assetChunkLoaderSync(assetloading_t *loading) {
|
|
|
|
|
errorOk();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t *data = loading->loading.chunk.data;
|
|
|
|
|
assertNotNull(data, "Chunk data should have been loaded by now.");
|
|
|
|
|
|
|
|
|
|
if(data[0] != 'D' || data[1] != 'C' || data[2] != 'F') {
|
|
|
|
|
memoryFree(data);
|
|
|
|
|
assetLoaderErrorThrow(loading, "Invalid chunk file header");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t version = endianLittleToHost32(*(uint32_t *)(data + 4));
|
|
|
|
|
if(version != ASSET_CHUNK_FILE_VERSION) {
|
|
|
|
|
memoryFree(data);
|
|
|
|
|
// --- PARSE: lock+require the chunk's own JSON as a sub-asset, exactly
|
|
|
|
|
// like assetmodelloader.c's ASSET_MODEL_LOADING_STATE_LOCK_ASSETS does
|
|
|
|
|
// for its model descriptor, then read everything out of it before
|
|
|
|
|
// unlocking (the yyjson_doc/vals only stay valid while jsonEntry does).
|
|
|
|
|
char_t jsonKey[ASSET_FILE_NAME_MAX];
|
|
|
|
|
stringFormat(jsonKey, sizeof(jsonKey), "json:%s", loading->entry->name);
|
|
|
|
|
assetloaderinput_t jsonInput;
|
|
|
|
|
memoryZero(&jsonInput, sizeof(jsonInput));
|
|
|
|
|
stringCopy(
|
|
|
|
|
jsonInput.json.path, loading->entry->name, ASSET_FILE_NAME_MAX
|
|
|
|
|
);
|
|
|
|
|
assetentry_t *jsonEntry =
|
|
|
|
|
assetLock(jsonKey, ASSET_LOADER_TYPE_JSON, &jsonInput);
|
|
|
|
|
errorret_t jsonRet = assetRequireLoaded(jsonEntry);
|
|
|
|
|
if(errorIsNotOk(jsonRet)) {
|
|
|
|
|
assetUnlockEntry(jsonEntry);
|
|
|
|
|
assetLoaderErrorThrow(
|
|
|
|
|
loading, "Unsupported chunk file version %u", version
|
|
|
|
|
loading, "Failed to load chunk JSON: %s", loading->entry->name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t offset = 8;
|
|
|
|
|
yyjson_val *root = yyjson_doc_get_root(jsonEntry->data.json);
|
|
|
|
|
|
|
|
|
|
size_t tileSize = CHUNK_TILE_COUNT * sizeof(tile_t);
|
|
|
|
|
out->tiles = memoryAllocate(tileSize);
|
|
|
|
|
memoryCopy(out->tiles, data + offset, tileSize);
|
|
|
|
|
offset += tileSize;
|
|
|
|
|
tile_t tiles[CHUNK_TILE_COUNT];
|
|
|
|
|
assetChunkParseTiles(root, tiles);
|
|
|
|
|
out->hasTerrain = assetChunkTilesHaveTerrain(tiles);
|
|
|
|
|
|
|
|
|
|
for(size_t t = 0; t < CHUNK_TILE_COUNT; t++) {
|
|
|
|
|
uint32_t *shape = (uint32_t *)&out->tiles[t].shape;
|
|
|
|
|
*shape = endianLittleToHost32(*shape);
|
|
|
|
|
}
|
|
|
|
|
uint8_t meshIndex = out->hasTerrain ? 1 : 0;
|
|
|
|
|
assetChunkParseMeshes(root, out, &meshIndex);
|
|
|
|
|
out->meshCount = meshIndex;
|
|
|
|
|
|
|
|
|
|
out->meshCount = data[offset];
|
|
|
|
|
offset += sizeof(uint8_t);
|
|
|
|
|
assertTrue(
|
|
|
|
|
out->meshCount <= CHUNK_MESH_COUNT_MAX,
|
|
|
|
|
"Chunk mesh count exceeds maximum."
|
|
|
|
|
);
|
|
|
|
|
assetChunkParseEntities(root, out);
|
|
|
|
|
assetChunkParseAreas(root, out);
|
|
|
|
|
|
|
|
|
|
for(uint8_t m = 0; m < out->meshCount; m++) {
|
|
|
|
|
uint8_t nameLen = 0;
|
|
|
|
|
while(
|
|
|
|
|
data[offset + nameLen] != '\0' &&
|
|
|
|
|
nameLen < CHUNK_MESH_NAME_MAX - 1
|
|
|
|
|
) {
|
|
|
|
|
nameLen++;
|
|
|
|
|
}
|
|
|
|
|
memoryCopy(out->modelNames[m], data + offset, nameLen);
|
|
|
|
|
out->modelNames[m][nameLen] = '\0';
|
|
|
|
|
offset += nameLen + 1;
|
|
|
|
|
assetUnlockEntry(jsonEntry);
|
|
|
|
|
|
|
|
|
|
memoryCopy(out->meshOffsets[m], data + offset, sizeof(vec3));
|
|
|
|
|
offset += sizeof(vec3);
|
|
|
|
|
out->meshOffsets[m][0] = endianLittleToHostFloat(out->meshOffsets[m][0]);
|
|
|
|
|
out->meshOffsets[m][1] = endianLittleToHostFloat(out->meshOffsets[m][1]);
|
|
|
|
|
out->meshOffsets[m][2] = endianLittleToHostFloat(out->meshOffsets[m][2]);
|
|
|
|
|
}
|
|
|
|
|
out->tiles = memoryAllocate(CHUNK_TILE_COUNT * sizeof(tile_t));
|
|
|
|
|
memoryCopy(out->tiles, tiles, CHUNK_TILE_COUNT * sizeof(tile_t));
|
|
|
|
|
|
|
|
|
|
out->entitySpawnCount = data[offset];
|
|
|
|
|
offset += sizeof(uint8_t);
|
|
|
|
|
assertTrue(
|
|
|
|
|
out->entitySpawnCount <= CHUNK_ENTITY_SPAWN_COUNT_MAX,
|
|
|
|
|
"Chunk entity spawn count exceeds maximum."
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for(uint8_t s = 0; s < out->entitySpawnCount; s++) {
|
|
|
|
|
chunkentityspawn_t *spawn = &out->entitySpawns[s];
|
|
|
|
|
spawn->kind = (chunkentityspawnkind_t)data[offset];
|
|
|
|
|
offset += sizeof(uint8_t);
|
|
|
|
|
|
|
|
|
|
uint16_t a;
|
|
|
|
|
memoryCopy(&a, data + offset, sizeof(uint16_t));
|
|
|
|
|
a = endianLittleToHost16(a);
|
|
|
|
|
offset += sizeof(uint16_t);
|
|
|
|
|
|
|
|
|
|
uint8_t b = data[offset];
|
|
|
|
|
offset += sizeof(uint8_t);
|
|
|
|
|
|
|
|
|
|
if(spawn->kind == CHUNK_ENTITY_SPAWN_KIND_ITEM) {
|
|
|
|
|
spawn->globalId = 0;
|
|
|
|
|
spawn->itemId = a;
|
|
|
|
|
spawn->itemQuantity = b;
|
|
|
|
|
} else {
|
|
|
|
|
spawn->globalId = a;
|
|
|
|
|
spawn->itemId = 0;
|
|
|
|
|
spawn->itemQuantity = 0;
|
|
|
|
|
// Terrain: build the mesh, then wrap it in synthetic (non-cache,
|
|
|
|
|
// non-refcounted) mesh/model entries so modelEntries[0] behaves exactly
|
|
|
|
|
// like a real file-backed model entry for every downstream consumer -
|
|
|
|
|
// see the header's doc comment on hasTerrain/terrainMeshEntry.
|
|
|
|
|
if(out->hasTerrain) {
|
|
|
|
|
assetmeshoutput_t terrainMesh;
|
|
|
|
|
memoryZero(&terrainMesh, sizeof(terrainMesh));
|
|
|
|
|
errorret_t terrainRet = assetChunkBuildTerrain(out->tiles, &terrainMesh);
|
|
|
|
|
if(errorIsNotOk(terrainRet)) {
|
|
|
|
|
memoryFree(out->tiles);
|
|
|
|
|
out->tiles = NULL;
|
|
|
|
|
assetLoaderErrorThrow(
|
|
|
|
|
loading, "Failed to build chunk terrain mesh: %s",
|
|
|
|
|
loading->entry->name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spawn->position = worldPosReadLE(data, &offset);
|
|
|
|
|
assetloaderinput_t texInput = { .texture = TEXTURE_FORMAT_RGBA };
|
|
|
|
|
assetentry_t *texEntry = assetLock(
|
|
|
|
|
ASSET_CHUNK_TERRAIN_TEXTURE, ASSET_LOADER_TYPE_TEXTURE, &texInput
|
|
|
|
|
);
|
|
|
|
|
errorret_t texRet = assetRequireLoaded(texEntry);
|
|
|
|
|
if(errorIsNotOk(texRet)) {
|
|
|
|
|
assetUnlockEntry(texEntry);
|
|
|
|
|
meshDispose(&terrainMesh.mesh);
|
|
|
|
|
memoryFree(out->tiles);
|
|
|
|
|
out->tiles = NULL;
|
|
|
|
|
assetLoaderErrorThrow(
|
|
|
|
|
loading, "Failed to load chunk terrain texture: %s",
|
|
|
|
|
loading->entry->name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
out->terrainTexEntry = texEntry;
|
|
|
|
|
|
|
|
|
|
out->terrainMeshEntry = memoryAllocate(sizeof(assetentry_t));
|
|
|
|
|
memoryZero(out->terrainMeshEntry, sizeof(assetentry_t));
|
|
|
|
|
out->terrainMeshEntry->type = ASSET_LOADER_TYPE_MESH;
|
|
|
|
|
out->terrainMeshEntry->state = ASSET_ENTRY_STATE_LOADED;
|
|
|
|
|
out->terrainMeshEntry->data.mesh = terrainMesh;
|
|
|
|
|
|
|
|
|
|
out->terrainModelEntry = memoryAllocate(sizeof(assetentry_t));
|
|
|
|
|
memoryZero(out->terrainModelEntry, sizeof(assetentry_t));
|
|
|
|
|
out->terrainModelEntry->type = ASSET_LOADER_TYPE_MODEL;
|
|
|
|
|
out->terrainModelEntry->state = ASSET_ENTRY_STATE_LOADED;
|
|
|
|
|
out->terrainModelEntry->data.model.meshEntry = out->terrainMeshEntry;
|
|
|
|
|
out->terrainModelEntry->data.model.texEntry = out->terrainTexEntry;
|
|
|
|
|
out->terrainModelEntry->data.model.color = COLOR_WHITE;
|
|
|
|
|
|
|
|
|
|
out->modelEntries[0] = out->terrainModelEntry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
out->areaSpawnCount = data[offset];
|
|
|
|
|
offset += sizeof(uint8_t);
|
|
|
|
|
assertTrue(
|
|
|
|
|
out->areaSpawnCount <= CHUNK_AREA_COUNT_MAX,
|
|
|
|
|
"Chunk area spawn count exceeds maximum."
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for(uint8_t s = 0; s < out->areaSpawnCount; s++) {
|
|
|
|
|
chunkareaspawn_t *area = &out->areaSpawns[s];
|
|
|
|
|
area->min = worldPosReadLE(data, &offset);
|
|
|
|
|
area->max = worldPosReadLE(data, &offset);
|
|
|
|
|
|
|
|
|
|
uint16_t callbackId;
|
|
|
|
|
memoryCopy(&callbackId, data + offset, sizeof(uint16_t));
|
|
|
|
|
area->callbackId = endianLittleToHost16(callbackId);
|
|
|
|
|
offset += sizeof(uint16_t);
|
|
|
|
|
|
|
|
|
|
area->notify = data[offset];
|
|
|
|
|
offset += sizeof(uint8_t);
|
|
|
|
|
area->trigger = data[offset];
|
|
|
|
|
offset += sizeof(uint8_t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memoryFree(data);
|
|
|
|
|
loading->loading.chunk.data = NULL;
|
|
|
|
|
|
|
|
|
|
if(out->meshCount == 0) {
|
|
|
|
|
loading->entry->state = ASSET_ENTRY_STATE_LOADED;
|
|
|
|
|
errorOk();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loading->loading.chunk.state = ASSET_CHUNK_LOADING_STATE_LOAD_MODELS;
|
|
|
|
|
loading->loading.chunk.modelIndex = 0;
|
|
|
|
|
loading->loading.chunk.modelIndex = out->hasTerrain ? 1 : 0;
|
|
|
|
|
loading->entry->state = ASSET_ENTRY_STATE_PENDING_SYNC;
|
|
|
|
|
errorOk();
|
|
|
|
|
}
|
|
|
|
@@ -231,8 +453,36 @@ errorret_t assetChunkDispose(assetentry_t *entry) {
|
|
|
|
|
|
|
|
|
|
for(uint8_t m = 0; m < out->meshCount; m++) {
|
|
|
|
|
if(out->modelEntries[m] == NULL) continue;
|
|
|
|
|
if(out->hasTerrain && m == 0) continue; // synthetic - freed below.
|
|
|
|
|
assetUnlockEntry(out->modelEntries[m]);
|
|
|
|
|
out->modelEntries[m] = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(out->hasTerrain) {
|
|
|
|
|
if(out->terrainMeshEntry != NULL) {
|
|
|
|
|
assetmeshoutput_t *meshOut = &out->terrainMeshEntry->data.mesh;
|
|
|
|
|
if(meshOut->meshInitialized) {
|
|
|
|
|
meshDispose(&meshOut->mesh);
|
|
|
|
|
meshOut->meshInitialized = false;
|
|
|
|
|
}
|
|
|
|
|
if(meshOut->vertices != NULL) {
|
|
|
|
|
memoryFree(meshOut->vertices);
|
|
|
|
|
meshOut->vertices = NULL;
|
|
|
|
|
}
|
|
|
|
|
memoryFree(out->terrainMeshEntry);
|
|
|
|
|
out->terrainMeshEntry = NULL;
|
|
|
|
|
}
|
|
|
|
|
if(out->terrainModelEntry != NULL) {
|
|
|
|
|
memoryFree(out->terrainModelEntry);
|
|
|
|
|
out->terrainModelEntry = NULL;
|
|
|
|
|
}
|
|
|
|
|
if(out->terrainTexEntry != NULL) {
|
|
|
|
|
assetUnlockEntry(out->terrainTexEntry);
|
|
|
|
|
out->terrainTexEntry = NULL;
|
|
|
|
|
}
|
|
|
|
|
out->modelEntries[0] = NULL;
|
|
|
|
|
out->hasTerrain = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
errorOk();
|
|
|
|
|
}
|
|
|
|
|