from typing import Dict, List, Any from dataclasses import dataclass from itertools import islice, chain from io import BytesIO import struct from collada.util import matrix_transpose, find_semantics from collada import parse from collada import types from collada.generate import renderer from collada import buffer from prettyprinter import pprint, install_extras install_extras(include=["dataclasses"]) @dataclass class State: # arbitrary binary data, including vertex buffers and index # buffers buf: BytesIO # geometry__indices: # keys: collada id # values: the index the geometry was placed at in the C++ geometries[] array geometry__indices: Dict[str, int] # geometry__vertex_index_tables: # keys: collada id # values: vertex_index_table (see buffer.py) geometry__vertex_index_tables: Dict[str, List[int]] # symbol_names: C++ symbols/names already emitted symbol_names: Dict[str, Any] # joint_sids joint_sids: Dict[str, types.Node] # emitted_input_elements_arrays emitted_input_elements_arrays: Dict[str, tuple] def __init__(self): self.buf = BytesIO() self.geometry__indices = {} self.geometry__vertex_index_tables = {} self.node_names = {} self.symbol_names = {} self.joint_sids = {} self.emitted_input_elements_arrays = {} def sanitize_name(state, name, value): assert name is not None assert type(name) is str assert '/' not in name, name c_id = name.lower().replace('-', '_').replace('.', '_') assert c_id not in state.node_names or state.node_names[c_id] is value state.symbol_names[c_id] = value return c_id def render_matrix(fs): fsi = iter(fs) for i in range(4): s = ", ".join(f"{f}f" for f in islice(fsi, 4)) yield f"{s}," def render_inverse_bind_matrix(collada, skin): inverse_bind_matrix_input, = find_semantics(skin.joints.inputs, "INV_BIND_MATRIX") inverse_bind_matrix_source = collada.lookup(inverse_bind_matrix_input.source, types.SourceCore) stride = inverse_bind_matrix_source.technique_common.accessor.stride count = inverse_bind_matrix_source.technique_common.accessor.count array = inverse_bind_matrix_source.array_element assert type(inverse_bind_matrix_source.array_element) == types.FloatArray assert stride == 16 assert array.count == count * stride inverse_bind_matrices = [] yield "static const float inverse_bind_matrices[] = {" for i in range(count): offset = stride * i matrix = matrix_transpose(array.floats[offset:offset+stride]) yield from render_matrix(matrix) yield "};" def renderbin(f, elems, t): fmt = { float: ' with `symbol` to with `target` # symbol is not an XML id # symbol is the `.material` attribute of in geometry.mesh element_index = find_material_symbol(geometry, instance_material.symbol) # target is an XML id material = collada.lookup(instance_material.target, types.Material) material_name = sanitize_name(state, material.id, material) yield "{" yield f".element_index = {element_index}, // an index into mesh.triangles" yield f".material = &material_{material_name}," yield "}," yield "};" def get_instance_materials(instance_geometry): return instance_geometry.bind_material.technique_common.materials if instance_geometry.bind_material is not None else [] def render_node_instance_geometries(state, collada, node_name, instance_geometries): for i, instance_geometry in enumerate(instance_geometries): geometry = collada.lookup(instance_geometry.url, types.Geometry) instance_materials = get_instance_materials(instance_geometry) yield from render_node_instance_geometry_instance_materials(state, collada, node_name, i, geometry, instance_materials) yield f"instance_geometry const instance_geometries_{node_name}[] = {{" for i, instance_geometry in enumerate(instance_geometries): geometry = collada.lookup(instance_geometry.url, types.Geometry) geometry_name = sanitize_name(state, geometry.id, geometry) instance_materials = get_instance_materials(instance_geometry) yield "{" yield f".geometry = &geometry_{geometry_name}," yield f".instance_materials = instance_materials_{node_name}_{i}," yield f".instance_materials_count = {len(instance_materials)}," yield "}," yield "};" def get_node_name_id(node): name = node.id if node.id is not None else f"node-{node.name}" assert name is not None, node return name def render_node(state, collada, node): if node.type is types.NodeType.JOINT: assert node.sid is not None, node.sid assert node.sid not in state.joint_sids, node.sid state.joint_sids[node.sid] = node # render children first for node in node.nodes: yield from render_node(state, collada, node) node_name_id = get_node_name_id(node) node_name = sanitize_name(state, node_name_id, node) yield from render_node_transforms(state, collada, node_name, node.transformation_elements) yield from render_node_instance_geometries(state, collada, node_name, node.instance_geometries) type = { types.NodeType.JOINT: "JOINT", types.NodeType.NODE: "NODE", }[node.type] yield f"node const node_{node_name} = {{" yield f".type = node_type::{type}," yield "" yield f".transforms = transforms_{node_name}," yield f".transforms_count = {len(node.transformation_elements)}," yield "" yield f".instance_geometries = instance_geometries_{node_name}," yield f".instance_geometries_count = {len(node.instance_geometries)}," yield "};" def linear_nodes(collada): for library_visual_scenes in collada.library_visual_scenes: for visual_scene in library_visual_scenes.visual_scenes: for node in visual_scene.nodes: yield node def render_library_visual_scenes(state, collada): for node in linear_nodes(collada): yield from render_node(state, collada, node) yield "node const * const nodes[] = {" for node in linear_nodes(collada): node_name_id = get_node_name_id(node) node_name = sanitize_name(state, node_name_id, node) yield f"&node_{node_name}," yield "};" def render_header(): yield '#include "collada_types.hpp"' yield '' yield 'using namespace collada;' def render_opt_color(field_name, color): yield f".color = {render_float_tuple(color.value)}," def render_opt_color_or_texture(field_name, color_or_texture): if color_or_texture is None: color_or_texture = types.Color(value=(0.0, 0.0, 0.0, 0.0)) assert type(color_or_texture) is types.Color, color_or_texture yield f".{field_name} = {{" yield from render_opt_color("color", color_or_texture) yield "}," def render_opt_float(field_name, f): if f is None: f = types.Float(value=0.0) yield f".{field_name} = {float(f.value)}f," def render_library_effects(state, collada): for library_effects in collada.library_effects: for effect in library_effects.effects: profile_common, = effect.profile_common assert profile_common.newparam == [] shader = profile_common.technique.shader effect_name = sanitize_name(state, effect.id, effect) yield f"effect const effect_{effect_name} = {{" if type(shader) is types.Blinn: yield ".type = effect_type::BLINN," yield ".blinn = {" yield from render_opt_color_or_texture("emission", shader.emission) yield from render_opt_color_or_texture("ambient", shader.ambient) yield from render_opt_color_or_texture("diffuse", shader.diffuse) yield from render_opt_color_or_texture("specular", shader.specular) yield from render_opt_float("shininess", shader.shininess) yield from render_opt_color_or_texture("reflective", shader.reflective) yield from render_opt_float("reflectivity", shader.reflectivity) yield from render_opt_color_or_texture("transparent", shader.transparent) yield from render_opt_float("transparency", shader.transparency) yield from render_opt_float("index_of_refraction", shader.index_of_refraction) yield "}" elif type(shader) is types.Lambert: yield ".type = effect_type::LAMBERT," yield ".lambert = {" yield from render_opt_color_or_texture("emission", shader.emission) yield from render_opt_color_or_texture("ambient", shader.ambient) yield from render_opt_color_or_texture("diffuse", shader.diffuse) yield from render_opt_color_or_texture("reflective", shader.reflective) yield from render_opt_float("reflectivity", shader.reflectivity) yield from render_opt_color_or_texture("transparent", shader.transparent) yield from render_opt_float("transparency", shader.transparency) yield from render_opt_float("index_of_refraction", shader.index_of_refraction) yield "}" elif type(shader) is types.Phong: yield ".type = effect_type::PHONG," yield ".phong = {" yield from render_opt_color_or_texture("emission", shader.emission) yield from render_opt_color_or_texture("ambient", shader.ambient) yield from render_opt_color_or_texture("diffuse", shader.diffuse) yield from render_opt_color_or_texture("specular", shader.specular) yield from render_opt_float("shininess", shader.shininess) yield from render_opt_color_or_texture("reflective", shader.reflective) yield from render_opt_float("reflectivity", shader.reflectivity) yield from render_opt_color_or_texture("transparent", shader.transparent) yield from render_opt_float("transparency", shader.transparency) yield from render_opt_float("index_of_refraction", shader.index_of_refraction) yield "}" elif type(shader) is types.Constant: yield ".type = effect_type::CONSTANT," yield ".constant = {" yield from render_opt_color("color", shader.color) yield from render_opt_color_or_texture("reflective", shader.reflective) yield from render_opt_float("reflectivity", shader.reflectivity) yield from render_opt_color_or_texture("transparent", shader.transparent) yield from render_opt_float("transparency", shader.transparency) yield from render_opt_float("index_of_refraction", shader.index_of_refraction) yield "}" else: assert False, type(shader) yield "};" def render_library_materials(state, collada): for library_materials in collada.library_materials: for material in library_materials.materials: effect = collada.lookup(material.instance_effect.url, types.Effect) material_name = sanitize_name(state, material.id, material) effect_name = sanitize_name(state, effect.id, effect) yield f"material const material_{material_name} = {{" yield f".effect = &effect_{effect_name}," yield "};" if __name__ == "__main__": import sys collada = parse.parse_collada_file(sys.argv[1]) #skin = collada.library_controllers[0].controllers[0].control_element #assert type(skin) is types.Skin #foo = render_inverse_bind_matrix(collada, skin) state = State() render, out = renderer() render(render_header()) render(render_library_effects(state, collada)) render(render_library_materials(state, collada)) render(render_library_geometries(state, collada)) render(render_library_visual_scenes(state, collada)) print(out.getvalue())