#!/usr/bin/env python3 """Export the seed demo room as a glTF/GLB file. Reads the seed_game.json workflow and converts all physics bodies (boxes) into a glTF scene with meshes. The output can be opened in Blender, edited, and re-exported for the engine to load. Usage: python export_room_gltf.py [--output packages/seed/map.glb] """ import argparse import json import struct import math from pathlib import Path def create_box_mesh(sx, sy, sz): """Create a unit box mesh scaled by sx, sy, sz. Returns (vertices, indices). Vertex format: position(3) + normal(3) + uv(2) = 8 floats per vertex. """ hx, hy, hz = sx / 2, sy / 2, sz / 2 # 6 faces, 4 vertices each = 24 vertices # Each face has its own normal for flat shading faces = [ # front (+Z) (( hx, hy, hz), (-hx, hy, hz), (-hx, -hy, hz), ( hx, -hy, hz), ( 0, 0, 1)), # back (-Z) ((-hx, hy, -hz), ( hx, hy, -hz), ( hx, -hy, -hz), (-hx, -hy, -hz), ( 0, 0, -1)), # right (+X) (( hx, hy, -hz), ( hx, hy, hz), ( hx, -hy, hz), ( hx, -hy, -hz), ( 1, 0, 0)), # left (-X) ((-hx, hy, hz), (-hx, hy, -hz), (-hx, -hy, -hz), (-hx, -hy, hz), (-1, 0, 0)), # top (+Y) (( hx, hy, -hz), (-hx, hy, -hz), (-hx, hy, hz), ( hx, hy, hz), ( 0, 1, 0)), # bottom (-Y) (( hx, -hy, hz), (-hx, -hy, hz), (-hx, -hy, -hz), ( hx, -hy, -hz), ( 0, -1, 0)), ] vertices = [] indices = [] uvs_face = [(1, 0), (0, 0), (0, 1), (1, 1)] for face_idx, (v0, v1, v2, v3, n) in enumerate(faces): base = len(vertices) for vi, (vx, vy, vz) in enumerate([v0, v1, v2, v3]): u, v = uvs_face[vi] # Scale UVs by face dimensions for tiling if abs(n[0]) > 0.5: # X-facing u *= sz v *= sy elif abs(n[1]) > 0.5: # Y-facing u *= sx v *= sz else: # Z-facing u *= sx v *= sy vertices.append((vx, vy, vz, n[0], n[1], n[2], u, v)) indices.extend([base, base + 1, base + 2, base, base + 2, base + 3]) return vertices, indices def build_gltf(bodies, output_path): """Build a GLB file from a list of physics bodies.""" import io nodes = [] meshes = [] accessors = [] buffer_views = [] bin_data = io.BytesIO() for body in bodies: name = body["name"] px = body.get("pos_x", 0) py = body.get("pos_y", 0) pz = body.get("pos_z", 0) sx = body.get("size_x", 1) sy = body.get("size_y", 1) sz = body.get("size_z", 1) shape = body.get("shape", "box") if shape == "capsule": continue # Skip player verts, idxs = create_box_mesh(sx, sy, sz) # Write index data (uint16) idx_offset = bin_data.tell() for i in idxs: bin_data.write(struct.pack("