2 Commits

Author SHA1 Message Date
YourWishes a6f449bb93 example building 2 2026-06-30 16:21:47 -05:00
YourWishes 0614bfc446 example building 2026-06-30 16:21:46 -05:00
16 changed files with 2430 additions and 118 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -21,7 +21,7 @@ add_subdirectory(texture)
# Color definitions # Color definitions
dusk_run_python( dusk_run_python(
dusk_color_defs dusk_color_defs
tools.color.csv tools.color
--csv ${CMAKE_CURRENT_SOURCE_DIR}/color.csv --csv ${CMAKE_CURRENT_SOURCE_DIR}/color.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/display/color.h --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 # Input Action Definitions
dusk_run_python( dusk_run_python(
dusk_input_csv_defs dusk_input_csv_defs
tools.input.csv tools.input
--csv ${CMAKE_CURRENT_SOURCE_DIR}/input.csv --csv ${CMAKE_CURRENT_SOURCE_DIR}/input.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/input/inputactiondefs.h --output ${DUSK_GENERATED_HEADERS_DIR}/input/inputactiondefs.h
) )
+1 -1
View File
@@ -13,7 +13,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Item Definitions # Item Definitions
dusk_run_python( dusk_run_python(
dusk_item_csv_defs dusk_item_csv_defs
tools.item.csv tools.item
--csv ${CMAKE_CURRENT_SOURCE_DIR}/item.csv --csv ${CMAKE_CURRENT_SOURCE_DIR}/item.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/item/item.h --output ${DUSK_GENERATED_HEADERS_DIR}/rpg/item/item.h
) )
+8 -7
View File
@@ -35,6 +35,7 @@ errorret_t rpgInit(void) {
assertTrue(entIndex != 0xFF, "No available entity slots!."); assertTrue(entIndex != 0xFF, "No available entity slots!.");
entity_t *ent = &ENTITIES[entIndex]; entity_t *ent = &ENTITIES[entIndex];
entityInit(ent, ENTITY_TYPE_PLAYER); entityInit(ent, ENTITY_TYPE_PLAYER);
ent->position = (worldpos_t){ 10, 2, 0 };
RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY; RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY;
RPG_CAMERA.followEntity.followEntityId = ent->id; RPG_CAMERA.followEntity.followEntityId = ent->id;
{ {
@@ -48,7 +49,7 @@ errorret_t rpgInit(void) {
entity_t *npc = &ENTITIES[npcIndex]; entity_t *npc = &ENTITIES[npcIndex];
entityInit(npc, ENTITY_TYPE_NPC); entityInit(npc, ENTITY_TYPE_NPC);
npcSetMoveType(npc, NPC_MOVE_TYPE_PATH); npcSetMoveType(npc, NPC_MOVE_TYPE_PATH);
npc->position = (worldpos_t){ 3, 3, 0 }; npc->position = (worldpos_t){ 8, 8, 0 };
npc->interact.type = ENTITY_INTERACT_PRINT; npc->interact.type = ENTITY_INTERACT_PRINT;
npc->interact.data.message = "hello world"; npc->interact.data.message = "hello world";
{ {
@@ -58,12 +59,12 @@ errorret_t rpgInit(void) {
if(ci != -1) entitySetChunk(npc, (uint8_t)ci); if(ci != -1) entitySetChunk(npc, (uint8_t)ci);
} }
npcpath_t *path = &npc->data.npc.moveData.path; // npcpath_t *path = &npc->data.npc.moveData.path;
path->positions[0] = (worldpos_t){ 3, 3, 0 }; // path->positions[0] = (worldpos_t){ 3, 3, 0 };
path->positions[1] = (worldpos_t){ 10, 3, 0 }; // path->positions[1] = (worldpos_t){ 10, 3, 0 };
path->positions[2] = (worldpos_t){ 10, 10, 0 }; // path->positions[2] = (worldpos_t){ 10, 10, 0 };
path->positions[3] = (worldpos_t){ 3, 10, 0 }; // path->positions[3] = (worldpos_t){ 3, 10, 0 };
path->count = 4; // path->count = 4;
// All Good! // All Good!
errorOk(); errorOk();
+1 -1
View File
@@ -12,7 +12,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
# Story Flag Definitions # Story Flag Definitions
dusk_run_python( dusk_run_python(
dusk_story_defs dusk_story_defs
tools.story.csv tools.story
--csv ${CMAKE_CURRENT_SOURCE_DIR}/storyflag.csv --csv ${CMAKE_CURRENT_SOURCE_DIR}/storyflag.csv
--output ${DUSK_GENERATED_HEADERS_DIR}/rpg/story/storyflagvalue.h --output ${DUSK_GENERATED_HEADERS_DIR}/rpg/story/storyflagvalue.h
) )
@@ -9,6 +9,8 @@
#include "console/console.h" #include "console/console.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "display/display.h"
#include "display/displaystate.h"
#include "display/shader/shader.h" #include "display/shader/shader.h"
#include "display/screen/screen.h" #include "display/screen/screen.h"
#include "display/shader/shaderunlit.h" #include "display/shader/shaderunlit.h"
@@ -57,6 +59,10 @@ errorret_t sceneOverworldUpdate(scenedata_t *sceneData) {
errorret_t sceneOverworldRender(scenedata_t *sceneData) { errorret_t sceneOverworldRender(scenedata_t *sceneData) {
assertNotNull(sceneData, "Scene data cannot be null"); 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; mat4 proj, model, eye;
+225 -103
View File
@@ -4,39 +4,54 @@
# https://opensource.org/licenses/MIT # https://opensource.org/licenses/MIT
""" """
Converts DCF chunk files (version 1 or 2) to version 3 and generates the Generates DCF + companion DMF files from raw chunk JSON files, or upgrades
companion DMF (Dusk Mesh Format) files that version 3 references. legacy DCF files (version 1 or 2) to version 3.
Version 1 format (after 8-byte header + tiles): JSON input (assetsraw/chunks/chunk_X_Y_Z.json):
uint32_t vertCount {
meshvertex_t vertices[vertCount] "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 uint8_t meshCount
for each mesh: for each mesh:
uint32_t vertCount null-terminated string (relative asset path to .dmf)
meshvertex_t vertices[vertCount] float32[3] (x, y, z offset)
Version 3 format (after 8-byte header + tiles):
uint8_t meshCount
for each mesh:
null-terminated string (path to companion .dmf asset)
DMF format: DMF format:
Bytes 0-3: DMF\x00 Bytes 0-3: DMF\\x00
Bytes 4-7: uint32_t version = 1 (little-endian) Bytes 4-7: uint32_t version = 1 (little-endian)
Bytes 8-11: uint32_t vertCount (little-endian) Bytes 8-11: uint32_t vertCount (little-endian)
Bytes 12+: meshvertex_t vertices[vertCount] Bytes 12+: meshvertex_t vertices[vertCount]
Each vertex: uv[2] + pos[3] = 5 x float32 LE = 20 bytes
Usage: Usage:
python3 -m tools.asset.chunk <input.dcf> [output.dcf] python3 -m tools.asset.chunk # process all assetsraw/chunks/*.json
If output is omitted the input file is updated in place. python3 -m tools.asset.chunk <file.json> # process one JSON file
DMF files are written to assets/meshes/ beside the chunks/ directory. python3 -m tools.asset.chunk <file.dcf> # upgrade legacy DCF in-place
""" """
import json
import os
import struct import struct
import sys 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_WIDTH = 16
CHUNK_HEIGHT = 16 CHUNK_HEIGHT = 16
@@ -44,62 +59,48 @@ CHUNK_DEPTH = 32
CHUNK_TILE_COUNT = CHUNK_WIDTH * CHUNK_HEIGHT * CHUNK_DEPTH # 8192 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_MESH_NAME_MAX = 64 CHUNK_MESH_NAME_MAX = 64
TILE_SIZE = 4 TILE_SIZE = 4
VERTEX_SIZE = 20 VERTEX_SIZE = 20
TILE_SHAPE_NULL = 0
TILE_SHAPE_GROUND = 1
FILE_MAGIC = b'DCF' FILE_MAGIC = b'DCF'
DMF_MAGIC = b'DMF\x00' DMF_MAGIC = b'DMF\x00'
VERSION_OUT = 3 VERSION_OUT = 3
DMF_VERSION = 1 DMF_VERSION = 1
def read_dcf(path): # ---------------------------------------------------------------------------
"""Read a v1 or v2 DCF file. Returns (tiles, meshes) where meshes is a # Helpers
list of raw vertex byte strings, one per mesh.""" # ---------------------------------------------------------------------------
with open(path, 'rb') as f:
data = f.read()
if data[:3] != FILE_MAGIC: def tile_index(x, y, z):
raise ValueError(f"{path}: not a DCF file") return x + y * CHUNK_WIDTH + z * CHUNK_WIDTH * CHUNK_HEIGHT
version = struct.unpack_from('<I', data, 4)[0]
if version not in (1, 2): def find_mesh(filename):
raise ValueError( """Search ASSETS_DIR/meshes/ recursively for filename.
f"{path}: expected version 1 or 2, got {version}" 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
offset = 8
tiles_size = CHUNK_TILE_COUNT * TILE_SIZE
tiles = data[offset:offset + tiles_size]
offset += tiles_size
meshes = [] def derive_dcf_path(json_path):
"""assetsraw/chunks/chunk_X_Y_Z.json -> assets/chunks/X_Y_Z.dcf"""
if version == 1: base = os.path.splitext(os.path.basename(json_path))[0]
vert_count = struct.unpack_from('<I', data, offset)[0] if base.startswith('chunk_'):
offset += 4 base = base[len('chunk_'):]
verts = data[offset:offset + vert_count * VERTEX_SIZE] rel_dir = os.path.relpath(os.path.dirname(os.path.abspath(json_path)), ASSETSRAW_DIR)
if len(verts) != vert_count * VERTEX_SIZE: return os.path.join(ASSETS_DIR, rel_dir, base + '.dcf')
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 write_dmf(path, vertex_bytes): def write_dmf(path, vertex_bytes):
@@ -111,18 +112,11 @@ def write_dmf(path, vertex_bytes):
buf += vertex_bytes buf += vertex_bytes
with open(path, 'wb') as f: with open(path, 'wb') as f:
f.write(buf) f.write(buf)
print( print(f' Wrote DMF {path}: {vert_count} vertices, {len(buf)} bytes')
f' Wrote DMF {path}: '
f'{vert_count} vertices, {len(buf)} bytes'
)
def write_v3(dcf_path, tiles, mesh_names, mesh_offsets=None): def write_v3(dcf_path, tiles, mesh_names, mesh_offsets=None):
"""Write a v3 DCF that references the given DMF asset paths. """Write a v3 DCF referencing 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) mesh_count = len(mesh_names)
if mesh_offsets is None: if mesh_offsets is None:
mesh_offsets = [(0.0, 0.0, 0.0)] * mesh_count 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) f.write(buf)
print( print(
f' Wrote DCF {dcf_path}: ' 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(): def main():
args = sys.argv[1:] args = sys.argv[1:]
if not args: if not args:
print( chunks_dir = os.path.join(ASSETSRAW_DIR, 'chunks')
"Usage: python3 -m tools.asset.chunk " if not os.path.isdir(chunks_dir):
"<input.dcf> [output.dcf]" print(f"No directory found: {chunks_dir}")
)
sys.exit(1) sys.exit(1)
json_files = sorted(
os.path.join(chunks_dir, f)
for f in os.listdir(chunks_dir)
if f.endswith('.json')
)
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] src = args[0]
dst = args[1] if len(args) > 1 else src if os.path.splitext(src)[1].lower() == '.json':
process_json(src)
print(f"Reading {src} ...") else:
tiles, meshes = read_dcf(src) upgrade_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 __name__ == '__main__': if __name__ == '__main__':