diff --git a/example/assets/cube.blend b/example/assets/cube.blend index 8b7d6249..8d167e51 100644 --- a/example/assets/cube.blend +++ b/example/assets/cube.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fb09944d65a155fc5b6522f296dd875df02fc2944733a35eb09bec23bbabdcd -size 813192 +oid sha256:4a3a6b8b48db5d5e71ecd774629cfea00bcd75d4cf1462cb58448e1825c89815 +size 813688 diff --git a/example/lib/main.dart b/example/lib/main.dart index d5f7ebf0..f61c2a13 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -21,6 +21,7 @@ class _MyAppState extends State { final weights = List.filled(255, 0.0); List _targets = []; + List _animationNames = []; bool _loop = false; @override @@ -69,11 +70,13 @@ class _MyAppState extends State { }), ElevatedButton( child: const Text('load cube GLB'), - onPressed:_cube != null ? null : () async { + onPressed:() async { _cube = await _filamentController .loadGlb('assets/cube.glb'); - print(await _filamentController - .getAnimationNames(_cube!)); + + _animationNames = + await _filamentController + .getAnimationNames(_cube!); }), ElevatedButton( child: const Text('load cube GLTF'), @@ -111,8 +114,12 @@ class _MyAppState extends State { .applyWeights(_cube!, List.filled(8, 0)); }), ElevatedButton( - onPressed: () => - _filamentController.playAnimation(_cube!, 0, loop: _loop), + onPressed: () { + _animationNames.asMap().forEach((idx, element) { + _filamentController.playAnimation(_cube!, idx, loop: _loop); + }); + + }, child: const Text('play animation')), ElevatedButton( onPressed: () { diff --git a/ios/src/SceneAsset.cpp b/ios/src/SceneAsset.cpp index 01ea017f..853d915e 100644 --- a/ios/src/SceneAsset.cpp +++ b/ios/src/SceneAsset.cpp @@ -1,15 +1,15 @@ -#include -#include "SceneResources.hpp" #include "SceneAsset.hpp" #include "Log.hpp" +#include "SceneResources.hpp" +#include #include #include #include #include #include -#include +#include using namespace std::chrono; @@ -20,15 +20,21 @@ using namespace filament; using namespace filament::gltfio; using namespace utils; -SceneAsset::SceneAsset(FilamentAsset* asset, Engine* engine, NameComponentManager* ncm) +SceneAsset::SceneAsset(FilamentAsset *asset, Engine *engine, + NameComponentManager *ncm) : _asset(asset), _engine(engine), _ncm(ncm) { - _animator = _asset->getAnimator(); + _animator = _asset->getAnimator(); + for (int i = 0; i < _animator->getAnimationCount(); i++) { + _embeddedAnimationStatus.push_back( + EmbeddedAnimationStatus(i, _animator->getAnimationDuration(i), false)); } + Log("Created animation buffers for %d", _embeddedAnimationStatus.size()); +} SceneAsset::~SceneAsset() { _asset = nullptr; } void SceneAsset::applyWeights(float *weights, int count) { - RenderableManager& rm = _engine->getRenderableManager(); + RenderableManager &rm = _engine->getRenderableManager(); for (size_t i = 0, c = _asset->getEntityCount(); i != c; ++i) { auto inst = rm.getInstance(_asset->getEntities()[i]); rm.setMorphWeights(inst, weights, count); @@ -46,7 +52,7 @@ void SceneAsset::animateWeights(float *data, int numWeights, int numFrames, void SceneAsset::updateAnimations() { updateMorphAnimation(); - updateEmbeddedAnimation(); + updateEmbeddedAnimations(); } void SceneAsset::updateMorphAnimation() { @@ -86,42 +92,60 @@ void SceneAsset::updateMorphAnimation() { } void SceneAsset::playAnimation(int index, bool loop) { + Log("Playing animation at index %d", index); if (index > _animator->getAnimationCount() - 1) { Log("Asset does not contain an animation at index %d", index); + } else if (_embeddedAnimationStatus[index].started) { + Log("Animation already playing, call stop first."); } else { - _boneAnimationStatus = make_unique( - index, _animator->getAnimationDuration(index), loop); + Log("Starting animation at index %d", index); + _embeddedAnimationStatus[index].play = true; + _embeddedAnimationStatus[index].loop = loop; } } -void SceneAsset::stopAnimation() { +void SceneAsset::stopAnimation(int index) { // TODO - does this need to be threadsafe? - _boneAnimationStatus = nullptr; + _embeddedAnimationStatus[index].play = false; + _embeddedAnimationStatus[index].started = false; } -void SceneAsset::updateEmbeddedAnimation() { - if (!_boneAnimationStatus) { - return; - } - - duration dur = duration_cast>( - high_resolution_clock::now() - _boneAnimationStatus->lastTime); - float startTime = 0; - if (!_boneAnimationStatus->hasStarted) { - _boneAnimationStatus->hasStarted = true; - _boneAnimationStatus->lastTime = high_resolution_clock::now(); - } else if (dur.count() >= _boneAnimationStatus->duration) { - if (_boneAnimationStatus->loop) { - _boneAnimationStatus->lastTime = high_resolution_clock::now(); - } else { - _boneAnimationStatus = nullptr; - return; +void SceneAsset::updateEmbeddedAnimations() { + auto now = high_resolution_clock::now(); + for (auto &status : _embeddedAnimationStatus) { + if (!status.play) { + // Log("Skipping animation %d", status.animationIndex); + continue; + } + duration dur = + duration_cast>(now - status.startedAt); + float animationTimeOffset = 0; + bool finished = false; + if (!status.started) { + status.started = true; + status.startedAt = now; + } else if (dur.count() >= status.duration) { + if (status.loop) { + status.startedAt = now; + } else { + finished = true; + } + } else { + animationTimeOffset = dur.count(); + } + + // Log("time offset %f", animationTimeOffset); + + if (!finished) { + _animator->applyAnimation(status.animationIndex, animationTimeOffset); + } else { + Log("Animation %d finished", status.animationIndex); + + status.play = false; + status.started = false; } - } else { - startTime = dur.count(); } - _animator->applyAnimation(_boneAnimationStatus->animationIndex, startTime); _animator->updateBoneMatrices(); } @@ -165,29 +189,28 @@ unique_ptr> SceneAsset::getTargetNames(const char *meshName) { return names; } -void SceneAsset::transformToUnitCube() - { - if (!_asset) - { - Log("No asset, cannot transform."); - return; - } - auto &tm = _engine->getTransformManager(); - auto aabb = _asset->getBoundingBox(); - auto center = aabb.center(); - auto halfExtent = aabb.extent(); - auto maxExtent = max(halfExtent) * 2; - auto scaleFactor = 2.0f / maxExtent; - auto transform = math::mat4f::scaling(scaleFactor) * math::mat4f::translation(-center); - tm.setTransform(tm.getInstance(_asset->getRoot()), transform); +void SceneAsset::transformToUnitCube() { + if (!_asset) { + Log("No asset, cannot transform."); + return; } + auto &tm = _engine->getTransformManager(); + auto aabb = _asset->getBoundingBox(); + auto center = aabb.center(); + auto halfExtent = aabb.extent(); + auto maxExtent = max(halfExtent) * 2; + auto scaleFactor = 2.0f / maxExtent; + auto transform = + math::mat4f::scaling(scaleFactor) * math::mat4f::translation(-center); + tm.setTransform(tm.getInstance(_asset->getRoot()), transform); +} - const utils::Entity* SceneAsset::getCameraEntities() { - return _asset->getCameraEntities(); - } +const utils::Entity *SceneAsset::getCameraEntities() { + return _asset->getCameraEntities(); +} - size_t SceneAsset::getCameraEntityCount() { - return _asset->getCameraEntityCount(); - } +size_t SceneAsset::getCameraEntityCount() { + return _asset->getCameraEntityCount(); +} } // namespace polyvox \ No newline at end of file diff --git a/ios/src/SceneAsset.hpp b/ios/src/SceneAsset.hpp index a9e65fc4..a6db2391 100644 --- a/ios/src/SceneAsset.hpp +++ b/ios/src/SceneAsset.hpp @@ -35,12 +35,12 @@ namespace polyvox { void updateAnimations(); /// - /// Immediately stop the currently playing animation. NOOP if no animation is playing. + /// Immediately stop the animation at the specified index. Noop if no animation is playing. /// - void stopAnimation(); + void stopAnimation(int index); /// - /// Play an embedded animation (i.e. an animation node embedded in the GLTF asset). If [loop] is true, the animation will repeat indefinitely. + /// Play the embedded animation (i.e. animation node embedded in the GLTF asset) under the specified index. If [loop] is true, the animation will repeat indefinitely. /// void playAnimation(int index, bool loop); @@ -69,15 +69,15 @@ namespace polyvox { FilamentAsset* _asset = nullptr; Engine* _engine = nullptr; NameComponentManager* _ncm; + void updateMorphAnimation(); - void updateEmbeddedAnimation(); + void updateEmbeddedAnimations(); Animator* _animator; // animation flags; - bool isAnimating; unique_ptr _morphAnimationBuffer; - unique_ptr _boneAnimationStatus; + vector _embeddedAnimationStatus; }; } \ No newline at end of file diff --git a/ios/src/SceneResources.hpp b/ios/src/SceneResources.hpp index f34bda9d..b2a2ec48 100644 --- a/ios/src/SceneResources.hpp +++ b/ios/src/SceneResources.hpp @@ -51,15 +51,44 @@ namespace polyvox { typedef std::chrono::time_point time_point_t; // - // Holds the current state of a bone animation embeded in a GLTF asset. + // Holds the current state of a bone animation embeded in a GLTF asset. + // Currently, an instance will be constructed for every animation in an asset whenever a SceneAsset is created (and thus will persist for the lifetime of the SceneAsset). // - struct BoneAnimationStatus { - BoneAnimationStatus(int animationIndex, float duration, bool loop) : animationIndex(animationIndex), duration(duration), loop(loop) {} - bool hasStarted = false; - int animationIndex; - float duration = 0; - time_point_t lastTime; + struct EmbeddedAnimationStatus { + EmbeddedAnimationStatus(int animationIndex, float duration, bool loop) : animationIndex(animationIndex), duration(duration), loop(loop) {} + + // + // A flag that is checked each frame to determine whether or not the animation should play. + // + bool play; + + // + // If [play] is true, this flag will be checked when the animation is complete. If true, the animation will restart. + // bool loop; + + // + // If [play] is true, this flag will be set to true when the animation is started. + // + bool started = false; + + // + // The index of the animation in the GLTF asset. + // + int animationIndex; + + // + // The duration of the animation (calculated from the GLTF animator). + // + float duration = 0; + + // + // The time point at which this animation was last started. + // This is used to calculate the "animation time offset" that is passed to the Animator. + // + time_point_t startedAt; + + }; //