summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-08 07:00:28 +0100
committerskal <pascal.massimino@gmail.com>2026-02-08 07:00:28 +0100
commit1bc1cf8cd2c66bbae615a5ddba883b7cd55bd67f (patch)
tree15d989e9ff31c1a691bfaa840f661f3eed92a6b2 /tools
parentef3839ac767057d80feb55aaaf3f4ededfe69e91 (diff)
feat(3d): Implement Blender export and binary scene loading pipeline
Diffstat (limited to 'tools')
-rw-r--r--tools/asset_packer.cc9
-rw-r--r--tools/blender_export.py117
2 files changed, 126 insertions, 0 deletions
diff --git a/tools/asset_packer.cc b/tools/asset_packer.cc
index 32742bd..42dfa7a 100644
--- a/tools/asset_packer.cc
+++ b/tools/asset_packer.cc
@@ -127,6 +127,7 @@ int main(int argc, char* argv[]) {
fprintf(
assets_data_cc_file,
"// This file is auto-generated by asset_packer.cc. Do not edit.\n\n");
+ fprintf(assets_data_cc_file, "#include <cstring>\n");
fprintf(assets_data_cc_file, "#include \"util/asset_manager.h\"\n");
fprintf(assets_data_cc_file, "#include \"%s\"\n",
generated_header_name.c_str());
@@ -499,6 +500,14 @@ int main(int argc, char* argv[]) {
fprintf(assets_data_cc_file, " return %zu;\n", asset_build_infos.size());
fprintf(assets_data_cc_file, "}\n\n");
+ fprintf(assets_data_cc_file, "AssetId GetAssetIdByName(const char* name) {\n");
+ for (const auto& info : asset_build_infos) {
+ fprintf(assets_data_cc_file, " if (std::strcmp(name, \"%s\") == 0) return AssetId::ASSET_%s;\n",
+ info.name.c_str(), info.name.c_str());
+ }
+ fprintf(assets_data_cc_file, " return AssetId::ASSET_LAST_ID;\n");
+ fprintf(assets_data_cc_file, "}\n\n");
+
std::fclose(assets_data_cc_file);
printf("Asset packer successfully generated records for %zu assets.\n",
diff --git a/tools/blender_export.py b/tools/blender_export.py
new file mode 100644
index 0000000..da7b986
--- /dev/null
+++ b/tools/blender_export.py
@@ -0,0 +1,117 @@
+import bpy
+import struct
+import os
+
+# Output format:
+# Header:
+# char[4] magic = "SCN1"
+# uint32_t num_objects
+# uint32_t num_cameras (reserved)
+# uint32_t num_lights (reserved)
+#
+# Object Block:
+# char[64] name
+# uint32_t type (0=CUBE, 1=SPHERE, 2=PLANE, 3=TORUS, 4=BOX, 5=SKYBOX, 6=MESH)
+# vec3 position
+# quat rotation (x, y, z, w)
+# vec3 scale
+# vec4 color
+# uint32_t mesh_name_len
+# char[] mesh_name (if type == MESH)
+# float mass
+# float restitution
+# uint32_t is_static (bool)
+
+def export_scene(filepath):
+ print(f"Exporting scene to {filepath}...")
+
+ objects = [obj for obj in bpy.context.scene.objects if obj.visible_get() and obj.type == 'MESH']
+
+ with open(filepath, 'wb') as f:
+ # Header
+ f.write(b'SCN1')
+ f.write(struct.pack('<III', len(objects), 0, 0))
+
+ for obj in objects:
+ print(f" Exporting {obj.name}...")
+
+ # Name (64 bytes, null-padded)
+ name_bytes = obj.name.encode('utf-8')[:63]
+ f.write(struct.pack('<64s', name_bytes))
+
+ # Type detection
+ # Default to MESH (6)
+ obj_type = 6
+
+ # Simple heuristic for primitives based on name
+ # In a real pipeline, we might use custom properties
+ name_lower = obj.name.lower()
+ if 'cube' in name_lower and 'mesh' not in name_lower: obj_type = 0
+ elif 'sphere' in name_lower: obj_type = 1
+ elif 'plane' in name_lower: obj_type = 2
+ elif 'torus' in name_lower: obj_type = 3
+ elif 'box' in name_lower: obj_type = 4
+
+ f.write(struct.pack('<I', obj_type))
+
+ # Transform
+ # Blender uses Z-up. We typically use Y-up.
+ # Conversion:
+ # Pos: (x, z, -y)
+ # Rot: Convert quaternion
+
+ pos = obj.location
+ rot = obj.rotation_quaternion
+ scale = obj.scale
+
+ # Position
+ # For now, exporting raw Blender coordinates.
+ # We can fix coordinate system in C++ or here if decided.
+ # Keeping raw for now to avoid confusion until verified.
+ f.write(struct.pack('<3f', pos.x, pos.y, pos.z))
+
+ # Rotation (x, y, z, w)
+ # Blender provides (w, x, y, z)
+ f.write(struct.pack('<4f', rot.x, rot.y, rot.z, rot.w))
+
+ # Scale
+ f.write(struct.pack('<3f', scale.x, scale.y, scale.z))
+
+ # Color (RGBA)
+ # Try to get from material
+ color = (1.0, 1.0, 1.0, 1.0)
+ if obj.active_material:
+ c = obj.active_material.diffuse_color
+ color = (c[0], c[1], c[2], c[3])
+ f.write(struct.pack('<4f', *color))
+
+ # Mesh Asset Name
+ mesh_asset_name = ""
+ if obj_type == 6: # MESH
+ # Ensure the mesh name is sanitized for AssetId (e.g. MESH_CUBE)
+ # Convention: MESH_<ObjectName_Upper>
+ mesh_asset_name = "MESH_" + obj.name.upper().replace('.', '_')
+
+ name_len = len(mesh_asset_name)
+ f.write(struct.pack('<I', name_len))
+ if name_len > 0:
+ f.write(mesh_asset_name.encode('utf-8'))
+
+ # Physics properties (from custom properties or defaults)
+ mass = obj.get('mass', 1.0)
+ restitution = obj.get('restitution', 0.5)
+ is_static = obj.get('is_static', 0)
+
+ f.write(struct.pack('<ffI', float(mass), float(restitution), int(is_static)))
+
+ print("Export complete.")
+
+# To run in Blender:
+# import sys
+# sys.path.append('/path/to/demo/tools')
+# import blender_export
+# blender_export.export_scene('/path/to/demo/assets/final/scene.bin')
+
+if __name__ == "__main__":
+ # Standalone test (won't work outside Blender environment usually)
+ pass