allow multiple simultaneous gltf animatiosn

This commit is contained in:
Nick Fisher
2023-08-09 13:59:10 +08:00
parent fa8c6b1ca0
commit e62bf64c04
7 changed files with 130 additions and 141 deletions

View File

@@ -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":

View File

@@ -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);

View File

@@ -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);

View File

@@ -31,12 +31,17 @@ namespace polyvox {
typedef std::chrono::time_point<std::chrono::high_resolution_clock> 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<AnimationStatus> mAnimations;
// the index of the last active glTF animation,
// used to cross-fade
int fadeGltfAnimationIndex = -1;

View File

@@ -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<AnimationStatus> completed;
for(auto& anim : asset.mAnimations) {
if(anim.mActive) {
auto elapsed = float(std::chrono::duration_cast<std::chrono::milliseconds>(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<int>(
anim.mDuration * 1000.0f /
asset.mMorphAnimationBuffer.mFrameLengthInMs
);
int frameNumber = static_cast<int>(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<int>(
anim.mDuration * 1000.0f /
asset.mBoneAnimationBuffer.mFrameLengthInMs
);
int frameNumber = static_cast<int>(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<std::chrono::milliseconds>(
now - morphAnimation.mStart
).count()) / 1000.0f;
int lengthInFrames = static_cast<int>(
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<int>(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<std::chrono::milliseconds>(
now - boneAnimation.mStart
).count()) / 1000.0f;
int lengthInFrames = static_cast<int>(
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<int>(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<std::chrono::milliseconds>(now - asset.mAnimations[2].mStart).count()) / 1000.0f;
asset.fadeOutAnimationStart = elapsed;
if(replaceActive) {
vector<int> 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<std::chrono::milliseconds>(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) {

View File

@@ -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(

View File

@@ -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
]);
}