#!/usr/bin/env python3 """ Convert a text-based static mesh file (.txt) to binary format (.txt.bin). Usage: python convert_model_to_binary.py [] If the output path is not given it is derived by appending ".bin" to the input path, e.g. resources/w/firebox.txt -> resources/w/firebox.txt.bin Binary format (BSMF v1) -- all values little-endian: HEADER 4 bytes magic "BSMF" uint32 version (1) uint32 numVertices uint32 numTriangles VERTICES (numVertices entries): 3 x float position x, y, z -- engine coordinate space 3 x float normal x, y, z -- engine coordinate space 2 x float UV u, v TRIANGLES (numTriangles entries): 3 x uint32 vertex indices i0, i1, i2 The same Blender->engine axis swap applied by LoadFromTextFile02 is baked in here, so the C++ binary loader can read coordinates directly without any post-processing: engine_x = blender_y engine_y = blender_z engine_z = blender_x """ import struct import re import sys import os def _parse_floats(text): return [float(x) for x in re.findall(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?', text)] def _parse_ints(text): return [int(x) for x in re.findall(r'[-]?\d+', text)] def _swap_axes(x, y, z): """Blender Y-up -> engine coordinate system (mirrors LoadFromTextFile02).""" return y, z, x def convert(input_path, output_path): with open(input_path, 'r', encoding='utf-8') as f: lines = [line.rstrip('\n') for line in f] idx = 0 def next_line(): nonlocal idx while idx < len(lines): line = lines[idx] idx += 1 return line raise EOFError("Unexpected end of file while parsing: " + input_path) # --- Vertices --- while True: line = next_line() if '===Vertices' in line: break m = re.search(r'\d+', line) if not m: raise ValueError("Could not parse vertex count from: " + line) num_vertices = int(m.group()) positions = [] normals = [] uvs = [] for i in range(num_vertices): line = next_line() # V N: Pos(x, y, z) Norm(nx, ny, nz) UV(u, v) nums = _parse_floats(line) # nums[0] = vertex index (float-parsed), then 3 pos, 3 norm, 2 uv if len(nums) < 9: raise ValueError(f"Malformed vertex line {i}: {line}") px, py, pz = _swap_axes(nums[1], nums[2], nums[3]) nx, ny, nz = _swap_axes(nums[4], nums[5], nums[6]) positions.append((px, py, pz)) normals.append((nx, ny, nz)) uvs.append((nums[7], nums[8])) # --- Triangles --- while True: line = next_line() if '===Triangles' in line: break m = re.search(r'\d+', line) if not m: raise ValueError("Could not parse triangle count from: " + line) num_triangles = int(m.group()) triangles = [] for i in range(num_triangles): line = next_line() ints = _parse_ints(line) if len(ints) != 3: raise ValueError(f"Malformed triangle line {i}: {line}") triangles.append(tuple(ints)) # --- Write binary --- with open(output_path, 'wb') as out: out.write(b'BSMF') out.write(struct.pack(' {output_path} ({out_size:,} bytes binary)") print(f" Vertices: {num_vertices}, Triangles: {num_triangles}") if __name__ == '__main__': if len(sys.argv) < 2 or len(sys.argv) > 3: print(f"Usage: {sys.argv[0]} []") sys.exit(1) input_path = sys.argv[1] output_path = sys.argv[2] if len(sys.argv) == 3 else input_path + '.bin' convert(input_path, output_path)