collada: emit instance controllers, skins, inverse bind matrices, joint indices

This commit is contained in:
Zack Buhman 2026-01-30 00:18:10 -06:00
parent 4afebb4380
commit acc14843cd
7 changed files with 279 additions and 61 deletions

View File

@ -141,14 +141,16 @@ def skin_vertex_buffer(collada, skin, vertex_index_table):
# vertex_index_table: input/collada vertex indices in the order written to the index buffer
for vertex_index in vertex_index_table:
influences = vertex_influences[vertex_index]
def emit(column):
def emit(column, cls):
for i in range(4):
if i >= len(influences):
vertex_buffer.append(0)
vertex_buffer.append(cls(0))
else:
vertex_buffer.append(influences[i][column])
emit(0) # emit joint int4
emit(1) # emit weight float4
value = cls(influences[i][column])
assert type(influences[i][column])(value) == influences[i][column]
vertex_buffer.append(value)
emit(0, int) # emit joint int4
emit(1, float) # emit weight float4
return vertex_buffer
if __name__ == "__main__":

View File

@ -103,30 +103,6 @@ def sanitize_name(state, name, value, *, allow_slash=False):
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: '<f',
@ -294,8 +270,8 @@ def shader_to_input_set(channel_to_input_set, shader, op):
return channel_to_input_set[texture.texcoord]
def render_node_instance_geometry_instance_materials(state, collada, node_name, i, geometry, instance_materials):
yield f"instance_material const instance_materials_{node_name}_{i}[] = {{"
def render_node_geometry_instance_materials(state, collada, prefix, node_name, i, geometry, instance_materials):
yield f"instance_material const {prefix}_instance_materials_{node_name}_{i}[] = {{"
for instance_material in instance_materials:
# bind <triangles> with `symbol` to <effect> with `target`
@ -343,7 +319,7 @@ def render_node_instance_geometries(state, collada, node_name, instance_geometri
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 from render_node_geometry_instance_materials(state, collada, "instance_geometry", node_name, i, geometry, instance_materials)
yield f"instance_geometry const instance_geometries_{node_name}[] = {{"
for i, instance_geometry in enumerate(instance_geometries):
@ -354,7 +330,8 @@ def render_node_instance_geometries(state, collada, node_name, instance_geometri
yield "{"
yield f".geometry = &geometry_{geometry_name},"
yield f".instance_materials = instance_materials_{node_name}_{i},"
yield ""
yield f".instance_materials = instance_geometry_instance_materials_{node_name}_{i},"
yield f".instance_materials_count = {len(instance_materials)},"
yield "},"
yield "};"
@ -394,12 +371,83 @@ def render_node_instance_lights(state, collada, node_name, instance_lights):
yield "}"
yield "};"
def find_node_by_sid(root_node, sid):
if sid == root_node.sid:
return root_node
for child_node in root_node.nodes:
node = find_node_by_sid(child_node, sid)
if node is not None:
return node
return None
def find_node_index(state, node):
for other_index, other in enumerate(state.linearized_nodes):
if other is node:
return other_index
def render_joint_node_indices(state, collada, skin, node_name, controller_name, skeleton_node):
joint_input, = find_semantics(skin.joints.inputs, "JOINT")
joint_source = collada.lookup(joint_input.source, types.SourceCore)
stride = joint_source.technique_common.accessor.stride
count = joint_source.technique_common.accessor.count
array = joint_source.array_element
assert type(joint_source.array_element) == types.NameArray, array
assert stride == 1
assert array.count == count * stride
yield f"int const joint_node_indices_{node_name}_{controller_name}[] = {{"
for node_sid in array.names:
joint_node = find_node_by_sid(skeleton_node, node_sid);
assert joint_node is not None, (node_sid, skeleton_node.sid_lookup)
joint_node_index = find_node_index(state, joint_node)
joint_node_name_id = get_node_name_id(joint_node)
joint_node_name = sanitize_name(state, joint_node_name_id, joint_node)
yield f"{joint_node_index}, // {node_sid} {joint_node_name}"
yield "};"
def render_node_instance_controller_joint_node_indices(state, collada, node_name, instance_controller):
controller = collada.lookup(instance_controller.url, types.Controller)
controller_name = sanitize_name(state, controller.id, controller)
assert type(controller.control_element) is types.Skin
skin = controller.control_element
skeleton_node = collada.lookup(instance_controller.skeleton, types.Node)
yield from render_joint_node_indices(state, collada, skin, node_name, controller_name, skeleton_node)
def render_node_instance_controllers(state, collada, node_name, instance_controllers):
for i, instance_controller in enumerate(instance_controllers):
yield from render_node_instance_controller_joint_node_indices(state, collada, node_name, instance_controller)
controller = collada.lookup(instance_controller.url, types.Controller)
assert type(controller.control_element) is types.Skin
geometry = collada.lookup(controller.control_element.source, types.Geometry)
instance_materials = get_instance_materials(instance_controller)
yield from render_node_geometry_instance_materials(state, collada, "instance_controller", node_name, i, geometry, instance_materials)
yield f"instance_controller const instance_controllers_{node_name}[] = {{"
for i, instance_controller in enumerate(instance_controllers):
controller = collada.lookup(instance_controller.url, types.Controller)
controller_name = sanitize_name(state, controller.id, controller)
instance_materials = get_instance_materials(instance_controller)
yield "{"
yield f".controller = &controller_{controller_name},"
yield ""
yield f".joint_node_indices = joint_node_indices_{node_name}_{controller_name},"
yield f".joint_count = (sizeof (joint_node_indices_{node_name}_{controller_name})) / (sizeof (int)),"
yield ""
yield f".instance_materials = instance_controller_instance_materials_{node_name}_{i},"
yield f".instance_materials_count = {len(instance_materials)},"
yield "},"
yield "};"
def render_node(state, collada, node, node_index):
node_name_id = get_node_name_id(node)
node_name = sanitize_name(state, node_name_id, node)
#yield from render_node_children(state, collada, node_name, node.nodes)
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)
yield from render_node_instance_controllers(state, collada, node_name, node.instance_controllers)
yield from render_node_instance_lights(state, collada, node_name, node.instance_lights)
yield from render_node_channels(state, collada, node, node_name)
@ -419,6 +467,9 @@ def render_node(state, collada, node, node_index):
yield f".instance_geometries = instance_geometries_{node_name},"
yield f".instance_geometries_count = {len(node.instance_geometries)},"
yield ""
yield f".instance_controllers = instance_controllers_{node_name},"
yield f".instance_controllers_count = {len(node.instance_controllers)},"
yield ""
yield f".instance_lights = instance_lights_{node_name},"
yield f".instance_lights_count = {len(node.instance_lights)},"
yield ""
@ -839,6 +890,62 @@ def render_library_images(state, collada):
yield f"&image_{image_name},"
yield "};"
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_matrices(collada, skin, controller_name):
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 = []
# emitted in joint order
yield f"matrix const inverse_bind_matrices_{controller_name}[] = {{"
for i in range(count):
yield "{"
offset = stride * i
matrix = matrix_transpose(array.floats[offset:offset+stride])
yield from render_matrix(matrix)
yield "},"
yield "};"
def render_controller(state, collada, controller):
controller_name = sanitize_name(state, controller.id, controller)
assert type(controller.control_element) == types.Skin
skin = controller.control_element
geometry = collada.lookup(skin.source, types.Geometry)
vertex_index_table = state.geometry__vertex_index_tables[geometry.id]
# this is a special index buffer that contains mixed floats/ints,
# and needs to be packed accordingly
# fixme: skin_vertex_buffer should multiply vertices by the bind shape matrix
vertex_buffer = buffer.skin_vertex_buffer(collada, skin, vertex_index_table)
# skin.vertex_weights and skin.source are entirely dealt with
FIXME emit vertex buffer
FIXME emit vertex buffer offset in controller struct
yield from render_inverse_bind_matrices(collada, skin, controller_name)
yield f"controller const controller_{controller_name} = {{"
yield ".skin = {"
yield f".inverse_bind_matrices = inverse_bind_matrices_{controller_name},"
yield "}"
yield "};"
def render_library_controllers(state, collada):
for library_controller in collada.library_controllers:
for controller in library_controller.controllers:
yield from render_controller(state, collada, controller)
def render_all(collada, namespace):
state = State()
render, out = renderer()
@ -849,6 +956,7 @@ def render_all(collada, namespace):
render(render_library_effects(state, collada))
render(render_library_materials(state, collada))
render(render_library_geometries(state, collada))
render(render_library_controllers(state, collada))
# root elements
render(render_library_visual_scenes(state, collada))
@ -871,9 +979,5 @@ 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, out = render_all(collada, "test")
print(out.getvalue())

View File

@ -7,7 +7,7 @@ from collada import header
def usage():
name = sys.argv[0]
print("usage (source):")
print(f" {name} [input_collada.dae] [output_source.cpp] [output_vertex.vtx] [output_vertex.idx] [output_resource.rc]")
print(f" {name} [input_collada.dae] [output_source.cpp] [output_vertex.vtx] [output_vertex.idx] [output_resource.rc] [output_makefile.mk]")
print("usage (header):")
print(f" {name} [output_header.hpp]")
sys.exit(1)

View File

@ -743,6 +743,27 @@ def parse_instance_light(lookup, sid_lookup, root):
lookup_add(sid_lookup, sid, instance_light)
return instance_light
def parse_instance_controller(lookup, sid_lookup, root):
sid = root.attrib.get("sid")
name = root.attrib.get("name")
url = root.attrib["url"]
skeleton = None
bind_material = None
for child in root.getchildren():
if child.tag == tag("bind_material"):
assert bind_material is None
bind_material = parse_bind_material(lookup, child)
if child.tag == tag("skeleton"):
assert skeleton is None
assert len(child.getchildren()) == 0
skeleton = child.text.strip()
instance_controller = types.InstanceController(sid, name, url, skeleton, bind_material)
lookup_add(sid_lookup, sid, instance_controller)
return instance_controller
def parse_node(lookup, sid_lookup, root):
id = root.attrib.get("id")
name = root.attrib.get("name")
@ -753,6 +774,7 @@ def parse_node(lookup, sid_lookup, root):
transformation_elements = []
instance_geometries = []
instance_lights = []
instance_controllers = []
nodes = []
child_sid_lookup = {}
@ -774,6 +796,8 @@ def parse_node(lookup, sid_lookup, root):
instance_geometries.append(parse_instance_geometry(lookup, child_sid_lookup, child))
if child.tag == tag("instance_light"):
instance_lights.append(parse_instance_light(lookup, child_sid_lookup, child))
if child.tag == tag("instance_controller"):
instance_controllers.append(parse_instance_controller(lookup, child_sid_lookup, child))
if child.tag == tag("node"):
nodes.append(parse_node(lookup, child_sid_lookup, child))
@ -781,6 +805,7 @@ def parse_node(lookup, sid_lookup, root):
transformation_elements,
instance_geometries,
instance_lights,
instance_controllers,
nodes,
child_sid_lookup)

View File

@ -412,6 +412,15 @@ class NodeType(Enum):
TransformationElements = Union[Lookat, Matrix, Rotate, Scale, Skew, Translate]
@dataclass(frozen=True)
class InstanceController:
sid: Optional[str]
name: Optional[str]
url: URI # controller id
skeleton: URI # node id
bind_material: Optional[BindMaterial]
@dataclass(frozen=True)
class Node:
id: Optional[ID]
@ -423,6 +432,7 @@ class Node:
transformation_elements: List[TransformationElements]
instance_geometries: List[InstanceGeometry]
instance_lights: List[InstanceLight]
instance_controllers: List[InstanceController]
nodes: List['Node']
sid_lookup: dict = field(repr=False)
@ -461,7 +471,7 @@ class VertexWeights:
@dataclass(frozen=True)
class Skin:
source: URI # required
source: URI # required, geometry ID
bind_shape_matrix: Optional[BindShapeMatrix]
sources: List[SourceCore] # 3 or more

View File

@ -30,6 +30,13 @@ namespace collada {
float const g;
};
struct matrix {
float const _11, _12, _13, _14;
float const _21, _22, _23, _24;
float const _31, _32, _33, _34;
float const _41, _42, _43, _44;
};
//////////////////////////////////////////////////////////////////////
// geometry
//////////////////////////////////////////////////////////////////////
@ -198,13 +205,6 @@ namespace collada {
float3 const up;
};
struct matrix {
float const _11, _12, _13, _14;
float const _21, _22, _23, _24;
float const _31, _32, _33, _34;
float const _41, _42, _43, _44;
};
enum class transform_type {
LOOKAT,
MATRIX,
@ -257,6 +257,26 @@ namespace collada {
int const instance_materials_count;
};
struct skin {
matrix const * const inverse_bind_matrices; // one per joint
};
struct controller {
skin skin;
};
struct instance_controller {
controller const * const controller;
//node const * const skeleton;
int const * const joint_node_indices; // one per joint
int const joint_count;
instance_material const * const instance_materials;
int const instance_materials_count;
};
struct instance_light {
light const * const light;
};
@ -339,6 +359,9 @@ namespace collada {
instance_geometry const * const instance_geometries;
int const instance_geometries_count;
instance_controller const * const instance_controllers;
int const instance_controllers_count;
instance_light const * const instance_lights;
int const instance_lights_count;

View File

@ -1296,6 +1296,9 @@ transform const transforms_node_environmentambientlight[] = {
instance_geometry const instance_geometries_node_environmentambientlight[] = {
};
instance_controller const instance_controllers_node_environmentambientlight[] = {
};
instance_light const instance_lights_node_environmentambientlight[] = {
{
.light = &light_environmentambientlight,
@ -1315,6 +1318,9 @@ node const node_node_environmentambientlight = {
.instance_geometries = instance_geometries_node_environmentambientlight,
.instance_geometries_count = 0,
.instance_controllers = instance_controllers_node_environmentambientlight,
.instance_controllers_count = 0,
.instance_lights = instance_lights_node_environmentambientlight,
.instance_lights_count = 1,
@ -1329,7 +1335,7 @@ transform const transforms_node_cube[] = {
},
};
instance_material const instance_materials_node_cube_0[] = {
instance_material const instance_geometry_instance_materials_node_cube_0[] = {
{
.element_index = 1, // an index into mesh.triangles
.material = &material_material__15_material,
@ -1389,17 +1395,21 @@ instance_material const instance_materials_node_cube_0[] = {
instance_geometry const instance_geometries_node_cube[] = {
{
.geometry = &geometry_geom_cube,
.instance_materials = instance_materials_node_cube_0,
.instance_materials = instance_geometry_instance_materials_node_cube_0,
.instance_materials_count = 6,
},
};
instance_controller const instance_controllers_node_cube[] = {
};
instance_light const instance_lights_node_cube[] = {
};
channel const * const node_channels_node_cube[] = {
&node_channel_node_cube_translation_y,
&node_channel_node_cube_translation_x,
&node_channel_node_cube_translation_y,
};
node const node_node_cube = {
@ -1413,6 +1423,9 @@ node const node_node_cube = {
.instance_geometries = instance_geometries_node_cube,
.instance_geometries_count = 1,
.instance_controllers = instance_controllers_node_cube,
.instance_controllers_count = 0,
.instance_lights = instance_lights_node_cube,
.instance_lights_count = 0,
@ -1443,7 +1456,7 @@ transform const transforms_node_torus[] = {
},
};
instance_material const instance_materials_node_torus_0[] = {
instance_material const instance_geometry_instance_materials_node_torus_0[] = {
{
.element_index = 0, // an index into mesh.triangles
.material = &material_coloreffectr26g177b26_material,
@ -1458,11 +1471,15 @@ instance_material const instance_materials_node_torus_0[] = {
instance_geometry const instance_geometries_node_torus[] = {
{
.geometry = &geometry_geom_torus,
.instance_materials = instance_materials_node_torus_0,
.instance_materials = instance_geometry_instance_materials_node_torus_0,
.instance_materials_count = 1,
},
};
instance_controller const instance_controllers_node_torus[] = {
};
instance_light const instance_lights_node_torus[] = {
};
@ -1481,6 +1498,9 @@ node const node_node_torus = {
.instance_geometries = instance_geometries_node_torus,
.instance_geometries_count = 1,
.instance_controllers = instance_controllers_node_torus,
.instance_controllers_count = 0,
.instance_lights = instance_lights_node_torus,
.instance_lights_count = 0,
@ -1491,7 +1511,7 @@ node const node_node_torus = {
transform const transforms_node_cylinder[] = {
};
instance_material const instance_materials_node_cylinder_0[] = {
instance_material const instance_geometry_instance_materials_node_cylinder_0[] = {
{
.element_index = 0, // an index into mesh.triangles
.material = &material_grass_material,
@ -1506,11 +1526,15 @@ instance_material const instance_materials_node_cylinder_0[] = {
instance_geometry const instance_geometries_node_cylinder[] = {
{
.geometry = &geometry_geom_cylinder,
.instance_materials = instance_materials_node_cylinder_0,
.instance_materials = instance_geometry_instance_materials_node_cylinder_0,
.instance_materials_count = 1,
},
};
instance_controller const instance_controllers_node_cylinder[] = {
};
instance_light const instance_lights_node_cylinder[] = {
};
@ -1528,6 +1552,9 @@ node const node_node_cylinder = {
.instance_geometries = instance_geometries_node_cylinder,
.instance_geometries_count = 1,
.instance_controllers = instance_controllers_node_cylinder,
.instance_controllers_count = 0,
.instance_lights = instance_lights_node_cylinder,
.instance_lights_count = 0,
@ -1546,7 +1573,7 @@ transform const transforms_node_plane[] = {
},
};
instance_material const instance_materials_node_plane_0[] = {
instance_material const instance_geometry_instance_materials_node_plane_0[] = {
{
.element_index = 0, // an index into mesh.triangles
.material = &material_wood_material,
@ -1561,11 +1588,15 @@ instance_material const instance_materials_node_plane_0[] = {
instance_geometry const instance_geometries_node_plane[] = {
{
.geometry = &geometry_geom_plane,
.instance_materials = instance_materials_node_plane_0,
.instance_materials = instance_geometry_instance_materials_node_plane_0,
.instance_materials_count = 1,
},
};
instance_controller const instance_controllers_node_plane[] = {
};
instance_light const instance_lights_node_plane[] = {
};
@ -1583,6 +1614,9 @@ node const node_node_plane = {
.instance_geometries = instance_geometries_node_plane,
.instance_geometries_count = 1,
.instance_controllers = instance_controllers_node_plane,
.instance_controllers_count = 0,
.instance_lights = instance_lights_node_plane,
.instance_lights_count = 0,
@ -1605,7 +1639,7 @@ transform const transforms_node_geosphere[] = {
},
};
instance_material const instance_materials_node_geosphere_0[] = {
instance_material const instance_geometry_instance_materials_node_geosphere_0[] = {
{
.element_index = 0, // an index into mesh.triangles
.material = &material_coloreffectr198g224b87_material,
@ -1620,18 +1654,22 @@ instance_material const instance_materials_node_geosphere_0[] = {
instance_geometry const instance_geometries_node_geosphere[] = {
{
.geometry = &geometry_geom_geosphere,
.instance_materials = instance_materials_node_geosphere_0,
.instance_materials = instance_geometry_instance_materials_node_geosphere_0,
.instance_materials_count = 1,
},
};
instance_controller const instance_controllers_node_geosphere[] = {
};
instance_light const instance_lights_node_geosphere[] = {
};
channel const * const node_channels_node_geosphere[] = {
&node_channel_node_geosphere_inversescaleaxisrotation,
&node_channel_node_geosphere_scaleaxisrotation,
&node_channel_node_geosphere_scale,
&node_channel_node_geosphere_scaleaxisrotation,
};
node const node_node_geosphere = {
@ -1645,6 +1683,9 @@ node const node_node_geosphere = {
.instance_geometries = instance_geometries_node_geosphere,
.instance_geometries_count = 1,
.instance_controllers = instance_controllers_node_geosphere,
.instance_controllers_count = 0,
.instance_lights = instance_lights_node_geosphere,
.instance_lights_count = 0,
@ -1662,6 +1703,9 @@ transform const transforms_node_light[] = {
instance_geometry const instance_geometries_node_light[] = {
};
instance_controller const instance_controllers_node_light[] = {
};
instance_light const instance_lights_node_light[] = {
{
.light = &light_light_light,
@ -1669,8 +1713,8 @@ instance_light const instance_lights_node_light[] = {
};
channel const * const node_channels_node_light[] = {
&node_channel_node_light_translation_z,
&node_channel_node_light_translation_x,
&node_channel_node_light_translation_z,
&node_channel_node_light_translation_y,
};
@ -1685,6 +1729,9 @@ node const node_node_light = {
.instance_geometries = instance_geometries_node_light,
.instance_geometries_count = 0,
.instance_controllers = instance_controllers_node_light,
.instance_controllers_count = 0,
.instance_lights = instance_lights_node_light,
.instance_lights_count = 1,
@ -1699,7 +1746,7 @@ transform const transforms_node_lightindicator[] = {
},
};
instance_material const instance_materials_node_lightindicator_0[] = {
instance_material const instance_geometry_instance_materials_node_lightindicator_0[] = {
{
.element_index = 0, // an index into mesh.triangles
.material = &material_lightemit_material,
@ -1714,11 +1761,15 @@ instance_material const instance_materials_node_lightindicator_0[] = {
instance_geometry const instance_geometries_node_lightindicator[] = {
{
.geometry = &geometry_geom_lightindicator,
.instance_materials = instance_materials_node_lightindicator_0,
.instance_materials = instance_geometry_instance_materials_node_lightindicator_0,
.instance_materials_count = 1,
},
};
instance_controller const instance_controllers_node_lightindicator[] = {
};
instance_light const instance_lights_node_lightindicator[] = {
};
@ -1736,6 +1787,9 @@ node const node_node_lightindicator = {
.instance_geometries = instance_geometries_node_lightindicator,
.instance_geometries_count = 1,
.instance_controllers = instance_controllers_node_lightindicator,
.instance_controllers_count = 0,
.instance_lights = instance_lights_node_lightindicator,
.instance_lights_count = 0,