From 226057dd61353eabcd2dd52aa2bc37be9f66504f Mon Sep 17 00:00:00 2001 From: James Lambert Date: Fri, 30 Dec 2022 20:28:27 -0700 Subject: [PATCH] Build out level animation export logic --- Makefile | 8 +- skelatool64/doc/index.html | 2 +- skelatool64/doc/modules/sk_animation.html | 196 +++++++++++++++++- .../doc/modules/sk_definition_writer.html | 2 +- skelatool64/doc/modules/sk_input.html | 2 +- skelatool64/doc/modules/sk_math.html | 2 +- skelatool64/doc/modules/sk_mesh.html | 2 +- skelatool64/doc/modules/sk_scene.html | 2 +- skelatool64/doc/modules/sk_transform.html | 2 +- skelatool64/lua/sk_animation.lua | 147 ++++++++++--- skelatool64/lua/sk_definition_writer.lua | 2 - src/levels/level_definition.h | 13 ++ tools/export_level.lua | 2 + tools/level_scripts/animation.lua | 78 ++++++- tools/level_scripts/static_export.lua | 20 +- 15 files changed, 422 insertions(+), 58 deletions(-) diff --git a/Makefile b/Makefile index 16d31d5..6aeef3d 100644 --- a/Makefile +++ b/Makefile @@ -210,9 +210,9 @@ build/src/player/player.o: build/assets/models/player/chell.h build/assets/mater build/assets/models/player/chell.h: assets/materials/chell.skm.yaml -build/anims.ld: $(ANIM_LIST) tools/generate_animation_ld.js +build/anims.ld: $(ANIM_LIST) $(ANIM_TEST_CHAMBERS) tools/generate_animation_ld.js @mkdir -p $(@D) - node tools/generate_animation_ld.js $@ $(ANIM_LIST) + node tools/generate_animation_ld.js $@ $(ANIM_LIST) $(ANIM_TEST_CHAMBERS) #################### ## Test Chambers @@ -223,6 +223,8 @@ TEST_CHAMBERS = assets/test_chambers/test_chamber_00/test_chamber_00.blend \ assets/test_chambers/test_chamber_02/test_chamber_02.blend \ assets/test_chambers/test_chamber_03/test_chamber_03.blend +ANIM_TEST_CHAMBERS = build/assets/test_chambers/test_chamber_03/test_chamber_03_anim.o + TEST_CHAMBER_HEADERS = $(TEST_CHAMBERS:%.blend=build/%.h) TEST_CHAMBER_OBJECTS = $(TEST_CHAMBERS:%.blend=build/%_geo.o) @@ -232,7 +234,7 @@ build/%.fbx: %.blend @mkdir -p $(@D) $(BLENDER_3_0) $< --background --python tools/export_fbx.py -- $@ -build/assets/test_chambers/%.h build/assets/test_chambers/%_geo.c: build/assets/test_chambers/%.fbx build/assets/materials/static.h $(SKELATOOL64) $(TEXTURE_IMAGES) $(LUA_FILES) +build/assets/test_chambers/%.h build/assets/test_chambers/%_geo.c build/assets/test_chambers/%_anim.c: build/assets/test_chambers/%.fbx build/assets/materials/static.h $(SKELATOOL64) $(TEXTURE_IMAGES) $(LUA_FILES) $(SKELATOOL64) --script tools/export_level.lua --fixed-point-scale 256 --model-scale 0.01 --name $(<:build/assets/test_chambers/%.fbx=%) -m assets/materials/static.skm.yaml -o $(<:%.fbx=%.h) $< build/assets/test_chambers/%.o: build/assets/test_chambers/%.c build/assets/materials/static.h diff --git a/skelatool64/doc/index.html b/skelatool64/doc/index.html index 52e3c78..0cf2d79 100644 --- a/skelatool64/doc/index.html +++ b/skelatool64/doc/index.html @@ -82,7 +82,7 @@
generated by LDoc 1.4.6 -Last updated 2022-12-29 21:32:18 +Last updated 2022-12-30 12:30:52
diff --git a/skelatool64/doc/modules/sk_animation.html b/skelatool64/doc/modules/sk_animation.html index d2e5579..66e3b7b 100644 --- a/skelatool64/doc/modules/sk_animation.html +++ b/skelatool64/doc/modules/sk_animation.html @@ -33,6 +33,7 @@

Contents

@@ -64,14 +65,33 @@ - export_animations (name_hint, armature, animations, file_suffix, animation_file_suffix) + export_animation (animation, armature, name_hint, file_suffix, animation_file_suffix) + + + + filter_animations_for_armature (armature, animations) + + + + export_armature (armature, gfx_reference_or_nil, name_hint, file_suffix) + + + +

Tables

+ + +
Clip

Class Armature

- + + + + +
Armature:has_node ()Armature:has_node (Node)
Armature:get_parent_bone (Node)
@@ -111,8 +131,8 @@
- - export_animations (name_hint, armature, animations, file_suffix, animation_file_suffix) + + export_animation (animation, armature, name_hint, file_suffix, animation_file_suffix)
@@ -120,16 +140,16 @@

Parameters:

+

Returns:

+
    + + Clip + the exported clip object use sk_definition_writer.reference_to(clip) to reference in other data +
+ + + + +
+
+ + filter_animations_for_armature (armature, animations) +
+
+ + + +

Parameters:

+ + +

Returns:

+
    + + {sk_scene.Animation,...} + +
+ + + + +
+
+ + export_armature (armature, gfx_reference_or_nil, name_hint, file_suffix) +
+
+ + + +

Parameters:

+ + +

Returns:

+
    + + table + +
+ + + + +
+ +

Tables

+ +
+
+ + Clip +
+
+ + + +

Fields:

+
    +
  • nFrames + number + +
  • +
  • nBones + number + +
  • +
  • frames + sk_definition_writer.RefType + +
  • +
  • fps + number + +
  • +
+ @@ -153,13 +286,56 @@
- Armature:has_node () + Armature:has_node (Node)
+

Parameters:

+
    +
  • Node + sk_scene.Node +
  • +
+ +

Returns:

+
    + + boolean + +
+ + + + +
+
+ + Armature:get_parent_bone (Node) +
+
+ + + +

Parameters:

+
    +
  • Node + sk_scene.Node + +
  • +
+ +

Returns:

+
    +
  1. + sk_scene.Node or nil +
  2. +
  3. + number + bone_index|nil
  4. +
@@ -172,7 +348,7 @@
generated by LDoc 1.4.6 -Last updated 2022-12-29 21:32:18 +Last updated 2022-12-30 12:30:52
diff --git a/skelatool64/doc/modules/sk_definition_writer.html b/skelatool64/doc/modules/sk_definition_writer.html index 923d920..6622324 100644 --- a/skelatool64/doc/modules/sk_definition_writer.html +++ b/skelatool64/doc/modules/sk_definition_writer.html @@ -542,7 +542,7 @@
generated by LDoc 1.4.6 -Last updated 2022-12-29 21:32:18 +Last updated 2022-12-30 12:30:52
diff --git a/skelatool64/doc/modules/sk_input.html b/skelatool64/doc/modules/sk_input.html index 1368c8c..8718cf1 100644 --- a/skelatool64/doc/modules/sk_input.html +++ b/skelatool64/doc/modules/sk_input.html @@ -136,7 +136,7 @@
generated by LDoc 1.4.6 -Last updated 2022-12-29 21:32:18 +Last updated 2022-12-30 12:30:52
diff --git a/skelatool64/doc/modules/sk_math.html b/skelatool64/doc/modules/sk_math.html index c0d824b..5702ee5 100644 --- a/skelatool64/doc/modules/sk_math.html +++ b/skelatool64/doc/modules/sk_math.html @@ -901,7 +901,7 @@
generated by LDoc 1.4.6 -Last updated 2022-12-29 21:32:18 +Last updated 2022-12-30 12:30:52
diff --git a/skelatool64/doc/modules/sk_mesh.html b/skelatool64/doc/modules/sk_mesh.html index 1739a3b..513725f 100644 --- a/skelatool64/doc/modules/sk_mesh.html +++ b/skelatool64/doc/modules/sk_mesh.html @@ -303,7 +303,7 @@
generated by LDoc 1.4.6 -Last updated 2022-12-29 21:32:18 +Last updated 2022-12-30 12:30:52
diff --git a/skelatool64/doc/modules/sk_scene.html b/skelatool64/doc/modules/sk_scene.html index 1b43fc0..c65a88c 100644 --- a/skelatool64/doc/modules/sk_scene.html +++ b/skelatool64/doc/modules/sk_scene.html @@ -429,7 +429,7 @@
generated by LDoc 1.4.6 -Last updated 2022-12-29 21:32:18 +Last updated 2022-12-30 12:30:52
diff --git a/skelatool64/doc/modules/sk_transform.html b/skelatool64/doc/modules/sk_transform.html index 21d0fd0..890d351 100644 --- a/skelatool64/doc/modules/sk_transform.html +++ b/skelatool64/doc/modules/sk_transform.html @@ -187,7 +187,7 @@
generated by LDoc 1.4.6 -Last updated 2022-12-29 21:32:18 +Last updated 2022-12-30 12:30:52
diff --git a/skelatool64/lua/sk_animation.lua b/skelatool64/lua/sk_animation.lua index 6c942f6..3bb64d2 100644 --- a/skelatool64/lua/sk_animation.lua +++ b/skelatool64/lua/sk_animation.lua @@ -80,15 +80,15 @@ local function build_armature(animation_nodes) return (node_order[a] or 0) < (node_order[b] or 0) end) - local nodes_as_set = {} + local node_to_index = {} - for _, node in pairs(nodes) do - nodes_as_set[node] = true + for index, node in pairs(nodes) do + node_to_index[node] = index end return setmetatable({ nodes = nodes, - nodes_as_set = nodes_as_set, + node_to_index = node_to_index, }, Armature) end @@ -106,12 +106,17 @@ local function add_to_node_pose(node_pose, node) end end -local function build_node_pose(node, node_pose) +local function build_node_pose(armature, node, node_pose) local result = nil while node do result = result and (node_pose[node] * result) or node_pose[node] node = node.parent + + -- only build the transform relative to the ancestor in the chain + if node and armature:has_node(node) then + return result + end end return result @@ -135,7 +140,7 @@ end local function build_armature_pose(armature, node_pose, result) for _, node in pairs(armature.nodes) do - local pose = build_node_pose(node, node_pose) + local pose = build_node_pose(armature, node, node_pose) local pos, rot = pose:decompose() @@ -170,26 +175,30 @@ local function build_animation(armature, animation) return frames, n_frames end ---- @function export_animations ---- @tparam string name_hint +--- @table Clip +--- @tfield number nFrames +--- @tfield number nBones +--- @tfield sk_definition_writer.RefType frames +--- @tfield number fps + +--- @function build_animation_clip +--- @tparam sk_scene.Animation animation --- @tparam Armature armature ---- @tparam {sk_scene.Animation,...} animations ---- @tparam string file_suffix --- @tparam string animation_file_suffix -local function export_animations(name_hint, armature, animations, file_suffix, animation_file_suffix) - for _, animation in pairs(animations) do - local animation_frames, n_frames = build_animation(armature, animation) - sk_definition_writer.add_definition(name_hint .. animation.name .. '_frames', 'struct SKAnimationBoneFrame[]', animation_file_suffix, animation_frames) +--- @treturn Clip the exported clip object use sk_definition_writer.reference_to(clip) to reference in other data +local function build_animation_clip(animation, armature, animation_file_suffix) + local animation_frames, n_frames = build_animation(armature, animation) + + sk_definition_writer.add_definition(animation.name .. '_frames', 'struct SKAnimationBoneFrame[]', animation_file_suffix, animation_frames) - local clip = { - nFrames = n_frames, - nBones = #armature.nodes, - frames = sk_definition_writer.reference_to(animation_frames, 1), - fps = sk_input.settings.ticks_per_second - } + local clip = { + nFrames = n_frames, + nBones = #armature.nodes, + frames = sk_definition_writer.reference_to(animation_frames, 1), + fps = sk_input.settings.ticks_per_second + } - sk_definition_writer.add_definition(name_hint .. animation.name .. '_clip', 'struct SKAnimationClip', file_suffix, clip) - end + return clip end local function build_armature_for_animations(animations) @@ -210,18 +219,108 @@ local function build_armature_for_animations(animations) return build_armature(all_nodes) end +--- @function filter_animations_for_armature +--- @tparam Armature armature +--- @tparam {sk_scene.Animation,...} animations +--- @treturn {sk_scene.Animation,...} +local function filter_animations_for_armature(armature, animations) + local result = {} + + for _, animation in pairs(animations) do + for _, channel in pairs(animation.channels) do + if armature:has_node(sk_scene.node_with_name(channel.node_name)) then + table.insert(result, animation) + break + end + end + end + + return result +end + +--- @function build_armature_data +--- @tparam Armature armature +--- @tparam sk_definition_writer.RefType|nil gfx_reference_or_nil +--- @tparam string name_hint +--- @tparam string file_suffix +--- @treturn table +local function build_armature_data(armature, gfx_reference_or_nil, name_hint, file_suffix) + + local node_pose = {} + local transforms = {} + local parent_mapping = {} + + for _, node in pairs(armature.nodes) do + add_to_node_pose(node_pose, node) + + -- calculate base pose + local relative_transform = build_node_pose(armature, node, node_pose) + + local pos, rot, scale = relative_transform:decompose() + + table.insert(transforms, {pos, rot, scale}) + + -- calculate parent mapping + local _, parent_index = armature:get_parent_bone(node) + + if parent_index then + table.insert(parent_mapping, parent_index - 1) + else + table.insert(parent_mapping, sk_definition_writer.raw('NO_BONE_PARENT')) + end + end + + sk_definition_writer.add_definition(name_hint .. '_base_transform', 'struct Transform[]', file_suffix, transforms) + sk_definition_writer.add_definition(name_hint .. '_parent_mapping', 'unsigned short[]', file_suffix, parent_mapping) + + return { + displayList = gfx_reference_or_nil or sk_definition_writer.null_value, + boneTransforms = sk_definition_writer.reference_to(transforms, 1), + numberOfBones = #armature.nodes, + numberOfAttachments = 0, + boneParentIndex = sk_definition_writer.reference_to(parent_mapping, 1), + } +end + --- @type Armature --- @tfield {sk_scene.Node,...} nodes Armature.__index = Armature; --- @function has_node +--- @tparam sk_scene.Node Node +--- @treturn boolean Armature.has_node = function(armature, node) - return armature.nodes_as_set[node] or false + return armature.node_to_index[node] or false +end + +--- @function get_parent_bone +--- @tparam sk_scene.Node Node +--- @treturn sk_scene.Node|nil +--- @treturn number bone_index|nil +Armature.get_parent_bone = function(armature, node) + local curr = node + + while curr do + -- check if current parent is in the armature + local parent_index = armature.node_to_index[node.parent] + + if parent_index then + -- return the parent bone if in armature + return armature.nodes[parent_index], parent_index + end + + curr = curr.parent + end + + -- no parent bone in armature so return nil + return nil, nil end return { build_armature = build_armature, - export_animations = export_animations, build_armature_for_animations = build_armature_for_animations, + build_armature_data = build_armature_data, + filter_animations_for_armature = filter_animations_for_armature, + build_animation_clip = build_animation_clip, Armature = Armature, } \ No newline at end of file diff --git a/skelatool64/lua/sk_definition_writer.lua b/skelatool64/lua/sk_definition_writer.lua index 6a9805d..34e5c1b 100644 --- a/skelatool64/lua/sk_definition_writer.lua +++ b/skelatool64/lua/sk_definition_writer.lua @@ -300,12 +300,10 @@ local function process_definitions(definitions) local name_mapping = {} for k, v in pairs(definitions) do - print("populating name mapping for " .. v.name) populate_name_mapping(v.name, v.data, name_mapping) end for k, v in pairs(definitions) do - print("replacing references for " .. v.name) v.data = replace_references(v.data, name_mapping, v.name) end end diff --git a/src/levels/level_definition.h b/src/levels/level_definition.h index 7d5e848..0e3891d 100644 --- a/src/levels/level_definition.h +++ b/src/levels/level_definition.h @@ -7,10 +7,15 @@ #include "../math/boxs16.h" #include "../math/box3d.h" #include "../math/range.h" +#include "../sk64/skelatool_clip.h" +#include "../sk64/skelatool_armature.h" + +#define NO_TRANSFORM_INDEX 0xFF struct StaticContentElement { Gfx* displayList; u8 materialIndex; + u8 transformIndex; }; struct BoundingSphere { @@ -167,6 +172,12 @@ struct BoxDropperDefinition { short signalIndex; }; +struct AnimationInfo { + struct SKArmature armature; + struct SKAnimationClip* clips; + short clipCount; +}; + struct LevelDefinition { struct CollisionObject* collisionQuads; struct StaticContentElement *staticContent; @@ -189,6 +200,7 @@ struct LevelDefinition { struct PedestalDefinition* pedestals; struct SignageDefinition* signage; struct BoxDropperDefinition* boxDroppers; + struct AnimationInfo* animations; short collisionQuadCount; short staticContentCount; short portalSurfaceCount; @@ -204,6 +216,7 @@ struct LevelDefinition { short pedestalCount; short signageCount; short boxDropperCount; + short animationInfoCount; short startLocation; }; diff --git a/tools/export_level.lua b/tools/export_level.lua index 9308003..506e13d 100644 --- a/tools/export_level.lua +++ b/tools/export_level.lua @@ -47,4 +47,6 @@ sk_definition_writer.add_definition("level", "struct LevelDefinition", "_geo", { signageCount = #entities.signage, signalOperators = sk_definition_writer.reference_to(signals.operators, 1), signalOperatorCount = #signals.operators, + animations = sk_definition_writer.reference_to(animation.animated_nodes, 1), + animationInfoCount = #animation.animated_nodes, }) \ No newline at end of file diff --git a/tools/level_scripts/animation.lua b/tools/level_scripts/animation.lua index 9cc0a65..30b1056 100644 --- a/tools/level_scripts/animation.lua +++ b/tools/level_scripts/animation.lua @@ -1,6 +1,80 @@ local sk_scene = require('sk_scene') local sk_animation = require('sk_animation') +local sk_scene = require('sk_scene') +local sk_definition_writer = require('sk_definition_writer') -local armature = sk_animation.build_armature_for_animations(sk_scene.scene.animations) +local armature_bones_by_name = {} -sk_animation.export_animations('animation', armature, sk_scene.scene.animations, '_geo', '_anim') \ No newline at end of file +for _, node in pairs(sk_scene.nodes_for_type('@anim')) do + local name = node.arguments[1] + local existing = armature_bones_by_name[name] + + if existing then + table.insert(existing, node.node) + else + armature_bones_by_name[name] = {node.node} + end +end + +local armatures = {} + +local node_to_bone_index = {} +local node_to_armature_index = {} +local bones_as_array = {} + +for name, nodes in pairs(armature_bones_by_name) do + table.insert(armatures, {name = name, armature = sk_animation.build_armature(nodes)}) +end + +table.sort(armatures, function(a, b) + return a.name < b.name +end) + +local animated_nodes = {} + +for index, armature in pairs(armatures) do + -- build an index used to attached parts of the scene + -- to animation nodes later + for _, node in pairs(armature.armature.nodes) do + table.insert(bones_as_array, node) + local bone_index = #bones_as_array + + sk_scene.for_each_node(node, function(child_node) + node_to_bone_index[child_node] = bone_index + node_to_armature_index[child_node] = index + end) + end + + local armature_data = sk_animation.build_armature_data(armature.armature, nil, armature.name, '_geo') + local animation_clips = {} + + for _, animation in pairs(sk_animation.filter_animations_for_armature(armature.armature, sk_scene.scene.animations)) do + local clip = sk_animation.build_animation_clip(animation, armature.armature, '_anim') + sk_definition_writer.add_macro(armature.name .. '_' .. animation.name, tostring(#animation_clips)) + table.insert(animation_clips, clip) + end + + sk_definition_writer.add_definition(armature.name .. '_clips', 'struct SKAnimationClip[]', '_geo', animation_clips) + + table.insert(animated_nodes, { + armature = armature_data, + clips = sk_definition_writer.reference_to(animation_clips, 1), + clipCount = #animation_clips + }) +end + +local function get_bone_index_for_node(node) + return node_to_bone_index[node] +end + +local function get_bone_for_index(index) + return bones_as_array[index] +end + +sk_definition_writer.add_definition('anim', 'struct AnimationInfo[]', '_geo', animated_nodes) + +return { + animated_nodes = animated_nodes, + get_bone_index_for_node = get_bone_index_for_node, + get_bone_for_index = get_bone_for_index, +} \ No newline at end of file diff --git a/tools/level_scripts/static_export.lua b/tools/level_scripts/static_export.lua index 659e44b..6337218 100644 --- a/tools/level_scripts/static_export.lua +++ b/tools/level_scripts/static_export.lua @@ -4,6 +4,7 @@ local sk_scene = require('sk_scene') local sk_mesh = require('sk_mesh') local sk_input = require('sk_input') local room_export = require('tools.level_scripts.room_export') +local animation = require('tools.level_scripts.animation') sk_definition_writer.add_header('"../build/assets/materials/static.h"') sk_definition_writer.add_header('"levels/level_definition.h"') @@ -33,33 +34,32 @@ local function proccessStaticNodes(nodes) mesh = chunkV.mesh, mesh_bb = mesh_bb, display_list = sk_definition_writer.raw(gfxName), - material_index = sk_definition_writer.raw(chunkV.material.macro_name) + material_index = sk_definition_writer.raw(chunkV.material.macro_name), + transform_index = animation.get_bone_index_for_node(v.node), + room_index = room_export.node_nearest_room_index(v.node) or 0 }) end end + table.sort(result, function(a, b) + return a.room_index < b.room_index + end) + return result; end local static_nodes = proccessStaticNodes(sk_scene.nodes_for_type('@static')) -for _, static_node in pairs(static_nodes) do - static_node.room_index = room_export.node_nearest_room_index(static_node.node) or 0 -end - local static_content_elements = {} -table.sort(static_nodes, function(a, b) - return a.room_index < b.room_index -end) - local room_ranges = {} local static_bounding_boxes = {} for index, static_node in pairs(static_nodes) do table.insert(static_content_elements, { displayList = static_node.display_list, - materialIndex = static_node.material_index + materialIndex = static_node.material_index, + transformIndex = static_node.transform_index and (static_node.transform_index - 1) or sk_definition_writer.raw('NO_TRANSFORM_INDEX'), }) table.insert(static_bounding_boxes, { static_node.mesh_bb.min.x,