diff --git a/ios/Classes/SwiftPolyvoxFilamentPlugin.swift b/ios/Classes/SwiftPolyvoxFilamentPlugin.swift index f3bb7d3f..183ef9a9 100644 --- a/ios/Classes/SwiftPolyvoxFilamentPlugin.swift +++ b/ios/Classes/SwiftPolyvoxFilamentPlugin.swift @@ -372,13 +372,13 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture // apply_weights(assetManager, asset, entityName, UnsafeMutablePointer(&weights), Int32(count)) result(true) case "setMorphTargetWeights": - guard let args = call.arguments as? [Any], args.count == 7, + guard let args = call.arguments as? [Any], args.count == 5, let assetManager = args[0] as? Int64, let asset = args[1] as? EntityId, let entityName = args[2] as? String, let morphData = args[3] as? FlutterStandardTypedData, let numMorphWeights = args[4] as? Int else { - result(FlutterError(code: "INVALID_ARGUMENTS", message: "Expected correct arguments for set_morph_animation", details: nil)) + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Expected correct arguments for setMorphTargetWeights", details: nil)) return } let success = morphData.data.withUnsafeBytes { buffer in @@ -434,18 +434,18 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture result(true) case "playAnimation": - guard let args = call.arguments as? [Any], args.count == 6, + guard let args = call.arguments as? [Any], args.count == 7, let assetManager = args[0] as? Int64, let asset = args[1] as? EntityId, let index = args[2] as? Int, let loop = args[3] as? Bool, let reverse = args[4] as? Bool, - let crossfade = args[5] as? Float else { + let replaceActive = args[5] as? Bool, + let crossfade = args[6] as? Double else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "Expected correct arguments for play_animation", details: nil)) return } - - play_animation(unsafeBitCast(assetManager, to:UnsafeMutableRawPointer.self), asset, Int32(index), loop, reverse, crossfade) + play_animation(unsafeBitCast(assetManager, to:UnsafeMutableRawPointer.self), asset, Int32(index), loop, reverse, replaceActive, Float(crossfade)) result(true) case "getAnimationDuration": guard let args = call.arguments as? [Any], args.count == 3, @@ -546,10 +546,12 @@ public class SwiftPolyvoxFilamentPlugin: NSObject, FlutterPlugin, FlutterTexture } let count = get_morph_target_name_count(unsafeBitCast(assetManager, to:UnsafeMutableRawPointer.self), asset, meshName) var names:[String] = [] - for i in 0...count - 1 { - var buffer = [CChar](repeating: 0, count: 256) // Assuming max name length of 256 for simplicity - get_morph_target_name(unsafeBitCast(assetManager, to:UnsafeMutableRawPointer.self), asset, meshName, &buffer, Int32(i)) - names.append(String(cString:buffer)) + if count > 0 { + for i in 0...count - 1 { + var buffer = [CChar](repeating: 0, count: 256) // Assuming max name length of 256 for simplicity + get_morph_target_name(unsafeBitCast(assetManager, to:UnsafeMutableRawPointer.self), asset, meshName, &buffer, Int32(i)) + names.append(String(cString:buffer)) + } } result(names) case "getMorphTargetNameCount": diff --git a/ios/include/AssetManager.hpp b/ios/include/AssetManager.hpp index d433b382..ba1e7d2b 100644 --- a/ios/include/AssetManager.hpp +++ b/ios/include/AssetManager.hpp @@ -59,7 +59,7 @@ namespace polyvox { const char** const meshName, int numMeshTargets, float frameLengthInMs); - void playAnimation(EntityId e, int index, bool loop, bool reverse, float crossfade = 0.3f); + void playAnimation(EntityId e, int index, bool loop, bool reverse, bool replaceActive, float crossfade = 0.3f); void stopAnimation(EntityId e, int index); void setMorphTargetWeights(const char* const entityName, float *weights, int count); void loadTexture(EntityId entity, const char* resourcePath, int renderableIndex); diff --git a/ios/include/PolyvoxFilamentApi.h b/ios/include/PolyvoxFilamentApi.h index d651524f..36749e9c 100644 --- a/ios/include/PolyvoxFilamentApi.h +++ b/ios/include/PolyvoxFilamentApi.h @@ -68,7 +68,7 @@ void set_bone_animation( int numMeshTargets, float frameLengthInMs); -void play_animation(void* assetManager, EntityId asset, int index, bool loop, bool reverse, float crossfade); +void play_animation(void* assetManager, EntityId asset, int index, bool loop, bool reverse, bool replaceActive, float crossfade); void set_animation_frame(void* assetManager, EntityId asset, int animationIndex, int animationFrame); void stop_animation(void* assetManager, EntityId asset, int index); int get_animation_count(void* assetManager, EntityId asset); diff --git a/ios/include/SceneAsset.hpp b/ios/include/SceneAsset.hpp index f5c1568b..c5e1025f 100644 --- a/ios/include/SceneAsset.hpp +++ b/ios/include/SceneAsset.hpp @@ -31,12 +31,17 @@ namespace polyvox { typedef std::chrono::time_point time_point_t; + enum AnimationType { + MORPH, BONE, GLTF + }; + struct AnimationStatus { time_point_t mStart = time_point_t::max(); bool mLoop = false; bool mReverse = false; float mDuration = 0; - bool mActive = false; + AnimationType type; + int gltfIndex = -1; }; // @@ -73,11 +78,9 @@ namespace polyvox { FilamentAsset* mAsset = nullptr; Animator* mAnimator = nullptr; - // fixed-sized array containing pointers to the active morph, bone and GLTF animations. - AnimationStatus mAnimations[3]; - // the index of the active glTF animation in the Filament Asset animations array - // if no glTF animation is active, this is -1 - int gltfAnimationIndex = -1; + // vector containing AnimationStatus structs for the morph, bone and/or glTF animations. + vector mAnimations; + // the index of the last active glTF animation, // used to cross-fade int fadeGltfAnimationIndex = -1; diff --git a/ios/src/AssetManager.cpp b/ios/src/AssetManager.cpp index ee87b11e..e13ddd16 100644 --- a/ios/src/AssetManager.cpp +++ b/ios/src/AssetManager.cpp @@ -243,104 +243,67 @@ void AssetManager::updateAnimations() { for (auto& asset : _assets) { - if(!asset.mAnimating) { - continue; - } - asset.mAnimating = false; - - // GLTF animations - AnimationStatus& anim = asset.mAnimations[2]; + vector completed; + for(auto& anim : asset.mAnimations) { - if(anim.mActive) { auto elapsed = float(std::chrono::duration_cast(now - anim.mStart).count()) / 1000.0f; if(anim.mLoop || elapsed < anim.mDuration) { - asset.mAnimator->applyAnimation(asset.gltfAnimationIndex, elapsed); - asset.mAnimating = true; - if(asset.fadeGltfAnimationIndex != -1 && elapsed < asset.fadeDuration) { - // cross-fade - auto fadeFromTime = asset.fadeOutAnimationStart + elapsed; - auto alpha = elapsed / asset.fadeDuration; - asset.mAnimator->applyCrossFade(asset.fadeGltfAnimationIndex, fadeFromTime, alpha); + + switch(anim.type) { + case AnimationType::GLTF: { + asset.mAnimator->applyAnimation(anim.gltfIndex, elapsed); + if(asset.fadeGltfAnimationIndex != -1 && elapsed < asset.fadeDuration) { + // cross-fade + auto fadeFromTime = asset.fadeOutAnimationStart + elapsed; + auto alpha = elapsed / asset.fadeDuration; + asset.mAnimator->applyCrossFade(asset.fadeGltfAnimationIndex, fadeFromTime, alpha); + } + break; + } + case AnimationType::MORPH: { + int lengthInFrames = static_cast( + anim.mDuration * 1000.0f / + asset.mMorphAnimationBuffer.mFrameLengthInMs + ); + int frameNumber = static_cast(elapsed * 1000.0f / asset.mMorphAnimationBuffer.mFrameLengthInMs) % lengthInFrames; + // offset from the end if reverse + if(anim.mReverse) { + frameNumber = lengthInFrames - frameNumber; + } + // set the weights appropriately + rm.setMorphWeights( + rm.getInstance(asset.mMorphAnimationBuffer.mMeshTarget), + asset.mMorphAnimationBuffer.mFrameData.data() + (frameNumber * asset.mMorphAnimationBuffer.mNumMorphWeights), + asset.mMorphAnimationBuffer.mNumMorphWeights + ); + break; + } + case AnimationType::BONE: { + int lengthInFrames = static_cast( + anim.mDuration * 1000.0f / + asset.mBoneAnimationBuffer.mFrameLengthInMs + ); + int frameNumber = static_cast(elapsed * 1000.0f / asset.mBoneAnimationBuffer.mFrameLengthInMs) % lengthInFrames; + + // offset from the end if reverse + if(anim.mReverse) { + frameNumber = lengthInFrames - frameNumber; + } + setBoneTransform( + asset, + frameNumber + ); + break; + } } + // animation has completed } else { - // stop - anim.mStart = time_point_t::max(); - anim.mActive = false; - asset.gltfAnimationIndex = -1; + completed.push_back(anim); asset.fadeGltfAnimationIndex = -1; } asset.mAnimator->updateBoneMatrices(); } - - // dynamic morph animation - AnimationStatus& morphAnimation = asset.mAnimations[0]; - - if(morphAnimation.mActive) { - - auto elapsed = float( - std::chrono::duration_cast( - now - morphAnimation.mStart - ).count()) / 1000.0f; - int lengthInFrames = static_cast( - morphAnimation.mDuration * 1000.0f / - asset.mMorphAnimationBuffer.mFrameLengthInMs - ); - - // if more time has elapsed than the animation duration && we aren't looping, then mark the animation as complete - if(elapsed >= morphAnimation.mDuration && !morphAnimation.mLoop) { - morphAnimation.mStart = time_point_t::max(); - morphAnimation.mActive = false; - } else { - asset.mAnimating = true; - int frameNumber = static_cast(elapsed * 1000.0f / asset.mMorphAnimationBuffer.mFrameLengthInMs) % lengthInFrames; - // offset from the end if reverse - if(morphAnimation.mReverse) { - frameNumber = lengthInFrames - frameNumber; - } - - // set the weights appropriately - rm.setMorphWeights( - rm.getInstance(asset.mMorphAnimationBuffer.mMeshTarget), - asset.mMorphAnimationBuffer.mFrameData.data() + (frameNumber * asset.mMorphAnimationBuffer.mNumMorphWeights), - asset.mMorphAnimationBuffer.mNumMorphWeights - ); - } - } - - // dynamic bone animations - AnimationStatus boneAnimation = asset.mAnimations[1]; - if(boneAnimation.mActive) { - auto elapsed = float( - std::chrono::duration_cast( - now - boneAnimation.mStart - ).count()) / 1000.0f; - int lengthInFrames = static_cast( - boneAnimation.mDuration * 1000.0f / - asset.mBoneAnimationBuffer.mFrameLengthInMs - ); - - // if more time has elapsed than the animation duration and we are not looping, mark the animation as complete - if(elapsed >= boneAnimation.mDuration && !boneAnimation.mLoop) { - boneAnimation.mStart = time_point_t::max(); - boneAnimation.mActive = false; - } else { - - asset.mAnimating = true; - int frameNumber = static_cast(elapsed * 1000.0f / asset.mBoneAnimationBuffer.mFrameLengthInMs) % lengthInFrames; - - // offset from the end if reverse - if(boneAnimation.mReverse) { - frameNumber = lengthInFrames - frameNumber; - } - - setBoneTransform( - asset, - frameNumber - ); - } - asset.mAnimator->updateBoneMatrices(); - } } } @@ -403,7 +366,7 @@ void AssetManager::remove(EntityId entityId) { } EntityManager& em = EntityManager::get(); em.destroy(Entity::import(entityId)); - sceneAsset.mAsset = nullptr; // still need to remove this somewhere... + sceneAsset.mAsset = nullptr; // still need to remove sceneAsset somewhere... } void AssetManager::setMorphTargetWeights(EntityId entityId, const char* const entityName, const float* const weights, const int count) { @@ -427,8 +390,6 @@ void AssetManager::setMorphTargetWeights(EntityId entityId, const char* const en weights, count ); - - } utils::Entity AssetManager::findEntityByName(SceneAsset asset, const char* entityName) { @@ -444,7 +405,6 @@ utils::Entity AssetManager::findEntityByName(SceneAsset asset, const char* entit return entity; } - bool AssetManager::setMorphAnimationBuffer( EntityId entityId, const char* entityName, @@ -476,11 +436,11 @@ bool AssetManager::setMorphAnimationBuffer( asset.mMorphAnimationBuffer.mFrameLengthInMs = frameLengthInMs; asset.mMorphAnimationBuffer.mNumMorphWeights = numMorphWeights; - AnimationStatus& animation = asset.mAnimations[0]; + AnimationStatus animation; animation.mDuration = (frameLengthInMs * numFrames) / 1000.0f; animation.mStart = high_resolution_clock::now(); - animation.mActive = true; - asset.mAnimating = true; + animation.type = AnimationType::MORPH; + asset.mAnimations.push_back(animation); return true; } @@ -529,7 +489,6 @@ bool AssetManager::setBoneAnimationBuffer( animationBuffer.mBaseTransforms.resize(numBones); for(int i = 0; i < numBones; i++) { - Log("Bone %s", boneNames[i]); for(int j = 0; j < numJoints; j++) { const char* jointName = _ncm->getName(_ncm->getInstance(joints[j])); if(strcmp(jointName, boneNames[i]) == 0) { @@ -571,18 +530,18 @@ bool AssetManager::setBoneAnimationBuffer( animationBuffer.mMeshTargets.push_back(entity); } - auto& animation = asset.mAnimations[1]; + AnimationStatus animation; animation.mStart = std::chrono::high_resolution_clock::now(); - animation.mActive = true; animation.mReverse = false; animation.mDuration = (frameLengthInMs * numFrames) / 1000.0f; - asset.mAnimating = true; + animation.type = AnimationType::BONE; + asset.mAnimations.push_back(animation); return true; } -void AssetManager::playAnimation(EntityId e, int index, bool loop, bool reverse, float crossfade) { +void AssetManager::playAnimation(EntityId e, int index, bool loop, bool reverse, bool replaceActive, float crossfade) { const auto& pos = _entityIdLookup.find(e); if(pos == _entityIdLookup.end()) { Log("ERROR: asset not found for entity."); @@ -590,24 +549,44 @@ void AssetManager::playAnimation(EntityId e, int index, bool loop, bool reverse, } auto& asset = _assets[pos->second]; - if(asset.gltfAnimationIndex != -1) { - asset.fadeGltfAnimationIndex = asset.gltfAnimationIndex; - asset.fadeDuration = crossfade; - auto now = high_resolution_clock::now(); - auto elapsed = float(std::chrono::duration_cast(now - asset.mAnimations[2].mStart).count()) / 1000.0f; - asset.fadeOutAnimationStart = elapsed; + if(replaceActive) { + vector active; + for(int i = 0; i < asset.mAnimations.size(); i++) { + if(asset.mAnimations[i].type == AnimationType::GLTF) { + active.push_back(i); + } + } + if(active.size() > 0) { + auto& last = asset.mAnimations[active.back()]; + asset.fadeGltfAnimationIndex = last.gltfIndex; + asset.fadeDuration = crossfade; + auto now = high_resolution_clock::now(); + auto elapsed = float(std::chrono::duration_cast(now - last.mStart).count()) / 1000.0f; + asset.fadeOutAnimationStart = elapsed; + for(int j = active.size() - 1; j >= 0; j--) { + asset.mAnimations.erase(asset.mAnimations.begin() + active[j]); + } + } else { + asset.fadeGltfAnimationIndex = -1; + asset.fadeDuration = 0.0f; + } + } else if(crossfade > 0) { + Log("ERROR: crossfade only supported when replaceActive is true."); + return; } else { - asset.fadeGltfAnimationIndex = -1; - asset.fadeDuration = 0.0f; + asset.fadeGltfAnimationIndex = -1; + asset.fadeDuration = 0.0f; } - asset.gltfAnimationIndex = index; - asset.mAnimations[2].mStart = std::chrono::high_resolution_clock::now(); - asset.mAnimations[2].mLoop = loop; - asset.mAnimations[2].mReverse = reverse; - asset.mAnimations[2].mActive = true; - asset.mAnimations[2].mDuration = asset.mAnimator->getAnimationDuration(index); - asset.mAnimating = true; + AnimationStatus animation; + animation.gltfIndex = index; + animation.mStart = std::chrono::high_resolution_clock::now(); + animation.mLoop = loop; + animation.mReverse = reverse; + animation.type = AnimationType::GLTF; + animation.mDuration = asset.mAnimator->getAnimationDuration(index); + + asset.mAnimations.push_back(animation); } void AssetManager::stopAnimation(EntityId entityId, int index) { @@ -617,12 +596,12 @@ void AssetManager::stopAnimation(EntityId entityId, int index) { return; } auto& asset = _assets[pos->second]; - if(asset.gltfAnimationIndex != index) { - // ignore? - } else { - asset.mAnimations[2].mStart = time_point_t::max(); - asset.mAnimations[2].mActive = false; - } + + asset.mAnimations.erase(std::remove_if(asset.mAnimations.begin(), + asset.mAnimations.end(), + [=](AnimationStatus& anim) { return anim.gltfIndex == index; }), + asset.mAnimations.end()); + } void AssetManager::loadTexture(EntityId entity, const char* resourcePath, int renderableIndex) { diff --git a/ios/src/PolyvoxFilamentApi.cpp b/ios/src/PolyvoxFilamentApi.cpp index a106a027..ed28f381 100644 --- a/ios/src/PolyvoxFilamentApi.cpp +++ b/ios/src/PolyvoxFilamentApi.cpp @@ -268,8 +268,9 @@ extern "C" { int index, bool loop, bool reverse, + bool replaceActive, float crossfade) { - ((AssetManager*)assetManager)->playAnimation(asset, index, loop, reverse, crossfade); + ((AssetManager*)assetManager)->playAnimation(asset, index, loop, reverse, replaceActive, crossfade); } FLUTTER_PLUGIN_EXPORT void set_animation_frame( diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index 622798d5..f8daf9a8 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -332,13 +332,17 @@ class FilamentController { } void playAnimation(FilamentEntity asset, int index, - {bool loop = false, bool reverse = false, double crossfade = 0.0}) async { + {bool loop = false, + bool reverse = false, + bool replaceActive = true, + double crossfade = 0.0}) async { await _channel.invokeMethod("playAnimation", [ _assetManager, asset, index, loop ? 1 : 0, reverse ? 1 : 0, + replaceActive, crossfade ]); }