From 0614bfc446c1bf9897795aa09af83d8227f0fd89 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Tue, 30 Jun 2026 16:21:46 -0500 Subject: [PATCH] example building --- assets/chunks/0_0_0.dcf | Bin 32814 -> 32864 bytes assets/meshes/buildings/house_5_3.dmf | Bin 0 -> 1332 bytes assets/meshes/chunk_0_0_0_0.dmf | Bin 30732 -> 0 bytes assets/meshes/chunks/chunk_0_0_0_0.dmf | Bin 0 -> 372 bytes assetsraw/chunks/chunk_0_0_0.json | 26 ++ src/dusk/display/CMakeLists.txt | 2 +- src/dusk/input/CMakeLists.txt | 2 +- src/dusk/rpg/item/CMakeLists.txt | 2 +- src/dusk/rpg/story/CMakeLists.txt | 2 +- src/dusk/scene/overworld/sceneoverworld.c | 6 + tools/asset/chunk/__main__.py | 336 +++++++++++++++------- tools/{color/csv/__main__.py => color.py} | 0 tools/{input/csv/__main__.py => input.py} | 0 tools/{item/csv/__main__.py => item.py} | 0 tools/{story/csv/__main__.py => story.py} | 0 15 files changed, 265 insertions(+), 111 deletions(-) create mode 100644 assets/meshes/buildings/house_5_3.dmf delete mode 100644 assets/meshes/chunk_0_0_0_0.dmf create mode 100644 assets/meshes/chunks/chunk_0_0_0_0.dmf create mode 100644 assetsraw/chunks/chunk_0_0_0.json rename tools/{color/csv/__main__.py => color.py} (100%) rename tools/{input/csv/__main__.py => input.py} (100%) rename tools/{item/csv/__main__.py => item.py} (100%) rename tools/{story/csv/__main__.py => story.py} (100%) diff --git a/assets/chunks/0_0_0.dcf b/assets/chunks/0_0_0.dcf index c14c4c902686ef3f0c2cb4acb12f839699f04ddd..943bd015b90b8d28ef8d1cd5d2d95cac1b13deaf 100644 GIT binary patch literal 32864 zcmeIx!3u&v5CG7r{wIUz+@a_AmwoAN9r1{W EJ31s8z5oCK delta 185 jcmaFRz_hM`X@bwj4-(9S9Z&r5lr=ZCI3snkXoCU(W5rNV diff --git a/assets/meshes/buildings/house_5_3.dmf b/assets/meshes/buildings/house_5_3.dmf new file mode 100644 index 0000000000000000000000000000000000000000..5a1473e83d48b8a665fe976933c0bde3d8779242 GIT binary patch literal 1332 zcmaiw(GkKh3`4_Y_&AO~c3>1n=_nlyKPMtinu6}q#Ihu(xj!G}QcAh`8evxJ_qFbu z*O`NdTj8HhnZLaBq>DVOwdRV;8LPR{DQoi_>ExZcc?Pw(@OXxhy(=9YKqs7Ws~OUU z*ZP~}JbJS(^wVe1v>x2RTWyFlYG0~rw|~V;*E`2|rk8c{?tX`t^% H`(f?}J@k92 literal 0 HcmV?d00001 diff --git a/assets/meshes/chunk_0_0_0_0.dmf b/assets/meshes/chunk_0_0_0_0.dmf deleted file mode 100644 index ada49debcff2f6190ca39eae7ceddb79f3376064..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30732 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 diff --git a/assets/meshes/chunks/chunk_0_0_0_0.dmf b/assets/meshes/chunks/chunk_0_0_0_0.dmf new file mode 100644 index 0000000000000000000000000000000000000000..d20086a71c0d575f88ef9dd03eb699d2b11abce2 GIT binary patch literal 372 zcmZ{gK@NZ*3J{Ey1Pc=Jy@-R{x`vnfrGFwVAw`+aOPv)xx+{TRGo^FJ7uvp=6k xWEP(G)HBm(bZfC=b4J%$&Ru0I4-C1?qNV2Ph(`yr@diFStIG0@;(@Umz8^VW9+v, "tile": } + ], + "meshes": [ + { "file": "house_5_3.dmf", "pos": [x, y, z] } + ] + } -Version 2 format (after 8-byte header + tiles): + Tiles absent from the array default to TILE_SHAPE_NULL. + Mesh files are located by searching under assets/meshes/ and referenced by + path from the assets root in the DCF. + + Output DCF is derived automatically: + assetsraw/chunks/chunk_X_Y_Z.json -> assets/chunks/X_Y_Z.dcf + +Version 3 DCF format (after 8-byte header + tiles): uint8_t meshCount for each mesh: - 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) + null-terminated string (relative asset path to .dmf) + float32[3] (x, y, z offset) DMF format: - Bytes 0-3: DMF\x00 + 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] + Each vertex: uv[2] + pos[3] = 5 x float32 LE = 20 bytes 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. + python3 -m tools.asset.chunk # process all assetsraw/chunks/*.json + python3 -m tools.asset.chunk # process one JSON file + python3 -m tools.asset.chunk # upgrade legacy DCF in-place """ +import json +import os import struct import sys -import os + +_HERE = os.path.dirname(os.path.abspath(__file__)) +PROJECT_ROOT = os.path.normpath(os.path.join(_HERE, '..', '..', '..')) +ASSETSRAW_DIR = os.path.join(PROJECT_ROOT, 'assetsraw') +ASSETS_DIR = os.path.join(PROJECT_ROOT, 'assets') CHUNK_WIDTH = 16 CHUNK_HEIGHT = 16 @@ -44,62 +59,48 @@ 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 TILE_SIZE = 4 VERTEX_SIZE = 20 -FILE_MAGIC = b'DCF' -DMF_MAGIC = b'DMF\x00' -VERSION_OUT = 3 -DMF_VERSION = 1 +TILE_SHAPE_NULL = 0 +TILE_SHAPE_GROUND = 1 + +FILE_MAGIC = b'DCF' +DMF_MAGIC = b'DMF\x00' +VERSION_OUT = 3 +DMF_VERSION = 1 -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() +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- - if data[:3] != FILE_MAGIC: - raise ValueError(f"{path}: not a DCF file") +def tile_index(x, y, z): + return x + y * CHUNK_WIDTH + z * CHUNK_WIDTH * CHUNK_HEIGHT - 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, meshes +def derive_dcf_path(json_path): + """assetsraw/chunks/chunk_X_Y_Z.json -> assets/chunks/X_Y_Z.dcf""" + base = os.path.splitext(os.path.basename(json_path))[0] + if base.startswith('chunk_'): + base = base[len('chunk_'):] + rel_dir = os.path.relpath(os.path.dirname(os.path.abspath(json_path)), ASSETSRAW_DIR) + return os.path.join(ASSETS_DIR, rel_dir, base + '.dcf') def write_dmf(path, vertex_bytes): @@ -111,18 +112,11 @@ def write_dmf(path, vertex_bytes): buf += vertex_bytes with open(path, 'wb') as f: f.write(buf) - print( - f' Wrote DMF {path}: ' - f'{vert_count} vertices, {len(buf)} bytes' - ) + print(f' Wrote DMF {path}: {vert_count} vertices, {len(buf)} bytes') 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. - """ + """Write a v3 DCF referencing the given DMF asset paths.""" mesh_count = len(mesh_names) if mesh_offsets is None: mesh_offsets = [(0.0, 0.0, 0.0)] * mesh_count @@ -145,51 +139,179 @@ def write_v3(dcf_path, tiles, mesh_names, mesh_offsets=None): f.write(buf) print( f' Wrote DCF {dcf_path}: ' - f'version {VERSION_OUT}, {mesh_count} mesh(es)' + f'version {VERSION_OUT}, {mesh_count} mesh(es), {len(buf)} bytes' ) +# --------------------------------------------------------------------------- +# JSON -> DCF + DMF +# --------------------------------------------------------------------------- + +def build_terrain_verts(tiles_bytes): + """Generate top-face quads for every TILE_SHAPE_GROUND tile.""" + buf = bytearray() + for z in range(CHUNK_DEPTH): + for y in range(CHUNK_HEIGHT): + for x in range(CHUNK_WIDTH): + idx = tile_index(x, y, z) + tile_type = struct.unpack_from( + ' {rel}') + + write_v3(dcf_path, bytes(tiles), mesh_names, mesh_offsets) + + +def process_json(json_path): + dcf_path = derive_dcf_path(json_path) + os.makedirs(os.path.dirname(dcf_path), exist_ok=True) + print(f"{json_path} -> {dcf_path}") + from_json(json_path, dcf_path) + + +# --------------------------------------------------------------------------- +# Legacy DCF (v1/v2) -> v3 +# --------------------------------------------------------------------------- + +def read_legacy_dcf(path): + with open(path, 'rb') as f: + data = f.read() + if data[:3] != FILE_MAGIC: + 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, meshes + + +def upgrade_dcf(path): + print(f"Upgrading legacy DCF {path} ...") + tiles, meshes = read_legacy_dcf(path) + print( + f" tiles={CHUNK_TILE_COUNT}, meshes={len(meshes)}, " + f"total_verts={sum(len(m) // VERTEX_SIZE for m in meshes)}" + ) + base = os.path.splitext(os.path.basename(path))[0] + terrain_dir = os.path.join(ASSETS_DIR, 'meshes', 'chunks') + os.makedirs(terrain_dir, exist_ok=True) + mesh_names = [] + for idx, verts in enumerate(meshes): + dmf_name = f'chunk_{base}_{idx}.dmf' + write_dmf(os.path.join(terrain_dir, dmf_name), verts) + mesh_names.append(f'meshes/chunks/{dmf_name}') + write_v3(path, tiles, mesh_names) + + +# --------------------------------------------------------------------------- +# Entry point +# --------------------------------------------------------------------------- + def main(): args = sys.argv[1:] + if not args: - print( - "Usage: python3 -m tools.asset.chunk " - " [output.dcf]" + chunks_dir = os.path.join(ASSETSRAW_DIR, 'chunks') + if not os.path.isdir(chunks_dir): + print(f"No directory found: {chunks_dir}") + sys.exit(1) + json_files = sorted( + os.path.join(chunks_dir, f) + for f in os.listdir(chunks_dir) + if f.endswith('.json') ) - sys.exit(1) + if not json_files: + print(f"No JSON files found in {chunks_dir}") + sys.exit(0) + for p in json_files: + process_json(p) + return src = args[0] - dst = args[1] if len(args) > 1 else src - - print(f"Reading {src} ...") - 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_v3(dst, tiles, mesh_names) + if os.path.splitext(src)[1].lower() == '.json': + process_json(src) + else: + upgrade_dcf(src) if __name__ == '__main__': diff --git a/tools/color/csv/__main__.py b/tools/color.py similarity index 100% rename from tools/color/csv/__main__.py rename to tools/color.py diff --git a/tools/input/csv/__main__.py b/tools/input.py similarity index 100% rename from tools/input/csv/__main__.py rename to tools/input.py diff --git a/tools/item/csv/__main__.py b/tools/item.py similarity index 100% rename from tools/item/csv/__main__.py rename to tools/item.py diff --git a/tools/story/csv/__main__.py b/tools/story.py similarity index 100% rename from tools/story/csv/__main__.py rename to tools/story.py