From acc14843cdfd3131509ce124882d0a9606155d3e Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Fri, 30 Jan 2026 00:18:10 -0600 Subject: [PATCH] collada: emit instance controllers, skins, inverse bind matrices, joint indices --- collada/buffer.py | 12 +- collada/header.py | 168 ++++++++++++++---- collada/main.py | 2 +- collada/parse.py | 25 +++ collada/types.py | 12 +- include/collada_types.hpp | 37 +++- .../curve_interpolation.cpp | 84 +++++++-- 7 files changed, 279 insertions(+), 61 deletions(-) diff --git a/collada/buffer.py b/collada/buffer.py index 5f21969..bf9d63c 100644 --- a/collada/buffer.py +++ b/collada/buffer.py @@ -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__": diff --git a/collada/header.py b/collada/header.py index ac0bf14..0f3922c 100644 --- a/collada/header.py +++ b/collada/header.py @@ -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: ' with `symbol` to 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()) diff --git a/collada/main.py b/collada/main.py index e310c87..1e47852 100644 --- a/collada/main.py +++ b/collada/main.py @@ -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) diff --git a/collada/parse.py b/collada/parse.py index 23277cb..e3b70c6 100644 --- a/collada/parse.py +++ b/collada/parse.py @@ -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) diff --git a/collada/types.py b/collada/types.py index be946cc..d53d9b4 100644 --- a/collada/types.py +++ b/collada/types.py @@ -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 diff --git a/include/collada_types.hpp b/include/collada_types.hpp index 9f9623e..8fe3cb8 100644 --- a/include/collada_types.hpp +++ b/include/collada_types.hpp @@ -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; diff --git a/src/scenes/curve_interpolation/curve_interpolation.cpp b/src/scenes/curve_interpolation/curve_interpolation.cpp index 698ded8..12d2e77 100644 --- a/src/scenes/curve_interpolation/curve_interpolation.cpp +++ b/src/scenes/curve_interpolation/curve_interpolation.cpp @@ -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,