diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-08 07:00:28 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-08 07:00:28 +0100 |
| commit | 1bc1cf8cd2c66bbae615a5ddba883b7cd55bd67f (patch) | |
| tree | 15d989e9ff31c1a691bfaa840f661f3eed92a6b2 /tools | |
| parent | ef3839ac767057d80feb55aaaf3f4ededfe69e91 (diff) | |
feat(3d): Implement Blender export and binary scene loading pipeline
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/asset_packer.cc | 9 | ||||
| -rw-r--r-- | tools/blender_export.py | 117 |
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 |
