@@ -36,6 +36,13 @@ Java_com_google_android_filament_gltfio_Animator_nUpdateBoneMatrices(JNIEnv*, jc
|
||||
animator->updateBoneMatrices();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_gltfio_Animator_nApplyCrossFade(JNIEnv*, jclass, jlong nativeAnimator,
|
||||
jint previousAnimIndex, jfloat previousAnimTime, jfloat alpha) {
|
||||
Animator* animator = (Animator*) nativeAnimator;
|
||||
animator->applyCrossFade(previousAnimIndex, previousAnimTime, alpha);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_gltfio_Animator_nResetBoneMatrices(JNIEnv*, jclass, jlong nativeAnimator) {
|
||||
Animator* animator = (Animator*) nativeAnimator;
|
||||
|
||||
@@ -66,6 +66,27 @@ public class Animator {
|
||||
nUpdateBoneMatrices(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a blended transform to the union of nodes affected by two animations.
|
||||
* Used for cross-fading from a previous skinning-based animation or rigid body animation.
|
||||
*
|
||||
* First, this stashes the current transform hierarchy into a transient memory buffer.
|
||||
*
|
||||
* Next, this applies previousAnimIndex / previousAnimTime to the actual asset by internally
|
||||
* calling applyAnimation().
|
||||
*
|
||||
* Finally, the stashed local transforms are lerped (via the scale / translation / rotation
|
||||
* components) with their live counterparts, and the results are pushed to the asset.
|
||||
*
|
||||
* To achieve a cross fade effect with skinned models, clients will typically call animator
|
||||
* methods in this order: (1) applyAnimation (2) applyCrossFade (3) updateBoneMatrices. The
|
||||
* animation that clients pass to applyAnimation is the "current" animation corresponding to
|
||||
* alpha=1, while the "previous" animation passed to applyCrossFade corresponds to alpha=0.
|
||||
*/
|
||||
public void applyCrossFade(int previousAnimIndex, float previousAnimTime, float alpha) {
|
||||
nApplyCrossFade(getNativeObject(), previousAnimIndex, previousAnimTime, alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass the identity matrix into all bone nodes, useful for returning to the T pose.
|
||||
*
|
||||
@@ -118,6 +139,7 @@ public class Animator {
|
||||
|
||||
private static native void nApplyAnimation(long nativeAnimator, int index, float time);
|
||||
private static native void nUpdateBoneMatrices(long nativeAnimator);
|
||||
private static native void nApplyCrossFade(long nativeAnimator, int animIndex, float animTime, float alpha);
|
||||
private static native void nResetBoneMatrices(long nativeAnimator);
|
||||
private static native int nGetAnimationCount(long nativeAnimator);
|
||||
private static native float nGetAnimationDuration(long nativeAnimator, int index);
|
||||
|
||||
@@ -56,6 +56,25 @@ public:
|
||||
*/
|
||||
void updateBoneMatrices();
|
||||
|
||||
/**
|
||||
* Applies a blended transform to the union of nodes affected by two animations.
|
||||
* Used for cross-fading from a previous skinning-based animation or rigid body animation.
|
||||
*
|
||||
* First, this stashes the current transform hierarchy into a transient memory buffer.
|
||||
*
|
||||
* Next, this applies previousAnimIndex / previousAnimTime to the actual asset by internally
|
||||
* calling applyAnimation().
|
||||
*
|
||||
* Finally, the stashed local transforms are lerped (via the scale / translation / rotation
|
||||
* components) with their live counterparts, and the results are pushed to the asset.
|
||||
*
|
||||
* To achieve a cross fade effect with skinned models, clients will typically call animator
|
||||
* methods in this order: (1) applyAnimation (2) applyCrossFade (3) updateBoneMatrices. The
|
||||
* animation that clients pass to applyAnimation is the "current" animation corresponding to
|
||||
* alpha=1, while the "previous" animation passed to applyCrossFade corresponds to alpha=0.
|
||||
*/
|
||||
void applyCrossFade(size_t previousAnimIndex, float previousAnimTime, float alpha);
|
||||
|
||||
/**
|
||||
* Pass the identity matrix into all bone nodes, useful for returning to the T pose.
|
||||
*
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace gltfio {
|
||||
|
||||
using TimeValues = map<float, size_t>;
|
||||
using SourceValues = vector<float>;
|
||||
using BoneVector = vector<filament::math::mat4f>;
|
||||
using BoneVector = vector<mat4f>;
|
||||
|
||||
struct Sampler {
|
||||
TimeValues times;
|
||||
@@ -75,8 +75,11 @@ struct AnimatorImpl {
|
||||
RenderableManager* renderableManager;
|
||||
TransformManager* transformManager;
|
||||
vector<float> weights;
|
||||
FixedCapacityVector<mat4f> crossFade;
|
||||
void addChannels(const NodeMap& nodeMap, const cgltf_animation& srcAnim, Animation& dst);
|
||||
void applyAnimation(const Channel& channel, float t, size_t prevIndex, size_t nextIndex);
|
||||
void stashCrossFade();
|
||||
void applyCrossFade(float alpha);
|
||||
};
|
||||
|
||||
static void createSampler(const cgltf_animation_sampler& src, Sampler& dst) {
|
||||
@@ -221,6 +224,12 @@ Animator::Animator(FFilamentAsset* asset, FFilamentInstance* instance) {
|
||||
}
|
||||
}
|
||||
|
||||
void Animator::applyCrossFade(size_t previousAnimIndex, float previousAnimTime, float alpha) {
|
||||
mImpl->stashCrossFade();
|
||||
applyAnimation(previousAnimIndex, previousAnimTime);
|
||||
mImpl->applyCrossFade(alpha);
|
||||
}
|
||||
|
||||
void Animator::addInstance(FFilamentInstance* instance) {
|
||||
const cgltf_data* srcAsset = mImpl->asset->mSourceAsset->hierarchy;
|
||||
const cgltf_animation* srcAnims = srcAsset->animations;
|
||||
@@ -367,6 +376,58 @@ const char* Animator::getAnimationName(size_t animationIndex) const {
|
||||
return mImpl->animations[animationIndex].name.c_str();
|
||||
}
|
||||
|
||||
void AnimatorImpl::stashCrossFade() {
|
||||
using Instance = TransformManager::Instance;
|
||||
auto& tm = *this->transformManager;
|
||||
auto& stash = this->crossFade;
|
||||
|
||||
// Count the total number of transformable nodes to preallocate the stash memory.
|
||||
// We considered caching this count, but the cache would need to be invalidated when entities
|
||||
// are added into the hierarchy.
|
||||
auto recursiveCount = [&tm](Instance node, size_t count, auto& fn) -> size_t {
|
||||
++count;
|
||||
for (auto iter = tm.getChildrenBegin(node); iter != tm.getChildrenEnd(node); ++iter) {
|
||||
count = fn(*iter, count, fn);
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
auto recursiveStash = [&tm, &stash](Instance node, size_t index, auto& fn) -> size_t {
|
||||
stash[index++] = tm.getTransform(node);
|
||||
for (auto iter = tm.getChildrenBegin(node); iter != tm.getChildrenEnd(node); ++iter) {
|
||||
index = fn(*iter, index, fn);
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
const Instance root = tm.getInstance(asset->mRoot);
|
||||
const size_t count = recursiveCount(root, 0, recursiveCount);
|
||||
crossFade.reserve(count);
|
||||
crossFade.resize(count);
|
||||
recursiveStash(root, 0, recursiveStash);
|
||||
}
|
||||
|
||||
void AnimatorImpl::applyCrossFade(float alpha) {
|
||||
using Instance = TransformManager::Instance;
|
||||
auto& tm = *this->transformManager;
|
||||
auto& stash = this->crossFade;
|
||||
auto recursiveFn = [&tm, &stash, alpha](Instance node, size_t index, auto& fn) -> size_t {
|
||||
float3 scale0, scale1;
|
||||
quatf rotation0, rotation1;
|
||||
float3 translation0, translation1;
|
||||
decomposeMatrix(stash[index++], &translation1, &rotation1, &scale1);
|
||||
decomposeMatrix(tm.getTransform(node), &translation0, &rotation0, &scale0);
|
||||
const float3 scale = mix(scale0, scale1, alpha);
|
||||
const quatf rotation = slerp(rotation0, rotation1, alpha);
|
||||
const float3 translation = mix(translation0, translation1, alpha);
|
||||
tm.setTransform(node, composeMatrix(translation, rotation, scale));
|
||||
for (auto iter = tm.getChildrenBegin(node); iter != tm.getChildrenEnd(node); ++iter) {
|
||||
index = fn(*iter, index, fn);
|
||||
}
|
||||
return index;
|
||||
};
|
||||
recursiveFn(tm.getInstance(asset->mRoot), 0, recursiveFn);
|
||||
}
|
||||
|
||||
void AnimatorImpl::addChannels(const NodeMap& nodeMap, const cgltf_animation& srcAnim,
|
||||
Animation& dst) {
|
||||
|
||||
@@ -255,7 +255,6 @@ private:
|
||||
// Properties that can be changed from the UI.
|
||||
int mCurrentAnimation = 1; // It is a 1-based index and 0 means not playing animation
|
||||
int mCurrentVariant = 0;
|
||||
bool mResetAnimation = true;
|
||||
bool mEnableWireframe = false;
|
||||
int mVsmMsaaSamplesLog2 = 1;
|
||||
Settings mSettings;
|
||||
@@ -269,6 +268,13 @@ private:
|
||||
// 0 is the default "free camera". Additional cameras come from the gltf file (1-based index).
|
||||
int mCurrentCamera = 0;
|
||||
|
||||
// Cross fade animation parameters.
|
||||
float mCrossFadeDuration = 0.5f; // number of seconds to transition between animations
|
||||
int mPreviousAnimation = 0; // one-based index of the previous animation
|
||||
double mCurrentStartTime = 0.0f; // start time of most recent cross-fade (seconds)
|
||||
double mPreviousStartTime = 0.0f; // start time of previous cross-fade (seconds)
|
||||
bool mResetAnimation = true; // set when building ImGui widgets, honored in applyAnimation
|
||||
|
||||
// Color grading UI state.
|
||||
float mToneMapPlot[1024];
|
||||
float mRangePlot[1024 * 3];
|
||||
|
||||
@@ -500,17 +500,20 @@ void ViewerGui::sceneSelectionUI() {
|
||||
|
||||
void ViewerGui::applyAnimation(double currentTime) {
|
||||
assert_invariant(!isRemoteMode());
|
||||
static double startTime = 0;
|
||||
const size_t numAnimations = mAnimator->getAnimationCount();
|
||||
if (mResetAnimation) {
|
||||
startTime = currentTime;
|
||||
for (size_t i = 0; i < numAnimations; i++) {
|
||||
mAnimator->applyAnimation(i, 0);
|
||||
}
|
||||
mPreviousStartTime = mCurrentStartTime;
|
||||
mCurrentStartTime = currentTime;
|
||||
mResetAnimation = false;
|
||||
}
|
||||
const double elapsedSeconds = currentTime - mCurrentStartTime;
|
||||
if (numAnimations > 0 && mCurrentAnimation > 0) {
|
||||
mAnimator->applyAnimation(mCurrentAnimation - 1, currentTime - startTime);
|
||||
mAnimator->applyAnimation(mCurrentAnimation - 1, elapsedSeconds);
|
||||
if (elapsedSeconds < mCrossFadeDuration && mPreviousAnimation > 0) {
|
||||
const double previousSeconds = currentTime - mPreviousStartTime;
|
||||
const float lerpFactor = elapsedSeconds / mCrossFadeDuration;
|
||||
mAnimator->applyCrossFade(mPreviousAnimation - 1, previousSeconds, lerpFactor);
|
||||
}
|
||||
}
|
||||
if (mShowingRestPose) {
|
||||
mAnimator->resetBoneMatrices();
|
||||
@@ -1031,6 +1034,8 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::Indent();
|
||||
int selectedAnimation = mCurrentAnimation;
|
||||
ImGui::RadioButton("Disable", &selectedAnimation, 0);
|
||||
ImGui::SliderFloat("Cross fade", &mCrossFadeDuration, 0.0f, 2.0f,
|
||||
"%4.2f seconds", ImGuiSliderFlags_AlwaysClamp);
|
||||
for (size_t i = 0, count = mAnimator->getAnimationCount(); i < count; ++i) {
|
||||
std::string label = mAnimator->getAnimationName(i);
|
||||
if (label.empty()) {
|
||||
@@ -1039,6 +1044,7 @@ void ViewerGui::updateUserInterface() {
|
||||
ImGui::RadioButton(label.c_str(), &selectedAnimation, i + 1);
|
||||
}
|
||||
if (selectedAnimation != mCurrentAnimation) {
|
||||
mPreviousAnimation = mCurrentAnimation;
|
||||
mCurrentAnimation = selectedAnimation;
|
||||
mResetAnimation = true;
|
||||
}
|
||||
|
||||
1
web/filament-js/filament.d.ts
vendored
1
web/filament-js/filament.d.ts
vendored
@@ -598,6 +598,7 @@ export class gltfio$FilamentInstance {
|
||||
|
||||
export class gltfio$Animator {
|
||||
public applyAnimation(index: number): void;
|
||||
public applyCrossFade(previousAnimIndex: number, previousAnimTime: number, alpha: number): void;
|
||||
public updateBoneMatrices(): void;
|
||||
public resetBoneMatrices(): void;
|
||||
public getAnimationCount(): number;
|
||||
|
||||
@@ -1707,6 +1707,7 @@ class_<SurfaceOrientation>("SurfaceOrientation")
|
||||
class_<Animator>("gltfio$Animator")
|
||||
.function("applyAnimation", &Animator::applyAnimation)
|
||||
.function("updateBoneMatrices", &Animator::updateBoneMatrices)
|
||||
.function("applyCrossFade", &Animator::applyCrossFade)
|
||||
.function("resetBoneMatrices", &Animator::resetBoneMatrices)
|
||||
.function("getAnimationCount", &Animator::getAnimationCount)
|
||||
.function("getAnimationDuration", &Animator::getAnimationDuration)
|
||||
|
||||
Reference in New Issue
Block a user