From c98e604e76138470867b1d012eceb3b89174ac94 Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Sat, 17 May 2025 10:11:55 +0800 Subject: [PATCH] split animation components into GltfAnimation/MorphAnimation/BoneAnimation --- .../src/bindings/src/thermion_dart_ffi.g.dart | 56 ++- .../src/implementation/background_image.dart | 9 +- .../src/implementation/ffi_asset.dart | 68 ++-- .../lib/src/filament/src/interface/asset.dart | 60 ++- .../components/AnimationComponentManager.hpp | 357 ++++-------------- .../native/include/scene/AnimationManager.hpp | 33 +- .../native/src/c_api/TAnimationManager.cpp | 253 ++++++++++--- .../BoneAnimationComponentManager.cpp | 154 ++++++++ .../GltfAnimationComponentManager.cpp | 308 +++++++++++++++ .../MorphAnimationComponentManager.cpp | 76 ++++ .../native/src/scene/AnimationManager.cpp | 116 +++--- thermion_dart/test/animation_tests.dart | 102 +++-- 12 files changed, 1109 insertions(+), 483 deletions(-) create mode 100644 thermion_dart/native/src/components/BoneAnimationComponentManager.cpp create mode 100644 thermion_dart/native/src/components/GltfAnimationComponentManager.cpp create mode 100644 thermion_dart/native/src/components/MorphAnimationComponentManager.cpp diff --git a/thermion_dart/lib/src/bindings/src/thermion_dart_ffi.g.dart b/thermion_dart/lib/src/bindings/src/thermion_dart_ffi.g.dart index ba124147..1be20c3d 100644 --- a/thermion_dart/lib/src/bindings/src/thermion_dart_ffi.g.dart +++ b/thermion_dart/lib/src/bindings/src/thermion_dart_ffi.g.dart @@ -2935,20 +2935,52 @@ external void AnimationManager_update( int frameTimeInNanos, ); +@ffi.Native< + ffi.Bool Function( + ffi.Pointer, ffi.Pointer)>(isLeaf: true) +external bool AnimationManager_addGltfAnimationComponent( + ffi.Pointer tAnimationManager, + ffi.Pointer tSceneAsset, +); + +@ffi.Native< + ffi.Bool Function( + ffi.Pointer, ffi.Pointer)>(isLeaf: true) +external bool AnimationManager_removeGltfAnimationComponent( + ffi.Pointer tAnimationManager, + ffi.Pointer tSceneAsset, +); + @ffi.Native, EntityId)>( isLeaf: true) -external void AnimationManager_addAnimationComponent( +external void AnimationManager_addMorphAnimationComponent( ffi.Pointer tAnimationManager, int entityId, ); @ffi.Native, EntityId)>( isLeaf: true) -external void AnimationManager_removeAnimationComponent( +external void AnimationManager_removeMorphAnimationComponent( ffi.Pointer tAnimationManager, int entityId, ); +@ffi.Native< + ffi.Bool Function( + ffi.Pointer, ffi.Pointer)>(isLeaf: true) +external bool AnimationManager_addBoneAnimationComponent( + ffi.Pointer tAnimationManager, + ffi.Pointer tSceneAsset, +); + +@ffi.Native< + ffi.Bool Function( + ffi.Pointer, ffi.Pointer)>(isLeaf: true) +external bool AnimationManager_removeBoneAnimationComponent( + ffi.Pointer tAnimationManager, + ffi.Pointer tSceneAsset, +); + @ffi.Native< ffi.Bool Function( ffi.Pointer, @@ -2984,7 +3016,7 @@ external void AnimationManager_resetToRestPose( ); @ffi.Native< - ffi.Void Function( + ffi.Bool Function( ffi.Pointer, ffi.Pointer, ffi.Int, @@ -2995,7 +3027,7 @@ external void AnimationManager_resetToRestPose( ffi.Float, ffi.Float, ffi.Float)>(isLeaf: true) -external void AnimationManager_addBoneAnimation( +external bool AnimationManager_addBoneAnimation( ffi.Pointer tAnimationManager, ffi.Pointer tSceneAsset, int skinIndex, @@ -3041,7 +3073,7 @@ external void AnimationManager_getInverseBindMatrix( ); @ffi.Native< - ffi.Void Function( + ffi.Bool Function( ffi.Pointer, ffi.Pointer, ffi.Int, @@ -3050,9 +3082,9 @@ external void AnimationManager_getInverseBindMatrix( ffi.Bool, ffi.Float, ffi.Float)>(isLeaf: true) -external void AnimationManager_playAnimation( +external bool AnimationManager_playGltfAnimation( ffi.Pointer tAnimationManager, - ffi.Pointer sceneAsset, + ffi.Pointer tSceneAsset, int index, bool loop, bool reverse, @@ -3062,9 +3094,9 @@ external void AnimationManager_playAnimation( ); @ffi.Native< - ffi.Void Function(ffi.Pointer, ffi.Pointer, + ffi.Bool Function(ffi.Pointer, ffi.Pointer, ffi.Int)>(isLeaf: true) -external void AnimationManager_stopAnimation( +external bool AnimationManager_stopGltfAnimation( ffi.Pointer tAnimationManager, ffi.Pointer sceneAsset, int index, @@ -3073,7 +3105,7 @@ external void AnimationManager_stopAnimation( @ffi.Native< ffi.Float Function(ffi.Pointer, ffi.Pointer, ffi.Int)>(isLeaf: true) -external double AnimationManager_getAnimationDuration( +external double AnimationManager_getGltfAnimationDuration( ffi.Pointer tAnimationManager, ffi.Pointer sceneAsset, int animationIndex, @@ -3082,7 +3114,7 @@ external double AnimationManager_getAnimationDuration( @ffi.Native< ffi.Int Function( ffi.Pointer, ffi.Pointer)>(isLeaf: true) -external int AnimationManager_getAnimationCount( +external int AnimationManager_getGltfAnimationCount( ffi.Pointer tAnimationManager, ffi.Pointer sceneAsset, ); @@ -3090,7 +3122,7 @@ external int AnimationManager_getAnimationCount( @ffi.Native< ffi.Void Function(ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Int)>(isLeaf: true) -external void AnimationManager_getAnimationName( +external void AnimationManager_getGltfAnimationName( ffi.Pointer tAnimationManager, ffi.Pointer sceneAsset, ffi.Pointer outPtr, diff --git a/thermion_dart/lib/src/filament/src/implementation/background_image.dart b/thermion_dart/lib/src/filament/src/implementation/background_image.dart index fa950829..014e37c3 100644 --- a/thermion_dart/lib/src/filament/src/implementation/background_image.dart +++ b/thermion_dart/lib/src/filament/src/implementation/background_image.dart @@ -1,9 +1,6 @@ -import 'dart:typed_data'; import 'package:vector_math/vector_math_64.dart' as v64; import 'package:animation_tools_dart/src/bone_animation_data.dart'; import 'package:animation_tools_dart/src/morph_animation_data.dart'; -import 'package:thermion_dart/src/filament/src/interface/layers.dart'; -import 'package:thermion_dart/src/bindings/bindings.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_scene.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart'; @@ -159,7 +156,7 @@ class BackgroundImage extends ThermionAsset { } @override - Future addAnimationComponent(ThermionEntity entity) { + Future addAnimationComponent() { // TODO: implement addAnimationComponent throw UnimplementedError(); } @@ -256,7 +253,7 @@ class BackgroundImage extends ThermionAsset { } @override - Future removeAnimationComponent(ThermionEntity entity) { + Future removeAnimationComponent() { // TODO: implement removeAnimationComponent throw UnimplementedError(); } @@ -355,4 +352,6 @@ class BackgroundImage extends ThermionAsset { Future getBoundingBox() { throw UnimplementedError(); } + + } diff --git a/thermion_dart/lib/src/filament/src/implementation/ffi_asset.dart b/thermion_dart/lib/src/filament/src/implementation/ffi_asset.dart index 9b9adca3..40b0ae53 100644 --- a/thermion_dart/lib/src/filament/src/implementation/ffi_asset.dart +++ b/thermion_dart/lib/src/filament/src/implementation/ffi_asset.dart @@ -493,16 +493,13 @@ class FFIAsset extends ThermionAsset { if (weights.isEmpty) { throw Exception("Weights must not be empty"); } - var weightsPtr = allocate(weights.length); + var weightsF32 = Float32List.fromList(weights); - for (int i = 0; i < weights.length; i++) { - weightsPtr[i] = weights[i]; - } var success = await withBoolCallback((cb) { AnimationManager_setMorphTargetWeightsRenderThread( - animationManager, entity, weightsPtr, weights.length, cb); + animationManager, entity, weightsF32.address, weights.length, cb); }); - free(weightsPtr); + weightsF32.free(); if (!success) { throw Exception( @@ -521,6 +518,10 @@ class FFIAsset extends ThermionAsset { var count = AnimationManager_getMorphTargetNameCount( animationManager, asset, entity); + + if (count < 0) { + throw Exception("Failed to retrieve morph target name count"); + } var outPtr = allocate(255); for (int i = 0; i < count; i++) { AnimationManager_getMorphTargetName( @@ -559,13 +560,17 @@ class FFIAsset extends ThermionAsset { /// /// @override - Future> getAnimationNames() async { + Future> getGltfAnimationNames() async { var animationCount = - AnimationManager_getAnimationCount(animationManager, asset); + AnimationManager_getGltfAnimationCount(animationManager, asset); + if (animationCount == -1) { + throw Exception("This is not a glTF asset"); + } + var names = []; var outPtr = allocate(255); for (int i = 0; i < animationCount; i++) { - AnimationManager_getAnimationName(animationManager, asset, outPtr, i); + AnimationManager_getGltfAnimationName(animationManager, asset, outPtr, i); names.add(outPtr.cast().toDartString()); } free(outPtr); @@ -577,8 +582,8 @@ class FFIAsset extends ThermionAsset { /// /// @override - Future getAnimationDuration(int animationIndex) async { - return AnimationManager_getAnimationDuration( + Future getGltfAnimationDuration(int animationIndex) async { + return AnimationManager_getGltfAnimationDuration( animationManager, asset, animationIndex); } @@ -586,12 +591,12 @@ class FFIAsset extends ThermionAsset { /// /// Future getAnimationDurationByName(String name) async { - var animations = await getAnimationNames(); + var animations = await getGltfAnimationNames(); var index = animations.indexOf(name); if (index == -1) { throw Exception("Failed to find animation $name"); } - return getAnimationDuration(index); + return getGltfAnimationDuration(index); } /// @@ -904,47 +909,52 @@ class FFIAsset extends ThermionAsset { /// /// @override - Future playAnimation(int index, + Future playGltfAnimation(int index, {bool loop = false, bool reverse = false, bool replaceActive = true, double crossfade = 0.0, double startOffset = 0.0}) async { - AnimationManager_playAnimation(animationManager, asset, index, loop, - reverse, replaceActive, crossfade, startOffset); + if (!AnimationManager_playGltfAnimation(animationManager, asset, index, + loop, reverse, replaceActive, crossfade, startOffset)) { + throw Exception("Failed to play glTF animation. Check logs for details"); + } } /// /// /// @override - Future stopAnimation(int animationIndex) async { - AnimationManager_stopAnimation(animationManager, asset, animationIndex); + Future stopGltfAnimation(int animationIndex) async { + if (!AnimationManager_stopGltfAnimation( + animationManager, asset, animationIndex)) { + throw Exception("Failed to stop glTF animation. Check logs for details"); + } } /// /// /// @override - Future stopAnimationByName(String name) async { - var animations = await getAnimationNames(); - await stopAnimation(animations.indexOf(name)); + Future stopGltfAnimationByName(String name) async { + var animations = await getGltfAnimationNames(); + await stopGltfAnimation(animations.indexOf(name)); } /// /// /// @override - Future playAnimationByName(String name, + Future playGltfAnimationByName(String name, {bool loop = false, bool reverse = false, bool replaceActive = true, double crossfade = 0.0, bool wait = false}) async { - var animations = await getAnimationNames(); + var animations = await getGltfAnimationNames(); var index = animations.indexOf(name); - var duration = await getAnimationDuration(index); - await playAnimation(index, + var duration = await getGltfAnimationDuration(index); + await playGltfAnimation(index, loop: loop, reverse: reverse, replaceActive: replaceActive, @@ -967,14 +977,14 @@ class FFIAsset extends ThermionAsset { /// /// @override - Future addAnimationComponent(ThermionEntity entity) async { - AnimationManager_addAnimationComponent(animationManager, entity); + Future addAnimationComponent() async { + AnimationManager_addGltfAnimationComponent(animationManager, this.asset); } /// /// /// - Future removeAnimationComponent(ThermionEntity entity) async { - AnimationManager_removeAnimationComponent(animationManager, entity); + Future removeAnimationComponent() async { + AnimationManager_removeGltfAnimationComponent(animationManager, this.asset); } } diff --git a/thermion_dart/lib/src/filament/src/interface/asset.dart b/thermion_dart/lib/src/filament/src/interface/asset.dart index 5fdde31a..5164968b 100644 --- a/thermion_dart/lib/src/filament/src/interface/asset.dart +++ b/thermion_dart/lib/src/filament/src/interface/asset.dart @@ -66,8 +66,8 @@ abstract class ThermionAsset { /// /// The dimensions of the bounding box for this asset. - /// This is independent of the boundingBoxAsset (which is used to visualize - /// the bounding box in the scene); you do not need to call + /// This is independent of the boundingBoxAsset (which is used to visualize + /// the bounding box in the scene); you do not need to call /// [createBoundingBoxAsset] before this method. Future getBoundingBox(); @@ -153,36 +153,46 @@ abstract class ThermionAsset { /// /// Schedules the glTF animation at [index] in [asset] to start playing on the next frame. /// - Future playAnimation(int index, + Future playGltfAnimation(int index, {bool loop = false, bool reverse = false, bool replaceActive = true, double crossfade = 0.0, - double startOffset = 0.0}); + double startOffset = 0.0}) { + throw UnimplementedError(); + } /// /// Schedules the glTF animation at [index] in [entity] to start playing on the next frame. /// - Future playAnimationByName(String name, + Future playGltfAnimationByName(String name, {bool loop = false, bool reverse = false, bool replaceActive = true, - double crossfade = 0.0}); + double crossfade = 0.0}) { + throw UnimplementedError(); + } /// /// /// - Future setGltfAnimationFrame(int index, int animationFrame); + Future setGltfAnimationFrame(int index, int animationFrame) { + throw UnimplementedError(); + } /// /// /// - Future stopAnimation(int animationIndex); + Future stopGltfAnimation(int animationIndex) { + throw UnimplementedError(); + } /// /// /// - Future stopAnimationByName(String name); + Future stopGltfAnimationByName(String name) { + throw UnimplementedError(); + } /// /// Set the weights for all morph targets in [entity] to [weights]. @@ -191,27 +201,37 @@ abstract class ThermionAsset { /// IMPORTANT - this accepts the actual ThermionEntity with the relevant morph targets (unlike [getMorphTargetNames], which uses the parent entity and the child mesh name). /// Use [getChildEntityByName] if you are setting the weights for a child mesh. /// - Future setMorphTargetWeights(ThermionEntity entity, List weights); + Future setMorphTargetWeights(ThermionEntity entity, List weights) { + throw UnimplementedError(); + } /// /// Gets the names of all morph targets for [entity] (which must be a renderable entity) /// - Future> getMorphTargetNames({ThermionEntity? entity}); + Future> getMorphTargetNames({ThermionEntity? entity}) { + throw UnimplementedError(); + } /// /// Gets the names of all bones for the skin at [skinIndex]. /// - Future> getBoneNames({int skinIndex = 0}); + Future> getBoneNames({int skinIndex = 0}) { + throw UnimplementedError(); + } /// /// Gets the names of all glTF animations embedded in the specified entity. /// - Future> getAnimationNames(); + Future> getGltfAnimationNames() { + throw UnimplementedError(); + } /// /// Returns the length (in seconds) of the animation at the given index. /// - Future getAnimationDuration(int animationIndex); + Future getGltfAnimationDuration(int animationIndex) { + throw UnimplementedError(); + } /// /// Construct animation(s) for every entity under [asset]. If [targetMeshNames] is provided, only entities with matching names will be animated. @@ -221,12 +241,16 @@ abstract class ThermionAsset { /// It is permissible for [animation] to omit any targets that do exist under [meshName]; these simply won't be animated. /// Future setMorphAnimationData(MorphAnimationData animation, - {List? targetMeshNames}); + {List? targetMeshNames}) { + throw UnimplementedError(); + } /// /// Clear all current morph animations for [entity]. /// - Future clearMorphAnimationData(ThermionEntity entity); + Future clearMorphAnimationData(ThermionEntity entity) { + throw UnimplementedError(); + } /// /// Resets all bones in the given entity to their rest pose. @@ -307,10 +331,10 @@ abstract class ThermionAsset { /// An [entity] will only be animatable after an animation component is attached. /// Any calls to [playAnimation]/[setBoneAnimation]/[setMorphAnimation] will have no visual effect until [addAnimationComponent] has been called on the instance. /// - Future addAnimationComponent(ThermionEntity entity); + Future addAnimationComponent(); /// /// Removes an animation component from [entity]. /// - Future removeAnimationComponent(ThermionEntity entity); + Future removeAnimationComponent(); } diff --git a/thermion_dart/native/include/components/AnimationComponentManager.hpp b/thermion_dart/native/include/components/AnimationComponentManager.hpp index ac4e8652..b31ccdd6 100644 --- a/thermion_dart/native/include/components/AnimationComponentManager.hpp +++ b/thermion_dart/native/include/components/AnimationComponentManager.hpp @@ -32,14 +32,7 @@ namespace thermion typedef std::chrono::time_point time_point_t; - enum AnimationType - { - MORPH, - BONE, - GLTF - }; - - struct AnimationStatus + struct Animation { time_point_t start = time_point_t::max(); float startOffset; @@ -52,7 +45,7 @@ namespace thermion /// The status of an animation embedded in a glTF object. /// @param index refers to the index of the animation in the animations property of the underlying object. /// - struct GltfAnimation : AnimationStatus + struct GltfAnimation : Animation { int index = -1; }; @@ -60,24 +53,18 @@ namespace thermion // // The status of a morph target animation created dynamically at runtime (not glTF embedded). // - struct MorphAnimation : AnimationStatus + struct MorphAnimation : Animation { - utils::Entity meshTarget; - int numFrames = -1; + int lengthInFrames; float frameLengthInMs = 0; std::vector frameData; - std::vector morphIndices; - int lengthInFrames; + std::vector morphIndices; }; - // - // The status of a skeletal animation created dynamically at runtime (not glTF embedded). - // - struct BoneAnimation : AnimationStatus - { + struct BoneAnimation : Animation { + int lengthInFrames; size_t boneIndex; size_t skinIndex = 0; - int lengthInFrames; float frameLengthInMs = 0; std::vector frameData; float fadeOutInSecs = 0; @@ -85,276 +72,86 @@ namespace thermion float maxDelta = 1.0f; }; - struct AnimationComponent + /// @brief + /// + /// + struct BoneAnimationComponent { - std::variant target; - std::vector gltfAnimations; - std::vector morphAnimations; - std::vector boneAnimations; + FilamentInstance * target; + std::vector animations; + }; + /// @brief + /// + /// + struct MorphAnimationComponent + { + std::vector animations; + }; + + /// @brief + /// + /// + struct GltfAnimationComponent + { + FilamentInstance * target; // the index of the last active glTF animation, // used to cross-fade int fadeGltfAnimationIndex = -1; float fadeDuration = 0.0f; float fadeOutAnimationStart = 0.0f; + std::vector animations; }; - class AnimationComponentManager : public utils::SingleInstanceComponentManager - { - filament::TransformManager &_transformManager; - filament::RenderableManager &_renderableManager; + class GltfAnimationComponentManager : public utils::SingleInstanceComponentManager { + public: + GltfAnimationComponentManager( + filament::TransformManager &transformManager, + filament::RenderableManager &renderableManager) : + mTransformManager(transformManager), mRenderableManager(renderableManager) {}; + ~GltfAnimationComponentManager() = default; + void addAnimationComponent(FilamentInstance *target); + void removeAnimationComponent(FilamentInstance *target); + void update(); - public: - AnimationComponentManager( - filament::TransformManager &transformManager, - filament::RenderableManager &renderableManager) : _transformManager(transformManager), - _renderableManager(renderableManager){}; - - void addAnimationComponent(std::variant target) - { - - AnimationComponent animationComponent; - animationComponent.target = target; - EntityInstanceBase::Type componentInstance; - if (std::holds_alternative(target)) - { - auto instance = std::get(target); - if(!hasComponent(instance->getRoot())) { - componentInstance = addComponent(instance->getRoot()); - this->elementAt<0>(componentInstance) = animationComponent; - } - } - else - { - auto entity = std::get(target); - if(!hasComponent(entity)) { - componentInstance = addComponent(entity); - this->elementAt<0>(componentInstance) = animationComponent; - } - } - } - - - void removeAnimationComponent(std::variant target) - { - AnimationComponent animationComponent; - animationComponent.target = target; - EntityInstanceBase::Type componentInstance; - if (std::holds_alternative(target)) - { - auto instance = std::get(target); - if(hasComponent(instance->getRoot())) { - removeComponent(instance->getRoot()); - } - } else { - auto entity = std::get(target); - if(hasComponent(entity)) { - removeComponent(entity); - } - } - } - - void update() - { - - for (auto it = begin(); it < end(); it++) - { - const auto &entity = getEntity(it); - - auto componentInstance = getInstance(entity); - auto &animationComponent = elementAt<0>(componentInstance); - - auto &morphAnimations = animationComponent.morphAnimations; - - if (std::holds_alternative(animationComponent.target)) - { - auto target = std::get(animationComponent.target); - auto animator = target->getAnimator(); - auto &gltfAnimations = animationComponent.gltfAnimations; - auto &boneAnimations = animationComponent.boneAnimations; - - if(gltfAnimations.size() > 0) { - for (int i = ((int)gltfAnimations.size()) - 1; i >= 0; i--) - { - auto now = high_resolution_clock::now(); - - auto animationStatus = animationComponent.gltfAnimations[i]; - - auto elapsedInSecs = animationStatus.startOffset + float(std::chrono::duration_cast(now - animationStatus.start).count()) / 1000.0f; - - if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs) - { - animator->applyAnimation(animationStatus.index, animationStatus.durationInSecs - 0.001); - animator->updateBoneMatrices(); - gltfAnimations.erase(gltfAnimations.begin() + i); - animationComponent.fadeGltfAnimationIndex = -1; - continue; - } - animator->applyAnimation(animationStatus.index, elapsedInSecs); - - if (animationComponent.fadeGltfAnimationIndex != -1 && elapsedInSecs < animationComponent.fadeDuration) - { - // cross-fade - auto fadeFromTime = animationComponent.fadeOutAnimationStart + elapsedInSecs; - auto alpha = elapsedInSecs / animationComponent.fadeDuration; - animator->applyCrossFade(animationComponent.fadeGltfAnimationIndex, fadeFromTime, alpha); - } - } - - animator->updateBoneMatrices(); - } - - /// - /// When fading in/out, interpolate between the "current" transform (which has possibly been set by the glTF animation loop above) - /// and the first (for fading in) or last (for fading out) frame. - /// - for (int i = (int)boneAnimations.size() - 1; i >= 0; i--) - { - auto animationStatus = boneAnimations[i]; - - auto now = high_resolution_clock::now(); - - auto elapsedInMillis = float(std::chrono::duration_cast(now - animationStatus.start).count()); - auto elapsedInSecs = elapsedInMillis / 1000.0f; - - // if we're not looping and the amount of time elapsed is greater than the animation duration plus the fade-in/out buffer, - // then the animation is completed and we can delete it - if (elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs)) - { - if(!animationStatus.loop) { - boneAnimations.erase(boneAnimations.begin() + i); - continue; - } - } - - // if we're fading in, treat elapsedFrames is zero (and fading out, treat elapsedFrames as lengthInFrames) - float elapsedInFrames = (elapsedInMillis - (1000 * animationStatus.fadeInInSecs)) / animationStatus.frameLengthInMs; - int currFrame = std::floor(elapsedInFrames); - int nextFrame = currFrame; - - // offset from the end if reverse - if (animationStatus.reverse) - { - currFrame = animationStatus.lengthInFrames - currFrame; - if (currFrame > 0) - { - nextFrame = currFrame - 1; - } - else - { - nextFrame = 0; - } - } - else - { - if (currFrame < animationStatus.lengthInFrames - 1) - { - nextFrame = currFrame + 1; - } - else - { - nextFrame = currFrame; - } - } - currFrame = std::clamp(currFrame, 0, animationStatus.lengthInFrames - 1); - nextFrame = std::clamp(nextFrame, 0, animationStatus.lengthInFrames - 1); - - float frameDelta = elapsedInFrames - currFrame; - - // linearly interpolate this animation between its last/current frames - // this is to avoid jerky animations when the animation framerate is slower than our tick rate - - math::float3 currScale, newScale; - math::quatf currRotation, newRotation; - math::float3 currTranslation, newTranslation; - math::mat4f curr = animationStatus.frameData[currFrame]; - decomposeMatrix(curr, &currTranslation, &currRotation, &currScale); - - if(frameDelta > 0) { - math::mat4f next = animationStatus.frameData[nextFrame]; - decomposeMatrix(next, &newTranslation, &newRotation, &newScale); - newScale = mix(currScale, newScale, frameDelta); - newRotation = slerp(currRotation, newRotation, frameDelta); - newTranslation = mix(currTranslation, newTranslation, frameDelta); - } else { - newScale = currScale; - newRotation = currRotation; - newTranslation = currTranslation; - } - - const Entity joint = target->getJointsAt(animationStatus.skinIndex)[animationStatus.boneIndex]; - - // now calculate the fade out/in delta - // if we're fading in, this will be 0.0 at the start of the fade and 1.0 at the end - auto fadeDelta = elapsedInSecs / animationStatus.fadeInInSecs; - - // // if we're fading out, this will be 1.0 at the start of the fade and 0.0 at the end - if(fadeDelta > 1.0f) { - fadeDelta = 1 - ((elapsedInSecs - animationStatus.durationInSecs - animationStatus.fadeInInSecs) / animationStatus.fadeOutInSecs); - } - - fadeDelta = std::clamp(fadeDelta, 0.0f, animationStatus.maxDelta); - - auto jointTransform = _transformManager.getInstance(joint); - - // linearly interpolate this animation between its current (interpolated) frame and the current transform (i.e. as set by the gltf frame) - // // if we are fading in or out, apply a delta - if (fadeDelta >= 0.0f && fadeDelta <= 1.0f) { - math::float3 fadeScale; - math::quatf fadeRotation; - math::float3 fadeTranslation; - auto currentTransform = _transformManager.getTransform(jointTransform); - decomposeMatrix(currentTransform, &fadeTranslation, &fadeRotation, &fadeScale); - newScale = mix(fadeScale, newScale, fadeDelta); - newRotation = slerp(fadeRotation, newRotation, fadeDelta); - newTranslation = mix(fadeTranslation, newTranslation, fadeDelta); - } - - _transformManager.setTransform(jointTransform, composeMatrix(newTranslation, newRotation, newScale)); - - animator->updateBoneMatrices(); - - if (animationStatus.loop && elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs)) - { - animationStatus.start = now; - } - } - } - for (int i = (int)morphAnimations.size() - 1; i >= 0; i--) - { - - auto now = high_resolution_clock::now(); - - auto animationStatus = morphAnimations[i]; - - auto elapsedInSecs = float(std::chrono::duration_cast(now - animationStatus.start).count()) / 1000.0f; - - if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs) - { - morphAnimations.erase(morphAnimations.begin() + i); - continue; - } - - int frameNumber = static_cast(elapsedInSecs * 1000.0f / animationStatus.frameLengthInMs) % animationStatus.lengthInFrames; - // offset from the end if reverse - if (animationStatus.reverse) - { - frameNumber = animationStatus.lengthInFrames - frameNumber; - } - auto baseOffset = frameNumber * animationStatus.morphIndices.size(); - for (int i = 0; i < animationStatus.morphIndices.size(); i++) - { - auto morphIndex = animationStatus.morphIndices[i]; - // set the weights appropriately - _renderableManager.setMorphWeights( - _renderableManager.getInstance(animationStatus.meshTarget), - animationStatus.frameData.data() + baseOffset + i, - 1, - morphIndex); - } - } - } - } + private: + filament::TransformManager &mTransformManager; + filament::RenderableManager &mRenderableManager; }; + + class BoneAnimationComponentManager : public utils::SingleInstanceComponentManager { + public: + BoneAnimationComponentManager( + filament::TransformManager &transformManager, + filament::RenderableManager &renderableManager) : + mTransformManager(transformManager), mRenderableManager(renderableManager) {}; + ~BoneAnimationComponentManager() {}; + + void addAnimationComponent(FilamentInstance *target); + void removeAnimationComponent(FilamentInstance *target); + void update(); + + private: + filament::TransformManager &mTransformManager; + filament::RenderableManager &mRenderableManager; + }; + + class MorphAnimationComponentManager : public utils::SingleInstanceComponentManager { + public: + MorphAnimationComponentManager( + filament::TransformManager &transformManager, + filament::RenderableManager &renderableManager) : + mTransformManager(transformManager), mRenderableManager(renderableManager) {}; + ~MorphAnimationComponentManager() {}; + + void addAnimationComponent(Entity entity); + void removeAnimationComponent(Entity entity); + void update(); + + private: + filament::TransformManager &mTransformManager; + filament::RenderableManager &mRenderableManager; + }; + } diff --git a/thermion_dart/native/include/scene/AnimationManager.hpp b/thermion_dart/native/include/scene/AnimationManager.hpp index 3936c1ee..30713c9f 100644 --- a/thermion_dart/native/include/scene/AnimationManager.hpp +++ b/thermion_dart/native/include/scene/AnimationManager.hpp @@ -31,7 +31,7 @@ namespace thermion AnimationManager( Engine *engine, Scene *scene); - ~AnimationManager(); + ~AnimationManager() = default; /// @brief /// @@ -158,18 +158,39 @@ namespace thermion float getGltfAnimationDuration(GltfSceneAssetInstance *instance, int animationIndex); /// @brief - /// @param entity + /// @param instance /// @return - bool addAnimationComponent(EntityId entity); + bool addGltfAnimationComponent(GltfSceneAssetInstance *instance); /// @brief - /// @param entity - void removeAnimationComponent(EntityId entity); + /// @param instance + void removeGltfAnimationComponent(GltfSceneAssetInstance *instance); + + /// @brief + /// @param instance + /// @return + bool addBoneAnimationComponent(GltfSceneAssetInstance *instance); + + /// @brief + /// @param instance + void removeBoneAnimationComponent(GltfSceneAssetInstance *instance); + + /// @brief + /// @param asset + /// @return + bool addMorphAnimationComponent(utils::Entity entity); + + /// @brief + /// @param asset + void removeMorphAnimationComponent(utils::Entity entity); + private: Engine *_engine = nullptr; Scene *_scene = nullptr; std::mutex _mutex; - std::unique_ptr _animationComponentManager = std::nullptr_t(); + std::unique_ptr _gltfAnimationComponentManager = std::nullptr_t(); + std::unique_ptr _morphAnimationComponentManager = std::nullptr_t(); + std::unique_ptr _boneAnimationComponentManager = std::nullptr_t(); }; } diff --git a/thermion_dart/native/src/c_api/TAnimationManager.cpp b/thermion_dart/native/src/c_api/TAnimationManager.cpp index 25ffc3fb..bcf61753 100644 --- a/thermion_dart/native/src/c_api/TAnimationManager.cpp +++ b/thermion_dart/native/src/c_api/TAnimationManager.cpp @@ -4,6 +4,8 @@ #include "Log.hpp" +#include + #include "c_api/APIExport.h" #include "scene/AnimationManager.hpp" @@ -26,15 +28,65 @@ extern "C" animationManager->update(frameTimeInNanos); } - EMSCRIPTEN_KEEPALIVE void AnimationManager_addAnimationComponent(TAnimationManager *tAnimationManager, EntityId entityId) + EMSCRIPTEN_KEEPALIVE bool AnimationManager_addGltfAnimationComponent(TAnimationManager *tAnimationManager, TSceneAsset *tSceneAsset) { + auto sceneAsset = reinterpret_cast(tSceneAsset); + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf || !sceneAsset->isInstance()) { + return false; + } + auto animationManager = reinterpret_cast(tAnimationManager); - animationManager->addAnimationComponent(entityId); + + animationManager->addGltfAnimationComponent(reinterpret_cast(sceneAsset)); + return true; } - EMSCRIPTEN_KEEPALIVE void AnimationManager_removeAnimationComponent(TAnimationManager *tAnimationManager, EntityId entityId) + EMSCRIPTEN_KEEPALIVE bool AnimationManager_removeGltfAnimationComponent(TAnimationManager *tAnimationManager, TSceneAsset *tSceneAsset) + { + auto sceneAsset = reinterpret_cast(tSceneAsset); + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf || !sceneAsset->isInstance()) { + return false; + } + + auto animationManager = reinterpret_cast(tAnimationManager); + animationManager->removeGltfAnimationComponent(reinterpret_cast(sceneAsset)); + return true; + } + + EMSCRIPTEN_KEEPALIVE bool AnimationManager_addBoneAnimationComponent(TAnimationManager *tAnimationManager, TSceneAsset *tSceneAsset) + { + auto sceneAsset = reinterpret_cast(tSceneAsset); + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf || !sceneAsset->isInstance()) { + return false; + } + + auto animationManager = reinterpret_cast(tAnimationManager); + + animationManager->addBoneAnimationComponent(reinterpret_cast(sceneAsset)); + return true; + } + + EMSCRIPTEN_KEEPALIVE bool AnimationManager_removeBoneAnimationComponent(TAnimationManager *tAnimationManager, TSceneAsset *tSceneAsset) + { + auto sceneAsset = reinterpret_cast(tSceneAsset); + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf || !sceneAsset->isInstance()) { + return false; + } + + auto animationManager = reinterpret_cast(tAnimationManager); + animationManager->removeBoneAnimationComponent(reinterpret_cast(sceneAsset)); + return true; + } + + EMSCRIPTEN_KEEPALIVE void AnimationManager_addMorphAnimationComponent(TAnimationManager *tAnimationManager, EntityId entity) { auto animationManager = reinterpret_cast(tAnimationManager); - animationManager->removeAnimationComponent(entityId); + animationManager->addMorphAnimationComponent(utils::Entity::import(entity)); + } + + EMSCRIPTEN_KEEPALIVE void AnimationManager_removeMorphAnimationComponent(TAnimationManager *tAnimationManager, EntityId entity) + { + auto animationManager = reinterpret_cast(tAnimationManager); + animationManager->removeMorphAnimationComponent(utils::Entity::import(entity)); } EMSCRIPTEN_KEEPALIVE bool AnimationManager_setMorphAnimation( @@ -66,26 +118,26 @@ extern "C" EMSCRIPTEN_KEEPALIVE bool AnimationManager_clearMorphAnimation(TAnimationManager *tAnimationManager, EntityId entityId) { - auto *animManager = reinterpret_cast(tAnimationManager); + auto *animationManager = reinterpret_cast(tAnimationManager); auto entity = utils::Entity::import(entityId); - animManager->clearMorphAnimationBuffer(entity); + animationManager->clearMorphAnimationBuffer(entity); return true; } EMSCRIPTEN_KEEPALIVE void AnimationManager_resetToRestPose(TAnimationManager *tAnimationManager, TSceneAsset *sceneAsset) { - auto *animManager = reinterpret_cast(tAnimationManager); + auto *animationManager = reinterpret_cast(tAnimationManager); auto asset = reinterpret_cast(sceneAsset); if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) { auto *instance = reinterpret_cast(asset); - animManager->resetToRestPose(instance); + animationManager->resetToRestPose(instance); } } - EMSCRIPTEN_KEEPALIVE void AnimationManager_addBoneAnimation( + EMSCRIPTEN_KEEPALIVE bool AnimationManager_addBoneAnimation( TAnimationManager *tAnimationManager, - TSceneAsset *sceneAsset, + TSceneAsset *tSceneAsset, int skinIndex, int boneIndex, const float *const frameData, @@ -95,13 +147,24 @@ extern "C" float fadeInInSecs, float maxDelta) { - auto *animManager = reinterpret_cast(tAnimationManager); - auto asset = reinterpret_cast(sceneAsset); - if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) - { - animManager->addBoneAnimation(reinterpret_cast(asset), skinIndex, boneIndex, frameData, numFrames, frameLengthInMs, - fadeOutInSecs, fadeInInSecs, maxDelta); + auto sceneAsset = reinterpret_cast(tSceneAsset); + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) { + return false; } + + auto animationManager = reinterpret_cast(tAnimationManager); + GltfSceneAssetInstance *instance; + + if (sceneAsset->isInstance()) + { + instance = reinterpret_cast(sceneAsset); + } else { + instance = reinterpret_cast(sceneAsset->getInstanceAt(0)); + } + animationManager->addBoneAnimationComponent(instance); + animationManager->addBoneAnimation(instance, skinIndex, boneIndex, frameData, numFrames, frameLengthInMs, fadeOutInSecs, fadeInInSecs, maxDelta); + return true; + } EMSCRIPTEN_KEEPALIVE EntityId AnimationManager_getBone( @@ -110,11 +173,11 @@ extern "C" int skinIndex, int boneIndex) { - auto *animManager = reinterpret_cast(tAnimationManager); + auto *animationManager = reinterpret_cast(tAnimationManager); auto asset = reinterpret_cast(sceneAsset); if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) { - auto entities = animManager->getBoneEntities(reinterpret_cast(asset), skinIndex); + auto entities = animationManager->getBoneEntities(reinterpret_cast(asset), skinIndex); if (boneIndex < entities.size()) { return utils::Entity::smuggle(entities[boneIndex]); @@ -131,12 +194,12 @@ extern "C" float *const out, int numBones) { - auto *animManager = reinterpret_cast(tAnimationManager); + auto *animationManager = reinterpret_cast(tAnimationManager); auto asset = reinterpret_cast(sceneAsset); if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) { auto *instance = reinterpret_cast(asset); - const auto transforms = animManager->getBoneRestTranforms(instance, skinIndex); + const auto transforms = animationManager->getBoneRestTranforms(instance, skinIndex); auto numTransforms = transforms.size(); if (numTransforms != numBones) { @@ -164,12 +227,12 @@ extern "C" int boneIndex, float *const out) { - auto *animManager = reinterpret_cast(tAnimationManager); + auto *animationManager = reinterpret_cast(tAnimationManager); auto asset = reinterpret_cast(sceneAsset); if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) { auto *instance = reinterpret_cast(asset); - auto transform = animManager->getInverseBindMatrix(instance, skinIndex, boneIndex); + auto transform = animationManager->getInverseBindMatrix(instance, skinIndex, boneIndex); for (int colNum = 0; colNum < 4; colNum++) { for (int rowNum = 0; rowNum < 4; rowNum++) @@ -180,9 +243,9 @@ extern "C" } } - EMSCRIPTEN_KEEPALIVE void AnimationManager_playAnimation( + EMSCRIPTEN_KEEPALIVE bool AnimationManager_playGltfAnimation( TAnimationManager *tAnimationManager, - TSceneAsset *sceneAsset, + TSceneAsset *tSceneAsset, int index, bool loop, bool reverse, @@ -190,29 +253,48 @@ extern "C" float crossfade, float startOffset) { - auto *animManager = reinterpret_cast(tAnimationManager); - auto asset = reinterpret_cast(sceneAsset); - if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) - { - auto *instance = reinterpret_cast(asset); - - animManager->playGltfAnimation(instance, index, loop, reverse, replaceActive, crossfade, startOffset); + auto sceneAsset = reinterpret_cast(tSceneAsset); + + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) { + return false; } + + auto animationManager = reinterpret_cast(tAnimationManager); + GltfSceneAssetInstance *instance; + + if (sceneAsset->isInstance()) + { + instance = reinterpret_cast(sceneAsset); + } else { + instance = reinterpret_cast(sceneAsset->getInstanceAt(0)); + } + animationManager->addGltfAnimationComponent(instance); + animationManager->playGltfAnimation(instance, index, loop, reverse, replaceActive, crossfade, startOffset); + + return true; } - EMSCRIPTEN_KEEPALIVE void AnimationManager_stopAnimation( + EMSCRIPTEN_KEEPALIVE bool AnimationManager_stopGltfAnimation( TAnimationManager *tAnimationManager, - TSceneAsset *sceneAsset, + TSceneAsset *tSceneAsset, int index) { - auto *animManager = reinterpret_cast(tAnimationManager); - auto asset = reinterpret_cast(sceneAsset); - if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) - { - auto *instance = reinterpret_cast(asset); - - animManager->stopGltfAnimation(instance, index); + auto sceneAsset = reinterpret_cast(tSceneAsset); + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) { + return false; } + + auto animationManager = reinterpret_cast(tAnimationManager); + GltfSceneAssetInstance *instance; + + if (sceneAsset->isInstance()) + { + instance = reinterpret_cast(sceneAsset); + } else { + instance = reinterpret_cast(sceneAsset->getInstanceAt(0)); + } + animationManager->stopGltfAnimation(instance, index); + return true; } EMSCRIPTEN_KEEPALIVE void AnimationManager_setGltfAnimationFrame( @@ -221,41 +303,79 @@ extern "C" int animationIndex, int frame) { - auto *animManager = reinterpret_cast(tAnimationManager); + auto *animationManager = reinterpret_cast(tAnimationManager); auto asset = reinterpret_cast(tSceneAsset); if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) { auto *instance = reinterpret_cast(asset); - animManager->setGltfAnimationFrame(instance, animationIndex, frame); + animationManager->setGltfAnimationFrame(instance, animationIndex, frame); } } - EMSCRIPTEN_KEEPALIVE float AnimationManager_getAnimationDuration( + EMSCRIPTEN_KEEPALIVE float AnimationManager_getGltfAnimationDuration( TAnimationManager *tAnimationManager, - TSceneAsset *sceneAsset, + TSceneAsset *tSceneAsset, int animationIndex) { - auto instance = ((GltfSceneAssetInstance *)sceneAsset); - return ((AnimationManager *)tAnimationManager)->getGltfAnimationDuration(instance, animationIndex); + auto sceneAsset = reinterpret_cast(tSceneAsset); + + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) { + return false; + } + + auto animationManager = reinterpret_cast(tAnimationManager); + GltfSceneAssetInstance *instance; + + if (sceneAsset->isInstance()) + { + instance = reinterpret_cast(sceneAsset); + } else { + instance = reinterpret_cast(sceneAsset->getInstanceAt(0)); + } + + return animationManager->getGltfAnimationDuration(instance, animationIndex); } - EMSCRIPTEN_KEEPALIVE int AnimationManager_getAnimationCount( + EMSCRIPTEN_KEEPALIVE int AnimationManager_getGltfAnimationCount( TAnimationManager *tAnimationManager, - TSceneAsset *sceneAsset) + TSceneAsset *tSceneAsset) { - auto instance = ((GltfSceneAssetInstance *)sceneAsset); - auto names = ((AnimationManager *)tAnimationManager)->getGltfAnimationNames(instance); + + auto sceneAsset = reinterpret_cast(tSceneAsset); + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) { + return -1; + } + auto animationManager = reinterpret_cast(tAnimationManager); + GltfSceneAssetInstance *instance; + if(sceneAsset->isInstance()) { + instance = reinterpret_cast(sceneAsset); + } else { + instance = reinterpret_cast(sceneAsset->getInstanceAt(0)); + } + auto names = animationManager->getGltfAnimationNames(instance); + TRACE("Animation count : %d", names.size()); return (int)names.size(); } - EMSCRIPTEN_KEEPALIVE void AnimationManager_getAnimationName( + EMSCRIPTEN_KEEPALIVE void AnimationManager_getGltfAnimationName( TAnimationManager *tAnimationManager, - TSceneAsset *sceneAsset, + TSceneAsset *tSceneAsset, char *const outPtr, int index) { - auto instance = ((GltfSceneAssetInstance *)sceneAsset); - auto names = ((AnimationManager *)tAnimationManager)->getGltfAnimationNames(instance); + auto sceneAsset = reinterpret_cast(tSceneAsset); + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) { + strcpy(outPtr, "FILAMENT_ERROR_NOT_FOUND"); + return; + } + auto animationManager = reinterpret_cast(tAnimationManager); + GltfSceneAssetInstance *instance; + if(sceneAsset->isInstance()) { + instance = reinterpret_cast(sceneAsset); + } else { + instance = reinterpret_cast(sceneAsset->getInstanceAt(0)); + } + auto names = animationManager->getGltfAnimationNames(instance); std::string name = names[index]; strcpy(outPtr, name.c_str()); } @@ -278,8 +398,7 @@ extern "C" { auto instance = ((GltfSceneAssetInstance *)sceneAsset); auto entities = ((AnimationManager *)tAnimationManager)->getBoneEntities(instance, skinIndex); - // Note: This needs implementation of a method to get bone names from entities - // Current source doesn't show how bone names are retrieved + } EMSCRIPTEN_KEEPALIVE bool AnimationManager_updateBoneMatrices( @@ -293,11 +412,27 @@ extern "C" EMSCRIPTEN_KEEPALIVE int AnimationManager_getMorphTargetNameCount( TAnimationManager *tAnimationManager, - TSceneAsset *sceneAsset, + TSceneAsset *tSceneAsset, EntityId childEntity) { - auto asset = ((GltfSceneAsset *)sceneAsset); - auto names = ((AnimationManager *)tAnimationManager)->getMorphTargetNames(asset, childEntity); + auto sceneAsset = reinterpret_cast(tSceneAsset); + + if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) { + return -1; + } + + auto animationManager = reinterpret_cast(tAnimationManager); + GltfSceneAsset *gltfAsset; + + if (sceneAsset->isInstance()) + { + auto *instance = reinterpret_cast(sceneAsset); + gltfAsset = reinterpret_cast(instance->getInstanceOwner()); + } else { + gltfAsset = reinterpret_cast(sceneAsset); + } + + auto names = animationManager->getMorphTargetNames(gltfAsset, childEntity); return (int)names.size(); } diff --git a/thermion_dart/native/src/components/BoneAnimationComponentManager.cpp b/thermion_dart/native/src/components/BoneAnimationComponentManager.cpp new file mode 100644 index 00000000..166a2a3d --- /dev/null +++ b/thermion_dart/native/src/components/BoneAnimationComponentManager.cpp @@ -0,0 +1,154 @@ +#include +#include + +#include "components/AnimationComponentManager.hpp" + +#include "Log.hpp" + +namespace thermion +{ + + void BoneAnimationComponentManager::addAnimationComponent(FilamentInstance *target) { + if(!hasComponent(target->getRoot())) { + EntityInstanceBase::Type componentInstance = addComponent(target->getRoot()); + this->elementAt<0>(componentInstance) = { target }; + } + } + + void BoneAnimationComponentManager::removeAnimationComponent(FilamentInstance *target) { + if(hasComponent(target->getRoot())) { + removeComponent(target->getRoot()); + } + } + + void BoneAnimationComponentManager::update() { + TRACE("Updating with %d components", getComponentCount()); + for (auto it = begin(); it < end(); it++) + { + const auto &entity = getEntity(it); + + auto componentInstance = getInstance(entity); + auto &animationComponent = elementAt<0>(componentInstance); + + auto &boneAnimations = animationComponent.animations; + + auto target = animationComponent.target; + auto animator = target->getAnimator(); + /// + /// When fading in/out, interpolate between the "current" transform (which has possibly been set by the glTF animation loop above) + /// and the first (for fading in) or last (for fading out) frame. + /// + for (int i = (int)boneAnimations.size() - 1; i >= 0; i--) + { + auto animationStatus = boneAnimations[i]; + + auto now = high_resolution_clock::now(); + + auto elapsedInMillis = float(std::chrono::duration_cast(now - animationStatus.start).count()); + auto elapsedInSecs = elapsedInMillis / 1000.0f; + + // if we're not looping and the amount of time elapsed is greater than the animation duration plus the fade-in/out buffer, + // then the animation is completed and we can delete it + if (elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs)) + { + if(!animationStatus.loop) { + boneAnimations.erase(boneAnimations.begin() + i); + continue; + } + } + + // if we're fading in, treat elapsedFrames is zero (and fading out, treat elapsedFrames as lengthInFrames) + float elapsedInFrames = (elapsedInMillis - (1000 * animationStatus.fadeInInSecs)) / animationStatus.frameLengthInMs; + int currFrame = std::floor(elapsedInFrames); + int nextFrame = currFrame; + + // offset from the end if reverse + if (animationStatus.reverse) + { + currFrame = animationStatus.lengthInFrames - currFrame; + if (currFrame > 0) + { + nextFrame = currFrame - 1; + } + else + { + nextFrame = 0; + } + } + else + { + if (currFrame < animationStatus.lengthInFrames - 1) + { + nextFrame = currFrame + 1; + } + else + { + nextFrame = currFrame; + } + } + currFrame = std::clamp(currFrame, 0, animationStatus.lengthInFrames - 1); + nextFrame = std::clamp(nextFrame, 0, animationStatus.lengthInFrames - 1); + + float frameDelta = elapsedInFrames - currFrame; + + // linearly interpolate this animation between its last/current frames + // this is to avoid jerky animations when the animation framerate is slower than our tick rate + + math::float3 currScale, newScale; + math::quatf currRotation, newRotation; + math::float3 currTranslation, newTranslation; + math::mat4f curr = animationStatus.frameData[currFrame]; + decomposeMatrix(curr, &currTranslation, &currRotation, &currScale); + + if(frameDelta > 0) { + math::mat4f next = animationStatus.frameData[nextFrame]; + decomposeMatrix(next, &newTranslation, &newRotation, &newScale); + newScale = mix(currScale, newScale, frameDelta); + newRotation = slerp(currRotation, newRotation, frameDelta); + newTranslation = mix(currTranslation, newTranslation, frameDelta); + } else { + newScale = currScale; + newRotation = currRotation; + newTranslation = currTranslation; + } + + const Entity joint = target->getJointsAt(animationStatus.skinIndex)[animationStatus.boneIndex]; + + // now calculate the fade out/in delta + // if we're fading in, this will be 0.0 at the start of the fade and 1.0 at the end + auto fadeDelta = elapsedInSecs / animationStatus.fadeInInSecs; + + // // if we're fading out, this will be 1.0 at the start of the fade and 0.0 at the end + if(fadeDelta > 1.0f) { + fadeDelta = 1 - ((elapsedInSecs - animationStatus.durationInSecs - animationStatus.fadeInInSecs) / animationStatus.fadeOutInSecs); + } + + fadeDelta = std::clamp(fadeDelta, 0.0f, animationStatus.maxDelta); + + auto jointTransform = mTransformManager.getInstance(joint); + + // linearly interpolate this animation between its current (interpolated) frame and the current transform (i.e. as set by the gltf frame) + // // if we are fading in or out, apply a delta + if (fadeDelta >= 0.0f && fadeDelta <= 1.0f) { + math::float3 fadeScale; + math::quatf fadeRotation; + math::float3 fadeTranslation; + auto currentTransform = mTransformManager.getTransform(jointTransform); + decomposeMatrix(currentTransform, &fadeTranslation, &fadeRotation, &fadeScale); + newScale = mix(fadeScale, newScale, fadeDelta); + newRotation = slerp(fadeRotation, newRotation, fadeDelta); + newTranslation = mix(fadeTranslation, newTranslation, fadeDelta); + } + + mTransformManager.setTransform(jointTransform, composeMatrix(newTranslation, newRotation, newScale)); + + animator->updateBoneMatrices(); + + if (animationStatus.loop && elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs)) + { + animationStatus.start = now; + } + } + } + } +} diff --git a/thermion_dart/native/src/components/GltfAnimationComponentManager.cpp b/thermion_dart/native/src/components/GltfAnimationComponentManager.cpp new file mode 100644 index 00000000..f8f513ac --- /dev/null +++ b/thermion_dart/native/src/components/GltfAnimationComponentManager.cpp @@ -0,0 +1,308 @@ +#include +#include + +#include "components/AnimationComponentManager.hpp" + +#include "Log.hpp" + +namespace thermion +{ + + void GltfAnimationComponentManager::addAnimationComponent(FilamentInstance *target) { + if(!hasComponent(target->getRoot())) { + EntityInstanceBase::Type componentInstance = addComponent(target->getRoot()); + this->elementAt<0>(componentInstance) = { target }; + } + } + + void GltfAnimationComponentManager::removeAnimationComponent(FilamentInstance *target) { + if(hasComponent(target->getRoot())) { + removeComponent(target->getRoot()); + } + } + + void GltfAnimationComponentManager::update() { + TRACE("Updating with %d components", getComponentCount()); + for (auto it = begin(); it < end(); it++) + { + const auto &entity = getEntity(it); + + auto componentInstance = getInstance(entity); + auto &animationComponent = elementAt<0>(componentInstance); + + auto target = animationComponent.target; + auto animator = target->getAnimator(); + auto &gltfAnimations = animationComponent.animations; + + for (int i = ((int)gltfAnimations.size()) - 1; i >= 0; i--) + { + auto now = high_resolution_clock::now(); + + auto animationStatus = gltfAnimations[i]; + + auto elapsedInSecs = animationStatus.startOffset + float(std::chrono::duration_cast(now - animationStatus.start).count()) / 1000.0f; + + if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs) + { + animator->applyAnimation(animationStatus.index, animationStatus.durationInSecs - 0.001); + animator->updateBoneMatrices(); + gltfAnimations.erase(gltfAnimations.begin() + i); + animationComponent.fadeGltfAnimationIndex = -1; + continue; + } + animator->applyAnimation(animationStatus.index, elapsedInSecs); + + if (animationComponent.fadeGltfAnimationIndex != -1 && elapsedInSecs < animationComponent.fadeDuration) + { + // cross-fade + auto fadeFromTime = animationComponent.fadeOutAnimationStart + elapsedInSecs; + auto alpha = elapsedInSecs / animationComponent.fadeDuration; + animator->applyCrossFade(animationComponent.fadeGltfAnimationIndex, fadeFromTime, alpha); + } + } + + animator->updateBoneMatrices(); + } + } +} + + // void AnimationComponentManager::addGltfAnimationComponent(FilamentInstance *target) { + // if(!hasComponent(target->getRoot())) { + // EntityInstanceBase::Type componentInstance; + // componentInstance = addComponent(instance->getRoot()); + // this->elementAt<0>(componentInstance) = GltfAnimationComponent animationComponent { target }; + // } + // } + // // } + // // else + // // { + // // auto entity = std::get(target); + // // if(!hasComponent(entity)) { + // // componentInstance = addComponent(entity); + // // this->elementAt<0>(componentInstance) = animationComponent; + // // } + // // } + // // } + + + // void AnimationComponentManager::removeAnimationComponent(std::variant target) + // { + // AnimationComponent animationComponent; + // animationComponent.target = target; + // EntityInstanceBase::Type componentInstance; + // if (std::holds_alternative(target)) + // { + // auto instance = std::get(target); + // if(hasComponent(instance->getRoot())) { + // removeComponent(instance->getRoot()); + // } + // } else { + // auto entity = std::get(target); + // if(hasComponent(entity)) { + // removeComponent(entity); + // } + // } + // } + + // void AnimationComponentManager::update() + // { + + // for (auto it = begin(); it < end(); it++) + // { + // const auto &entity = getEntity(it); + + // auto componentInstance = getInstance(entity); + // auto &animationComponent = elementAt<0>(componentInstance); + + // auto &morphAnimations = animationComponent.morphAnimations; + + // if (std::holds_alternative(animationComponent.target)) + // { + // auto target = std::get(animationComponent.target); + // auto animator = target->getAnimator(); + // auto &gltfAnimations = animationComponent.gltfAnimations; + // auto &boneAnimations = animationComponent.boneAnimations; + + // if(gltfAnimations.size() > 0) { + // for (int i = ((int)gltfAnimations.size()) - 1; i >= 0; i--) + // { + // auto now = high_resolution_clock::now(); + + // auto animationStatus = animationComponent.gltfAnimations[i]; + + // auto elapsedInSecs = animationStatus.startOffset + float(std::chrono::duration_cast(now - animationStatus.start).count()) / 1000.0f; + + // if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs) + // { + // animator->applyAnimation(animationStatus.index, animationStatus.durationInSecs - 0.001); + // animator->updateBoneMatrices(); + // gltfAnimations.erase(gltfAnimations.begin() + i); + // animationComponent.fadeGltfAnimationIndex = -1; + // continue; + // } + // animator->applyAnimation(animationStatus.index, elapsedInSecs); + + // if (animationComponent.fadeGltfAnimationIndex != -1 && elapsedInSecs < animationComponent.fadeDuration) + // { + // // cross-fade + // auto fadeFromTime = animationComponent.fadeOutAnimationStart + elapsedInSecs; + // auto alpha = elapsedInSecs / animationComponent.fadeDuration; + // animator->applyCrossFade(animationComponent.fadeGltfAnimationIndex, fadeFromTime, alpha); + // } + // } + + // animator->updateBoneMatrices(); + // } + + // /// + // /// When fading in/out, interpolate between the "current" transform (which has possibly been set by the glTF animation loop above) + // /// and the first (for fading in) or last (for fading out) frame. + // /// + // for (int i = (int)boneAnimations.size() - 1; i >= 0; i--) + // { + // auto animationStatus = boneAnimations[i]; + + // auto now = high_resolution_clock::now(); + + // auto elapsedInMillis = float(std::chrono::duration_cast(now - animationStatus.start).count()); + // auto elapsedInSecs = elapsedInMillis / 1000.0f; + + // // if we're not looping and the amount of time elapsed is greater than the animation duration plus the fade-in/out buffer, + // // then the animation is completed and we can delete it + // if (elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs)) + // { + // if(!animationStatus.loop) { + // boneAnimations.erase(boneAnimations.begin() + i); + // continue; + // } + // } + + // // if we're fading in, treat elapsedFrames is zero (and fading out, treat elapsedFrames as lengthInFrames) + // float elapsedInFrames = (elapsedInMillis - (1000 * animationStatus.fadeInInSecs)) / animationStatus.frameLengthInMs; + // int currFrame = std::floor(elapsedInFrames); + // int nextFrame = currFrame; + + // // offset from the end if reverse + // if (animationStatus.reverse) + // { + // currFrame = animationStatus.lengthInFrames - currFrame; + // if (currFrame > 0) + // { + // nextFrame = currFrame - 1; + // } + // else + // { + // nextFrame = 0; + // } + // } + // else + // { + // if (currFrame < animationStatus.lengthInFrames - 1) + // { + // nextFrame = currFrame + 1; + // } + // else + // { + // nextFrame = currFrame; + // } + // } + // currFrame = std::clamp(currFrame, 0, animationStatus.lengthInFrames - 1); + // nextFrame = std::clamp(nextFrame, 0, animationStatus.lengthInFrames - 1); + + // float frameDelta = elapsedInFrames - currFrame; + + // // linearly interpolate this animation between its last/current frames + // // this is to avoid jerky animations when the animation framerate is slower than our tick rate + + // math::float3 currScale, newScale; + // math::quatf currRotation, newRotation; + // math::float3 currTranslation, newTranslation; + // math::mat4f curr = animationStatus.frameData[currFrame]; + // decomposeMatrix(curr, &currTranslation, &currRotation, &currScale); + + // if(frameDelta > 0) { + // math::mat4f next = animationStatus.frameData[nextFrame]; + // decomposeMatrix(next, &newTranslation, &newRotation, &newScale); + // newScale = mix(currScale, newScale, frameDelta); + // newRotation = slerp(currRotation, newRotation, frameDelta); + // newTranslation = mix(currTranslation, newTranslation, frameDelta); + // } else { + // newScale = currScale; + // newRotation = currRotation; + // newTranslation = currTranslation; + // } + + // const Entity joint = target->getJointsAt(animationStatus.skinIndex)[animationStatus.boneIndex]; + + // // now calculate the fade out/in delta + // // if we're fading in, this will be 0.0 at the start of the fade and 1.0 at the end + // auto fadeDelta = elapsedInSecs / animationStatus.fadeInInSecs; + + // // // if we're fading out, this will be 1.0 at the start of the fade and 0.0 at the end + // if(fadeDelta > 1.0f) { + // fadeDelta = 1 - ((elapsedInSecs - animationStatus.durationInSecs - animationStatus.fadeInInSecs) / animationStatus.fadeOutInSecs); + // } + + // fadeDelta = std::clamp(fadeDelta, 0.0f, animationStatus.maxDelta); + + // auto jointTransform = _transformManager.getInstance(joint); + + // // linearly interpolate this animation between its current (interpolated) frame and the current transform (i.e. as set by the gltf frame) + // // // if we are fading in or out, apply a delta + // if (fadeDelta >= 0.0f && fadeDelta <= 1.0f) { + // math::float3 fadeScale; + // math::quatf fadeRotation; + // math::float3 fadeTranslation; + // auto currentTransform = _transformManager.getTransform(jointTransform); + // decomposeMatrix(currentTransform, &fadeTranslation, &fadeRotation, &fadeScale); + // newScale = mix(fadeScale, newScale, fadeDelta); + // newRotation = slerp(fadeRotation, newRotation, fadeDelta); + // newTranslation = mix(fadeTranslation, newTranslation, fadeDelta); + // } + + // _transformManager.setTransform(jointTransform, composeMatrix(newTranslation, newRotation, newScale)); + + // animator->updateBoneMatrices(); + + // if (animationStatus.loop && elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs)) + // { + // animationStatus.start = now; + // } + // } + // } + // for (int i = (int)morphAnimations.size() - 1; i >= 0; i--) + // { + + // auto now = high_resolution_clock::now(); + + // auto animationStatus = morphAnimations[i]; + + // auto elapsedInSecs = float(std::chrono::duration_cast(now - animationStatus.start).count()) / 1000.0f; + + // if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs) + // { + // morphAnimations.erase(morphAnimations.begin() + i); + // continue; + // } + + // int frameNumber = static_cast(elapsedInSecs * 1000.0f / animationStatus.frameLengthInMs) % animationStatus.lengthInFrames; + // // offset from the end if reverse + // if (animationStatus.reverse) + // { + // frameNumber = animationStatus.lengthInFrames - frameNumber; + // } + // auto baseOffset = frameNumber * animationStatus.morphIndices.size(); + // for (int i = 0; i < animationStatus.morphIndices.size(); i++) + // { + // auto morphIndex = animationStatus.morphIndices[i]; + // // set the weights appropriately + // _renderableManager.setMorphWeights( + // _renderableManager.getInstance(animationStatus.meshTarget), + // animationStatus.frameData.data() + baseOffset + i, + // 1, + // morphIndex); + // } + // } + // } + // }; +// } diff --git a/thermion_dart/native/src/components/MorphAnimationComponentManager.cpp b/thermion_dart/native/src/components/MorphAnimationComponentManager.cpp new file mode 100644 index 00000000..0fa29e40 --- /dev/null +++ b/thermion_dart/native/src/components/MorphAnimationComponentManager.cpp @@ -0,0 +1,76 @@ +#include +#include + +#include "components/AnimationComponentManager.hpp" + +#include "Log.hpp" + +namespace thermion +{ + + void MorphAnimationComponentManager::addAnimationComponent(utils::Entity target) { + if(!hasComponent(target)) { + EntityInstanceBase::Type componentInstance = addComponent(target); + this->elementAt<0>(componentInstance) = MorphAnimationComponent { }; + } + } + + void MorphAnimationComponentManager::removeAnimationComponent(utils::Entity target) { + if(hasComponent(target)) { + removeComponent(target); + } + } + + void MorphAnimationComponentManager::update() { + TRACE("Updating %d morph animation components", getComponentCount()); + for (auto it = begin(); it < end(); it++) + { + const auto &entity = getEntity(it); + + auto componentInstance = getInstance(entity); + + auto &animationComponent = elementAt<0>(componentInstance); + auto &animations = animationComponent.animations; + + TRACE("Component has %d animations", animations.size()); + + for (int i = (int)animations.size() - 1; i >= 0; i--) + { + + auto now = high_resolution_clock::now(); + + auto &animation = animationComponent.animations[i]; + + auto elapsedInSecs = float(std::chrono::duration_cast(now - animation.start).count()) / 1000.0f; + + if (!animation.loop && elapsedInSecs >= animation.durationInSecs) + { + animations.erase(animations.begin() + i); + TRACE("Animation %d completed", i); + continue; + } + + int frameNumber = static_cast(elapsedInSecs * 1000.0f / animation.frameLengthInMs) % animation.lengthInFrames; + // offset from the end if reverse + if (animation.reverse) + { + frameNumber = animation.lengthInFrames - frameNumber; + } + + auto baseOffset = frameNumber * animation.morphIndices.size(); + for (int i = 0; i < animation.morphIndices.size(); i++) + { + auto morphIndex = animation.morphIndices[i]; + auto renderableInstance = mRenderableManager.getInstance(entity); + + mRenderableManager.setMorphWeights( + renderableInstance, + animation.frameData.data() + baseOffset + i, + 1, + morphIndex); + + } + } + } + } +} diff --git a/thermion_dart/native/src/scene/AnimationManager.cpp b/thermion_dart/native/src/scene/AnimationManager.cpp index 1c8e67d8..4325a5e8 100644 --- a/thermion_dart/native/src/scene/AnimationManager.cpp +++ b/thermion_dart/native/src/scene/AnimationManager.cpp @@ -11,6 +11,8 @@ #include "Log.hpp" +#include "components/AnimationComponentManager.hpp" +#include "components/AnimationComponentManager.hpp" #include "scene/AnimationManager.hpp" #include "scene/SceneAsset.hpp" #include "scene/GltfSceneAssetInstance.hpp" @@ -25,12 +27,9 @@ namespace thermion { auto &transformManager = _engine->getTransformManager(); auto &renderableManager = _engine->getRenderableManager(); - _animationComponentManager = std::make_unique(transformManager, renderableManager); - } - - AnimationManager::~AnimationManager() - { - _animationComponentManager = std::nullptr_t(); + _gltfAnimationComponentManager = std::make_unique(transformManager, renderableManager); + _morphAnimationComponentManager = std::make_unique(transformManager, renderableManager); + _boneAnimationComponentManager = std::make_unique(transformManager, renderableManager); } bool AnimationManager::setMorphAnimationBuffer( @@ -41,16 +40,20 @@ namespace thermion int numFrames, float frameLengthInMs) { + std::lock_guard lock(_mutex); - if (!_animationComponentManager->hasComponent(entity)) + if (!_morphAnimationComponentManager->hasComponent(entity)) { - _animationComponentManager->addAnimationComponent(entity); + _morphAnimationComponentManager->addAnimationComponent(entity); } + auto animationComponentInstance = _morphAnimationComponentManager->getInstance(entity); + auto &animationComponent = _morphAnimationComponentManager->elementAt<0>(animationComponentInstance); + auto &morphAnimations = animationComponent.animations; + MorphAnimation morphAnimation; - morphAnimation.meshTarget = entity; morphAnimation.frameData.clear(); morphAnimation.frameData.insert( morphAnimation.frameData.begin(), @@ -65,15 +68,12 @@ namespace thermion morphAnimation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f; morphAnimation.start = high_resolution_clock::now(); - morphAnimation.lengthInFrames = static_cast( - morphAnimation.durationInSecs * 1000.0f / - frameLengthInMs); - - auto animationComponentInstance = _animationComponentManager->getInstance(entity); - auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance); - auto &morphAnimations = animationComponent.morphAnimations; + morphAnimation.lengthInFrames = numFrames; morphAnimations.emplace_back(morphAnimation); + + auto& foo = morphAnimations[morphAnimations.size() - 1]; + return true; } @@ -82,9 +82,9 @@ namespace thermion { std::lock_guard lock(_mutex); - auto animationComponentInstance = _animationComponentManager->getInstance(entity); - auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance); - auto &morphAnimations = animationComponent.morphAnimations; + auto animationComponentInstance = _morphAnimationComponentManager->getInstance(entity); + auto &animationComponent = _morphAnimationComponentManager->elementAt<0>(animationComponentInstance); + auto &morphAnimations = animationComponent.animations; morphAnimations.clear(); } @@ -309,17 +309,17 @@ namespace thermion animation.fadeInInSecs = fadeInInSecs; animation.maxDelta = maxDelta; animation.skinIndex = skinIndex; - if (!_animationComponentManager->hasComponent(instance->getInstance()->getRoot())) + if (!_boneAnimationComponentManager->hasComponent(instance->getInstance()->getRoot())) { Log("ERROR: specified entity is not animatable (has no animation component attached)."); return false; } - auto animationComponentInstance = _animationComponentManager->getInstance(instance->getInstance()->getRoot()); + auto animationComponentInstance = _boneAnimationComponentManager->getInstance(instance->getInstance()->getRoot()); - auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance); - auto &boneAnimations = animationComponent.boneAnimations; + auto &animationComponent = _boneAnimationComponentManager->elementAt<0>(animationComponentInstance); + // auto &boneAnimations = animationComponent.boneAnimations; - boneAnimations.emplace_back(animation); + // boneAnimations.emplace_back(animation); return true; } @@ -334,27 +334,30 @@ namespace thermion return; } - if (!_animationComponentManager->hasComponent(instance->getEntity())) + if (!_gltfAnimationComponentManager->hasComponent(instance->getEntity())) { + _gltfAnimationComponentManager->addComponent(instance->getEntity()); Log("ERROR: specified entity is not animatable (has no animation component attached)."); return; } - auto animationComponentInstance = _animationComponentManager->getInstance(instance->getEntity()); + auto animationComponentInstance = _gltfAnimationComponentManager->getInstance(instance->getEntity()); - auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance); + auto &animationComponent = _gltfAnimationComponentManager->elementAt<0>(animationComponentInstance); + + animationComponent.target = instance->getInstance(); if (replaceActive) { - if (animationComponent.gltfAnimations.size() > 0) + if (animationComponent.animations.size() > 0) { - auto &last = animationComponent.gltfAnimations.back(); + auto &last = animationComponent.animations.back(); animationComponent.fadeGltfAnimationIndex = last.index; animationComponent.fadeDuration = crossfade; auto now = high_resolution_clock::now(); auto elapsedInSecs = float(std::chrono::duration_cast(now - last.start).count()) / 1000.0f; animationComponent.fadeOutAnimationStart = elapsedInSecs; - animationComponent.gltfAnimations.clear(); + animationComponent.animations.clear(); } else { @@ -384,9 +387,9 @@ namespace thermion bool found = false; // don't play the animation if it's already running - for (int i = 0; i < animationComponent.gltfAnimations.size(); i++) + for (int i = 0; i < animationComponent.animations.size(); i++) { - if (animationComponent.gltfAnimations[i].index == index) + if (animationComponent.animations[i].index == index) { found = true; break; @@ -394,22 +397,22 @@ namespace thermion } if (!found) { - animationComponent.gltfAnimations.push_back(animation); + animationComponent.animations.push_back(animation); } } void AnimationManager::stopGltfAnimation(GltfSceneAssetInstance *instance, int index) { - auto animationComponentInstance = _animationComponentManager->getInstance(instance->getEntity()); - auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance); + auto animationComponentInstance = _gltfAnimationComponentManager->getInstance(instance->getEntity()); + auto &animationComponent = _gltfAnimationComponentManager->elementAt<0>(animationComponentInstance); - auto erased = std::remove_if(animationComponent.gltfAnimations.begin(), - animationComponent.gltfAnimations.end(), + auto erased = std::remove_if(animationComponent.animations.begin(), + animationComponent.animations.end(), [=](GltfAnimation &anim) { return anim.index == index; }); - animationComponent.gltfAnimations.erase(erased, - animationComponent.gltfAnimations.end()); + animationComponent.animations.erase(erased, + animationComponent.animations.end()); return; } @@ -417,6 +420,7 @@ namespace thermion { RenderableManager &rm = _engine->getRenderableManager(); auto renderableInstance = rm.getInstance(entity); + rm.setMorphWeights( renderableInstance, weights, @@ -477,7 +481,9 @@ namespace thermion void AnimationManager::update(uint64_t frameTimeInNanos) { std::lock_guard lock(_mutex); - _animationComponentManager->update(); + _gltfAnimationComponentManager->update(); + _morphAnimationComponentManager->update(); + _boneAnimationComponentManager->update(); } math::mat4f AnimationManager::getInverseBindMatrix(GltfSceneAssetInstance *instance, int skinIndex, int boneIndex) @@ -507,15 +513,37 @@ namespace thermion return true; } - bool AnimationManager::addAnimationComponent(EntityId entity) + bool AnimationManager::addGltfAnimationComponent(GltfSceneAssetInstance *instance) { - _animationComponentManager->addAnimationComponent(utils::Entity::import(entity)); + _gltfAnimationComponentManager->addAnimationComponent(instance->getInstance()); return true; } - void AnimationManager::removeAnimationComponent(EntityId entity) + void AnimationManager::removeGltfAnimationComponent(GltfSceneAssetInstance *instance) { - _animationComponentManager->removeComponent(utils::Entity::import(entity)); + _gltfAnimationComponentManager->removeAnimationComponent(instance->getInstance()); + } + + bool AnimationManager::addBoneAnimationComponent(GltfSceneAssetInstance *instance) + { + _boneAnimationComponentManager->addAnimationComponent(instance->getInstance()); + return true; + } + + void AnimationManager::removeBoneAnimationComponent(GltfSceneAssetInstance *instance) + { + _boneAnimationComponentManager->removeAnimationComponent(instance->getInstance()); + } + + bool AnimationManager::addMorphAnimationComponent(utils::Entity entity) + { + _morphAnimationComponentManager->addAnimationComponent(entity); + return true; + } + + void AnimationManager::removeMorphAnimationComponent(utils::Entity entity) + { + _morphAnimationComponentManager->removeAnimationComponent(entity); } } \ No newline at end of file diff --git a/thermion_dart/test/animation_tests.dart b/thermion_dart/test/animation_tests.dart index d6d436fc..24924660 100644 --- a/thermion_dart/test/animation_tests.dart +++ b/thermion_dart/test/animation_tests.dart @@ -3,45 +3,87 @@ import 'dart:typed_data'; import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:test/test.dart'; import 'package:thermion_dart/src/bindings/bindings.dart'; -import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart'; -import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart'; import 'package:thermion_dart/thermion_dart.dart'; import 'helpers.dart'; void main() async { final testHelper = TestHelper("animation"); await testHelper.setup(); - group('morph animation tests', () { - test('retrieve morph target names', () async { - await testHelper.withViewer((viewer) async { - final cube = await viewer.loadGltf( - "${testHelper.testDir}/assets/cube_with_morph_targets.glb"); - final childEntities = await cube.getChildEntities(); - var morphTargets = - await cube.getMorphTargetNames(entity: childEntities.first); - expect(morphTargets.length, 1); - expect(morphTargets.first, "Key 1"); - }); - }); - test('set morph target weights', () async { - await testHelper.withViewer((viewer) async { - final cube = await viewer.loadGltf( - "${testHelper.testDir}/assets/cube_with_morph_targets.glb"); - print(await cube.getChildEntityNames()); - await viewer.addToScene(cube); - await testHelper.capture(viewer.view, "cube_no_morph"); + test('get morph target names', () async { + await testHelper.withViewer((viewer) async { + var cube = await viewer.loadGltf("${testHelper.testDir}/assets/cube.glb"); + var morphTargets = await cube.getMorphTargetNames(); + expect(morphTargets.length, 0); - final childEntities = await cube.getChildEntities(); + var childEntities = await cube.getChildEntities(); + var childEntity = childEntities.first; - var morphData = MorphAnimationData( - Float32List.fromList([1.0]), ["Key 1"], - frameLengthInMs: 1000.0 / 60.0); - await cube.addAnimationComponent(childEntities.first); - await cube.setMorphAnimationData(morphData); - await viewer.render(); - await testHelper.capture(viewer.view, "cube_with_morph"); - }, bg: kRed); + morphTargets = await cube.getMorphTargetNames(entity: childEntity); + expect(morphTargets.length, 0); + + cube = await viewer + .loadGltf("${testHelper.testDir}/assets/cube_with_morph_targets.glb"); + morphTargets = await cube.getMorphTargetNames(); + expect(morphTargets.length, 0); + + childEntities = await cube.getChildEntities(); + + morphTargets = + await cube.getMorphTargetNames(entity: childEntities.first); + expect(morphTargets.length, 1); + expect(morphTargets.first, "Key 1"); }); }); + + test('set morph target weights', () async { + await testHelper.withViewer((viewer) async { + final cube = await viewer.loadGltf( + "${testHelper.testDir}/assets/cube_with_morph_targets.glb"); + + await viewer.addToScene(cube); + + await testHelper.capture(viewer.view, "cube_no_morph"); + + await cube.setMorphTargetWeights((await cube.getChildEntities()).first, [1.0]); + await testHelper.capture(viewer.view, "cube_with_morph"); + + }, bg:kRed, cameraPosition: Vector3(3, 2, 6)); + }); + + test('set morph target animation', () async { + await testHelper.withViewer((viewer) async { + final cube = await viewer.loadGltf( + "${testHelper.testDir}/assets/cube_with_morph_targets.glb"); + + await viewer.addToScene(cube); + + var morphData = MorphAnimationData(Float32List.fromList([1.0]), ["Key 1"], + frameLengthInMs: 1000.0 / 60.0); + + await cube.setMorphAnimationData(morphData); + await viewer.render(); + await testHelper.capture(viewer.view, "cube_with_morph_animation"); + + }, bg:kRed, cameraPosition: Vector3(3, 2, -6)); + }); + + test('get gltf animation names', () async { + await testHelper.withViewer((viewer) async { + final cube = await viewer + .loadGltf("${testHelper.testDir}/assets/cube_with_morph_targets.glb"); + + await viewer.addToScene(cube); + await testHelper.capture(viewer.view, "gltf_animation_stopped"); + + final animationNames = await cube.getGltfAnimationNames(); + + expect(animationNames.first, "CubeAction"); + + await cube.playGltfAnimation(0); + await Future.delayed(Duration(seconds: 1)); + await viewer.render(); + await testHelper.capture(viewer.view, "gltf_animation_started"); + }, bg: kRed); + }); }