example building

This commit is contained in:
2026-06-30 16:21:46 -05:00
parent bb020c36c1
commit 0614bfc446
15 changed files with 265 additions and 111 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+26
View File
@@ -0,0 +1,26 @@
{
"meshes": [
{
"file": "house_5_3.dmf",
"pos": [ 1, 1, 0 ]
}
],
"tiles": [
{
"pos": [ 0, 0, 0 ],
"type": 1,
"tile": 0
},
{
"pos": [ 1, 0, 0 ],
"type": 1,
"tile": 0
},
{
"pos": [ 0, 1, 0 ],
"type": 1,
"tile": 0
}
]
}
+1 -1
View File
@@ -21,7 +21,7 @@ add_subdirectory(texture)
# Color definitions
dusk_run_python(
dusk_color_defs
tools.color.csv
tools.color
--csv ${CMAKE_CURRENT_SOURCE_DIR}/color.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/display/color.h
)
+1 -1
View File
@@ -14,7 +14,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Input Action Definitions
dusk_run_python(
dusk_input_csv_defs
tools.input.csv
tools.input
--csv ${CMAKE_CURRENT_SOURCE_DIR}/input.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/input/inputactiondefs.h
)
+1 -1
View File
@@ -13,7 +13,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Item Definitions
dusk_run_python(
dusk_item_csv_defs
tools.item.csv
tools.item
--csv ${CMAKE_CURRENT_SOURCE_DIR}/item.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/item/item.h
)
+1 -1
View File
@@ -12,7 +12,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Story Flag Definitions
dusk_run_python(
dusk_story_defs
tools.story.csv
tools.story
--csv ${CMAKE_CURRENT_SOURCE_DIR}/storyflag.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/story/storyflagvalue.h
)
@@ -9,6 +9,8 @@
#include "console/console.h"
#include "assert/assert.h"
#include "display/display.h"
#include "display/displaystate.h"
#include "display/shader/shader.h"
#include "display/screen/screen.h"
#include "display/shader/shaderunlit.h"
@@ -57,6 +59,10 @@ errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
errorret_t sceneOverworldRender(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null");
errorChain(displaySetState((displaystate_t){
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
}));
mat4 proj, model, eye;
+229 -107
View File
@@ -4,39 +4,54 @@
# https://opensource.org/licenses/MIT
"""
Converts DCF chunk files (version 1 or 2) to version 3 and generates the
companion DMF (Dusk Mesh Format) files that version 3 references.
Generates DCF + companion DMF files from raw chunk JSON files, or upgrades
legacy DCF files (version 1 or 2) to version 3.
Version 1 format (after 8-byte header + tiles):
uint32_t vertCount
meshvertex_t vertices[vertCount]
JSON input (assetsraw/chunks/chunk_X_Y_Z.json):
{
"tiles": [
{ "pos": [x, y, z], "type": <tile_shape_int>, "tile": <uv_tile_int> }
],
"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 <input.dcf> [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 <file.json> # process one JSON file
python3 -m tools.asset.chunk <file.dcf> # 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('<I', data, 4)[0]
if version not in (1, 2):
raise ValueError(
f"{path}: expected version 1 or 2, got {version}"
)
offset = 8
tiles_size = CHUNK_TILE_COUNT * TILE_SIZE
tiles = data[offset:offset + tiles_size]
offset += tiles_size
def find_mesh(filename):
"""Search ASSETS_DIR/meshes/ recursively for filename.
Returns the path relative to ASSETS_DIR (forward slashes), or None."""
meshes_root = os.path.join(ASSETS_DIR, 'meshes')
for dirpath, _, filenames in os.walk(meshes_root):
if filename in filenames:
rel = os.path.relpath(
os.path.join(dirpath, filename), ASSETS_DIR
)
return rel.replace('\\', '/')
return None
meshes = []
if version == 1:
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")
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, 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(
'<I', tiles_bytes, idx * TILE_SIZE
)[0]
if tile_type != TILE_SHAPE_GROUND:
continue
fx, fy, fz = float(x), float(y), float(z)
uv_u0 = x / CHUNK_WIDTH
uv_u1 = (x + 1) / CHUNK_WIDTH
uv_v0 = y / CHUNK_HEIGHT
uv_v1 = (y + 1) / CHUNK_HEIGHT
for v in [
(uv_u0, uv_v0, fx, fy, fz),
(uv_u1, uv_v0, fx+1, fy, fz),
(uv_u1, uv_v1, fx+1, fy+1, fz),
(uv_u0, uv_v0, fx, fy, fz),
(uv_u1, uv_v1, fx+1, fy+1, fz),
(uv_u0, uv_v1, fx, fy+1, fz),
]:
buf += struct.pack('<5f', *v)
return bytes(buf)
def from_json(json_path, dcf_path):
with open(json_path) as f:
data = json.load(f)
tiles = bytearray(CHUNK_TILE_COUNT * TILE_SIZE)
for tile in data.get('tiles', []):
pos = tile['pos']
x, y, z = int(pos[0]), int(pos[1]), int(pos[2])
struct.pack_into(
'<I', tiles, tile_index(x, y, z) * TILE_SIZE, int(tile['type'])
)
terrain_verts = build_terrain_verts(tiles)
mesh_names = []
mesh_offsets = []
# Terrain mesh (only written if non-empty)
dcf_base = os.path.splitext(os.path.basename(dcf_path))[0]
terrain_dir = os.path.join(ASSETS_DIR, 'meshes', 'chunks')
os.makedirs(terrain_dir, exist_ok=True)
if terrain_verts:
dmf_name = f'chunk_{dcf_base}_0.dmf'
write_dmf(os.path.join(terrain_dir, dmf_name), terrain_verts)
mesh_names.append(f'meshes/chunks/{dmf_name}')
mesh_offsets.append((0.0, 0.0, 0.0))
# External mesh references
for mesh in data.get('meshes', []):
filename = mesh['file']
pos = mesh.get('pos', [0, 0, 0])
rel = find_mesh(filename)
if rel is None:
raise ValueError(f"Mesh not found under assets/meshes/: {filename}")
mesh_names.append(rel)
mesh_offsets.append((float(pos[0]), float(pos[1]), float(pos[2])))
print(f' Resolved {filename} -> {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('<I', data, 4)[0]
if version not in (1, 2):
raise ValueError(f"{path}: expected version 1 or 2, got {version}")
offset = 8
tiles_size = CHUNK_TILE_COUNT * TILE_SIZE
tiles = data[offset:offset + tiles_size]
offset += tiles_size
meshes = []
if version == 1:
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")
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, 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 "
"<input.dcf> [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__':