diff --git a/collada/generate.py b/collada/generate.py index bfa5583..7a6df86 100644 --- a/collada/generate.py +++ b/collada/generate.py @@ -17,7 +17,7 @@ def _render(out, lines): if l and (l[0] == "}" or l[0] == ")"): level -= 2 if level < 0: - assert namespace >= 0 + assert namespace >= 0, l namespace -= 1 level = 0 diff --git a/collada/header.py b/collada/header.py index db1062c..98ae9b3 100644 --- a/collada/header.py +++ b/collada/header.py @@ -1,5 +1,6 @@ from typing import Dict, List, Any +from collections import defaultdict from dataclasses import dataclass from itertools import islice, chain from io import BytesIO @@ -34,12 +35,12 @@ class State: # 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] + # channel nodes: node_id to list of sanitized target names + node_animation_channels: Dict[str, set] + def __init__(self): self.vertex_buffer = BytesIO() self.index_buffer = BytesIO() @@ -47,14 +48,15 @@ class State: self.geometry__vertex_index_tables = {} self.node_names = {} self.symbol_names = {} - self.joint_sids = {} self.emitted_input_elements_arrays = {} + self.node_animation_channels = defaultdict(set) -def sanitize_name(state, name, value): - assert name is not None +def sanitize_name(state, name, value, *, allow_slash=False): + assert name is not None, value assert type(name) is str - assert '/' not in name, name - c_id = name.lower().replace('-', '_').replace('.', '_') + if not allow_slash: + assert '/' not in name, name + c_id = name.lower().replace('-', '_').replace('.', '_').replace('/', '_') assert c_id not in state.node_names or state.node_names[c_id] is value state.symbol_names[c_id] = value @@ -282,20 +284,37 @@ def get_node_name_id(node): 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 +def render_node_children(state, collada, node_name, nodes): + yield f"node const * const node_children_{node_name} = {{" + for node in nodes: + node_name_id = get_node_name_id(node) + node_name = sanitize_name(state, node_name_id, node) + yield "&node_{node_name}," + yield "};" +def render_node_channels(state, collada, node, node_name): + if node.id is None: + # nodes with no ID can't have channels + yield f"channel const * const node_channels_{node_name}[] = {{}};" + return + + target_names = state.node_animation_channels[node.id] + yield f"channel const * const node_channels_{node_name}[] = {{" + for target_name in target_names: + yield f"&node_channel_{target_name}," + yield "};" + +def render_node(state, collada, node): # render children first - for node in node.nodes: - yield from render_node(state, collada, node) + for child_node in node.nodes: + yield from render_node(state, collada, child_node) 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_channels(state, collada, node, node_name) type = { types.NodeType.JOINT: "JOINT", @@ -310,6 +329,12 @@ def render_node(state, collada, node): yield "" yield f".instance_geometries = instance_geometries_{node_name}," yield f".instance_geometries_count = {len(node.instance_geometries)}," + yield "" + yield f".channels = node_channels_{node_name}," + yield f".channels_count = {len(state.node_animation_channels[node.id])}," + yield "" + yield f".nodes = node_children_{node_name}," + yield f".nodes_count = {len(node.nodes)}," yield "};" def linear_nodes(collada): @@ -446,13 +471,166 @@ def render_descriptor(): def render_end_of_namespace(): yield "}" +def render_animation_children(state, collada, animation_name, animations): + yield f"node const * const animation_children_{animation_name} = {{" + for animation in animations: + animation_name = sanitize_name(state, animation.id, animation) + yield "&animation_{animation_name}," + yield "};" + +def array_c_type(accessor): + if accessor.params[0].name == "INTERPOLATION": + return "interpolation" + assert all(param.type == "float" for param in accessor.params) + if accessor.stride == 1: + return "float" + elif accessor.stride in {2, 3, 4}: + assert list(param.name for param in accessor.params) == ["X", "Y", "Z", "W"][:accessor.stride], accessor.params + return f"float{accessor.stride}" + else: + assert False, accessor.stride + +def render_array(state, collada, accessor, array): + array_name = sanitize_name(state, array.id, array) + # render the array + if type(array) is types.NameArray: + assert accessor.stride == 1 + assert accessor.params[0].name == "INTERPOLATION" + assert len(array.names) == accessor.count + yield f"enum interpolation const array_{array_name}[] = {{" + for name in array.names: + assert name in {"BEZIER", "LINEAR"}, name + yield f"interpolation::{name}," + yield "};" + return "interpolation" + elif type(array) is types.FloatArray: + c_type = array_c_type(accessor) + yield f"{c_type} const array_{array_name}[] = {{" + it = iter(array.floats) + for i in range(accessor.count): + vector = ", ".join(f"{float(f)}f" for f in islice(it, accessor.stride)) + yield f"{{ {vector} }}," + yield "};" + else: + assert False, type(array) + +def render_source(state, collada, field_name, source): + array_name = sanitize_name(state, source.array_element.id, source.array_element) + c_type = array_c_type(source.technique_common.accessor) + source_name = sanitize_name(state, source.id, source) + #yield f"source const source_{source_name} = {{" + yield f"// {source_name}" + yield f".{field_name} = {{" + yield f".{c_type}_array = array_{array_name}," + yield f".count = {source.technique_common.accessor.count}," + yield "}," + +def render_sampler(state, collada, sampler): + order = dict((s, i) for i, s in + enumerate(["INPUT", "OUTPUT", "IN_TANGENT", "OUT_TANGENT", "INTERPOLATION"])) + inputs = sorted((input for input in sampler.inputs if input.semantic in order), + key=lambda input: order[input.semantic]) + + # render the source arrays first + for input in inputs: + assert type(input) is types.InputUnshared + source = collada.lookup(input.source, types.SourceCore) + yield from render_array(state, collada, source.technique_common.accessor, source.array_element) + + sampler_name = sanitize_name(state, sampler.id, sampler) + yield f"sampler const sampler_{sampler_name} = {{" + for input in inputs: + source = collada.lookup(input.source, types.SourceCore) + field_name = input.semantic.lower() + yield from render_source(state, collada, field_name, source) + yield "};" + +target_attributes = { + "A", "ANGLE", "B", "G", "P", "Q", "R", "S", "T", "TIME", "U", "V", "W", "X", "Y", "Z" +} + +def render_transform_type(transformation_element): + return { + types.Lookat: "LOOKAT", + types.Matrix: "MATRIX", + types.Rotate: "ROTATE", + types.Scale: "SCALE", + types.Skew: "SKEW", + types.Translate: "TRANSLATE", + }[type(transformation_element)] + +def render_channel(state, collada, channel): + sampler = collada.lookup(channel.source, types.Sampler) + sampler_name = sanitize_name(state, sampler.id, sampler) + + assert '/' in channel.target, channel.target + assert '.' in channel.target, channel.target + assert "(" not in channel.target, channel.target + + node_id, rest = channel.target.split("/") + node_transform_sid, target_attribute = rest.split(".") + assert target_attribute in target_attributes + + node = collada.lookup(f"#{node_id}", types.Node) + node_name_id = get_node_name_id(node) + node_name = sanitize_name(state, node_name_id, node) + transformation_element = node.sid_lookup[node_transform_sid] + + target_name = sanitize_name(state, channel.target, channel, allow_slash=True) + assert target_name not in state.node_animation_channels[node.id] + state.node_animation_channels[node.id].add(target_name) + + yield f"channel const node_channel_{target_name} = {{" + yield f".source_sampler = &sampler_{sampler_name}," + yield f".target_transform_type = transform_type::{render_transform_type(transformation_element)}," + yield f".target_attribute = target_attribute::{target_attribute}," + yield "};" + +def render_animation(state, collada, animation_name, animation): + # render children first + for i, child_animation in enumerate(animation.animations): + child_animation_name = f"{animation_name}_{i}" + yield from render_animation(state, collada, child_animation_name, child_animation) + + # samplers (includes sources) + for sampler in animation.samplers: + yield from render_sampler(state, collada, sampler) + + for channel in animation.channels: + yield from render_channel(state, collada, channel) + + # all animations channels are referenced from the node (inverse + # the relationship in collada) + # + # I haven't considered how nested or layered animations are + # affected by this inversion. + + #yield f"animation const animation_{animation_name} = {{" + #yield f".animations = animation_children_{animation_name}," + #yield f".animations_count = {len(animation.animations)}," + #yield "" + #yield f".channels = animation_channels_{animation_name}" + #yield f".channels_count = {len(animation.channels)}" + #yield "};" + +def render_library_animations(state, collada): + animation_ix = 0 + for library_animations in collada.library_animations: + for animation in library_animations.animations: + animation_name = f"{animation_ix}" + yield from render_animation(state, collada, animation_name, animation) + animation_ix += 1 + def render_all(collada, namespace): state = State() render, out = renderer() render(render_header(namespace)) + render(render_library_animations(state, collada)) render(render_library_effects(state, collada)) render(render_library_materials(state, collada)) render(render_library_geometries(state, collada)) + + # root elements render(render_library_visual_scenes(state, collada)) render(render_input_elements_list(state)) render(render_descriptor()) diff --git a/collada/parse.py b/collada/parse.py index 0d47c7e..23277cb 100644 --- a/collada/parse.py +++ b/collada/parse.py @@ -874,7 +874,7 @@ def parse_animation(lookup, root): sources.append(parse_source_core(lookup, child)) if child.tag == tag("sampler"): samplers.append(parse_sampler(lookup, child)) - if child.tag == tag("channels"): + if child.tag == tag("channel"): channels.append(parse_channel(lookup, child)) animation = types.Animation(id, name, animations, sources, samplers, channels) diff --git a/include/collada_types.hpp b/include/collada_types.hpp index 02cd981..3fd3d08 100644 --- a/include/collada_types.hpp +++ b/include/collada_types.hpp @@ -2,6 +2,11 @@ namespace collada { + struct float2 { + float const x; + float const y; + }; + struct float3 { float const x; float const y; @@ -25,32 +30,6 @@ namespace collada { float const g; }; - ////////////////////////////////////////////////////////////////////// - // animation - ////////////////////////////////////////////////////////////////////// - - enum class interpolation { - LINEAR, - BEZIER, - }; - - struct source { - union { - float const * const float_array; - enum interpolation const name_array; - }; - int const count; - int const stride; - }; - - struct sampler { - source const input; - source const output; - source const intangent; - source const outangent; - source const interpolation; - }; - ////////////////////////////////////////////////////////////////////// // geometry ////////////////////////////////////////////////////////////////////// @@ -228,6 +207,74 @@ namespace collada { int const instance_materials_count; }; + ////////////////////////////////////////////////////////////////////// + // animation + ////////////////////////////////////////////////////////////////////// + + enum class interpolation { + LINEAR, + BEZIER, + }; + + struct source { + union { + float const * const float_array; + float2 const * const float2_array; + float3 const * const float3_array; + float4 const * const float4_array; + enum interpolation const * const interpolation_array; + }; + int const count; + }; + + struct sampler { + source const input; + source const output; + source const in_tangent; + source const out_tangent; + source const interpolation; + }; + + enum class target_attribute { + A, // alpha color component + ANGLE, // euler angle + B, // blue color component + G, // green color component + P, // third texture component + Q, // fourth texture component + R, // red color component + S, // first texture coordinate + T, // second texture coordinate + TIME, // time in seconds + U, // first generic parameter + V, // second generic parameter + W, // fourth cartesian coordinate + X, // first cartesian coordinate + Y, // second cartesian coordinate + Z, // third cartesian coordinate + }; + + struct channel { + sampler const * const source_sampler; + int const target_node_index; // an index into the nodes array + transform_type const target_transform_type; + target_attribute const target_attribute; + }; + + /* + struct animation { + animation const * const animations; // nested animations + int const animations_count; + + channels const * const channels; + int const channels_count; + }; + */ + + ////////////////////////////////////////////////////////////////////// + // scene + ////////////////////////////////////////////////////////////////////// + struct node { node_type const type; @@ -237,6 +284,9 @@ namespace collada { instance_geometry const * const instance_geometries; int const instance_geometries_count; + channel const * const * const channels; + int const channels_count; + node const * const nodes; int const nodes_count; }; @@ -245,7 +295,10 @@ namespace collada { node const * const * const nodes; int const nodes_count; - inputs const * inputs_list; + inputs const * const inputs_list; int const inputs_list_count; + + //animation const * const animations; + //int const animations_count; }; } diff --git a/include/scenes/curve_interpolation.hpp b/include/scenes/curve_interpolation.hpp index 3e3f34d..2da5128 100644 --- a/include/scenes/curve_interpolation.hpp +++ b/include/scenes/curve_interpolation.hpp @@ -4,6 +4,144 @@ namespace curve_interpolation { using namespace collada; +float const array_node_cube_translation_x_input_array[] = { + { 0.0f }, + { 1.666667f }, + { 3.333333f }, + { 5.0f }, +}; + +float const array_node_cube_translation_x_output_array[] = { + { 10.0f }, + { -10.0f }, + { 10.0f }, + { -10.0f }, +}; + +float2 const array_node_cube_translation_x_intangent_array[] = { + { -0.3332306f, 10.0f }, + { 1.111167f, -10.0f }, + { 2.778333f, 10.0f }, + { 4.4445f, -9.219337f }, +}; + +float2 const array_node_cube_translation_x_outtangent_array[] = { + { 0.5555f, 10.0f }, + { 2.222167f, -10.0f }, + { 3.888333f, 10.0f }, + { 4.000208f, -8.594958f }, +}; + +enum interpolation const array_node_cube_translation_x_interpolation_array[] = { + interpolation::BEZIER, + interpolation::BEZIER, + interpolation::BEZIER, + interpolation::BEZIER, +}; + +sampler const sampler_node_cube_translation_x_sampler = { + // node_cube_translation_x_input + .input = { + .float_array = array_node_cube_translation_x_input_array, + .count = 4, + }, + // node_cube_translation_x_output + .output = { + .float_array = array_node_cube_translation_x_output_array, + .count = 4, + }, + // node_cube_translation_x_intangent + .in_tangent = { + .float2_array = array_node_cube_translation_x_intangent_array, + .count = 4, + }, + // node_cube_translation_x_outtangent + .out_tangent = { + .float2_array = array_node_cube_translation_x_outtangent_array, + .count = 4, + }, + // node_cube_translation_x_interpolation + .interpolation = { + .interpolation_array = array_node_cube_translation_x_interpolation_array, + .count = 4, + }, +}; + +float const array_node_cube_translation_y_input_array[] = { + { -0.8333334f }, + { 0.8333334f }, + { 2.5f }, + { 4.166667f }, +}; + +float const array_node_cube_translation_y_output_array[] = { + { -10.05776f }, + { 10.05852f }, + { -9.941484f }, + { 10.05852f }, +}; + +float2 const array_node_cube_translation_y_intangent_array[] = { + { -1.166264f, -10.05776f }, + { 0.2778334f, 10.05852f }, + { 1.9445f, -9.941484f }, + { 3.611667f, 10.05852f }, +}; + +float2 const array_node_cube_translation_y_outtangent_array[] = { + { -0.2783333f, -10.05776f }, + { 1.388833f, 10.05852f }, + { 3.0555f, -9.941484f }, + { 4.499598f, 10.05852f }, +}; + +enum interpolation const array_node_cube_translation_y_interpolation_array[] = { + interpolation::BEZIER, + interpolation::BEZIER, + interpolation::BEZIER, + interpolation::BEZIER, +}; + +sampler const sampler_node_cube_translation_y_sampler = { + // node_cube_translation_y_input + .input = { + .float_array = array_node_cube_translation_y_input_array, + .count = 4, + }, + // node_cube_translation_y_output + .output = { + .float_array = array_node_cube_translation_y_output_array, + .count = 4, + }, + // node_cube_translation_y_intangent + .in_tangent = { + .float2_array = array_node_cube_translation_y_intangent_array, + .count = 4, + }, + // node_cube_translation_y_outtangent + .out_tangent = { + .float2_array = array_node_cube_translation_y_outtangent_array, + .count = 4, + }, + // node_cube_translation_y_interpolation + .interpolation = { + .interpolation_array = array_node_cube_translation_y_interpolation_array, + .count = 4, + }, +}; + +channel const node_channel_node_cube_translation_x = { + .source_sampler = &sampler_node_cube_translation_x_sampler, + .target_transform_type = transform_type::TRANSLATE, + .target_attribute = target_attribute::X, +}; + +channel const node_channel_node_cube_translation_y = { + .source_sampler = &sampler_node_cube_translation_y_sampler, + .target_transform_type = transform_type::TRANSLATE, + .target_attribute = target_attribute::Y, +}; + effect const effect_material__15 = { .type = effect_type::BLINN, .blinn = { @@ -372,12 +510,17 @@ geometry const * const geometries[] = { &geometry_geom_plane001, }; +node const * const node_children_node_environmentambientlight = { +}; + transform const transforms_node_environmentambientlight[] = { }; instance_geometry const instance_geometries_node_environmentambientlight[] = { }; +channel const * const node_channels_node_environmentambientlight[] = {}; + node const node_node_environmentambientlight = { .type = node_type::NODE, @@ -386,6 +529,15 @@ node const node_node_environmentambientlight = { .instance_geometries = instance_geometries_node_environmentambientlight, .instance_geometries_count = 0, + + .channels = node_channels_node_environmentambientlight, + .channels_count = 0, + + .nodes = node_children_node_environmentambientlight, + .nodes_count = 0, +}; + +node const * const node_children_node_cube = { }; transform const transforms_node_cube[] = { @@ -430,6 +582,11 @@ instance_geometry const instance_geometries_node_cube[] = { }, }; +channel const * const node_channels_node_cube[] = { + &node_channel_node_cube_translation_x, + &node_channel_node_cube_translation_y, +}; + node const node_node_cube = { .type = node_type::NODE, @@ -438,6 +595,15 @@ node const node_node_cube = { .instance_geometries = instance_geometries_node_cube, .instance_geometries_count = 1, + + .channels = node_channels_node_cube, + .channels_count = 2, + + .nodes = node_children_node_cube, + .nodes_count = 0, +}; + +node const * const node_children_node_cylinder001 = { }; transform const transforms_node_cylinder001[] = { @@ -458,6 +624,9 @@ instance_geometry const instance_geometries_node_cylinder001[] = { }, }; +channel const * const node_channels_node_cylinder001[] = { +}; + node const node_node_cylinder001 = { .type = node_type::NODE, @@ -466,6 +635,15 @@ node const node_node_cylinder001 = { .instance_geometries = instance_geometries_node_cylinder001, .instance_geometries_count = 1, + + .channels = node_channels_node_cylinder001, + .channels_count = 0, + + .nodes = node_children_node_cylinder001, + .nodes_count = 0, +}; + +node const * const node_children_node_plane001 = { }; transform const transforms_node_plane001[] = { @@ -494,6 +672,9 @@ instance_geometry const instance_geometries_node_plane001[] = { }, }; +channel const * const node_channels_node_plane001[] = { +}; + node const node_node_plane001 = { .type = node_type::NODE, @@ -502,6 +683,12 @@ node const node_node_plane001 = { .instance_geometries = instance_geometries_node_plane001, .instance_geometries_count = 1, + + .channels = node_channels_node_plane001, + .channels_count = 0, + + .nodes = node_children_node_plane001, + .nodes_count = 0, }; node const * const nodes[] = {