add fade in/out to dynamic bone animations
This commit is contained in:
@@ -274,7 +274,7 @@ abstract class AbstractFilamentViewer {
|
|||||||
/// to transform to another space, you will need to do so manually.
|
/// to transform to another space, you will need to do so manually.
|
||||||
///
|
///
|
||||||
Future addBoneAnimation(FilamentEntity entity, BoneAnimationData animation,
|
Future addBoneAnimation(FilamentEntity entity, BoneAnimationData animation,
|
||||||
{int skinIndex = 0});
|
{int skinIndex = 0, double fadeInInSecs=0.0, double fadeOutInSecs=0.0});
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Gets the entity representing the bone at [boneIndex]/[skinIndex].
|
/// Gets the entity representing the bone at [boneIndex]/[skinIndex].
|
||||||
|
|||||||
@@ -461,7 +461,7 @@ external void reset_to_rest_pose(
|
|||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Void Function(ffi.Pointer<ffi.Void>, EntityId, ffi.Int, ffi.Int,
|
ffi.Void Function(ffi.Pointer<ffi.Void>, EntityId, ffi.Int, ffi.Int,
|
||||||
ffi.Pointer<ffi.Float>, ffi.Int, ffi.Float)>(
|
ffi.Pointer<ffi.Float>, ffi.Int, ffi.Float, ffi.Float, ffi.Float)>(
|
||||||
symbol: 'add_bone_animation',
|
symbol: 'add_bone_animation',
|
||||||
assetId: 'package:dart_filament/dart_filament.dart')
|
assetId: 'package:dart_filament/dart_filament.dart')
|
||||||
external void add_bone_animation(
|
external void add_bone_animation(
|
||||||
@@ -472,6 +472,8 @@ external void add_bone_animation(
|
|||||||
ffi.Pointer<ffi.Float> frameData,
|
ffi.Pointer<ffi.Float> frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
double frameLengthInMs,
|
double frameLengthInMs,
|
||||||
|
double fadeOutInSecs,
|
||||||
|
double fadeInInSecs,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
@@ -485,6 +487,19 @@ external void get_local_transform(
|
|||||||
ffi.Pointer<ffi.Float> arg2,
|
ffi.Pointer<ffi.Float> arg2,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ffi.Native<
|
||||||
|
ffi.Void Function(ffi.Pointer<ffi.Void>, EntityId, ffi.Int,
|
||||||
|
ffi.Pointer<ffi.Float>, ffi.Int)>(
|
||||||
|
symbol: 'get_rest_local_transforms',
|
||||||
|
assetId: 'package:dart_filament/dart_filament.dart')
|
||||||
|
external void get_rest_local_transforms(
|
||||||
|
ffi.Pointer<ffi.Void> sceneManager,
|
||||||
|
int entityId,
|
||||||
|
int skinIndex,
|
||||||
|
ffi.Pointer<ffi.Float> out,
|
||||||
|
int numBones,
|
||||||
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Void Function(
|
ffi.Void Function(
|
||||||
ffi.Pointer<ffi.Void>, EntityId, ffi.Pointer<ffi.Float>)>(
|
ffi.Pointer<ffi.Void>, EntityId, ffi.Pointer<ffi.Float>)>(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'dart:io';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
import 'package:animation_tools_dart/animation_tools_dart.dart';
|
||||||
import 'package:dart_filament/dart_filament/compatibility/compatibility.dart';
|
import 'package:dart_filament/dart_filament/compatibility/compatibility.dart';
|
||||||
import 'package:dart_filament/dart_filament/compatibility/native/compatibility.dart';
|
|
||||||
import 'package:dart_filament/dart_filament/entities/filament_entity.dart';
|
import 'package:dart_filament/dart_filament/entities/filament_entity.dart';
|
||||||
import 'package:dart_filament/dart_filament/entities/gizmo.dart';
|
import 'package:dart_filament/dart_filament/entities/gizmo.dart';
|
||||||
|
|
||||||
@@ -676,7 +675,7 @@ class FilamentViewer extends AbstractFilamentViewer {
|
|||||||
///
|
///
|
||||||
@override
|
@override
|
||||||
Future addBoneAnimation(FilamentEntity entity, BoneAnimationData animation,
|
Future addBoneAnimation(FilamentEntity entity, BoneAnimationData animation,
|
||||||
{int skinIndex = 0}) async {
|
{int skinIndex = 0, double fadeOutInSecs=0.0, double fadeInInSecs=0.0}) async {
|
||||||
if (animation.space != Space.Bone &&
|
if (animation.space != Space.Bone &&
|
||||||
animation.space != Space.ParentWorldRotation) {
|
animation.space != Space.ParentWorldRotation) {
|
||||||
throw UnimplementedError("TODO - support ${animation.space}");
|
throw UnimplementedError("TODO - support ${animation.space}");
|
||||||
@@ -739,7 +738,7 @@ class FilamentViewer extends AbstractFilamentViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add_bone_animation(_sceneManager!, entity, skinIndex, entityBoneIndex,
|
add_bone_animation(_sceneManager!, entity, skinIndex, entityBoneIndex,
|
||||||
data, numFrames, animation.frameLengthInMs);
|
data, numFrames, animation.frameLengthInMs, fadeOutInSecs, fadeInInSecs);
|
||||||
}
|
}
|
||||||
allocator.free(data);
|
allocator.free(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,9 @@ extern "C"
|
|||||||
int boneIndex,
|
int boneIndex,
|
||||||
const float *const frameData,
|
const float *const frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs);
|
float frameLengthInMs,
|
||||||
|
float fadeOutInSecs,
|
||||||
|
float fadeInInSecs);
|
||||||
EMSCRIPTEN_KEEPALIVE void get_local_transform(void *sceneManager,
|
EMSCRIPTEN_KEEPALIVE void get_local_transform(void *sceneManager,
|
||||||
EntityId entityId, float* const);
|
EntityId entityId, float* const);
|
||||||
EMSCRIPTEN_KEEPALIVE void get_rest_local_transforms(void *sceneManager,
|
EMSCRIPTEN_KEEPALIVE void get_rest_local_transforms(void *sceneManager,
|
||||||
|
|||||||
@@ -116,7 +116,10 @@ namespace flutter_filament
|
|||||||
int boneIndex,
|
int boneIndex,
|
||||||
const float *const frameData,
|
const float *const frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs);
|
float frameLengthInMs,
|
||||||
|
float fadeOutInSecs,
|
||||||
|
float fadeInInSecs
|
||||||
|
);
|
||||||
|
|
||||||
std::unique_ptr<std::vector<math::mat4f>> getBoneRestTranforms(EntityId entityId, int skinIndex);
|
std::unique_ptr<std::vector<math::mat4f>> getBoneRestTranforms(EntityId entityId, int skinIndex);
|
||||||
void resetBones(EntityId entityId);
|
void resetBones(EntityId entityId);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include <gltfio/Animator.h>
|
#include <gltfio/Animator.h>
|
||||||
#include <gltfio/AssetLoader.h>
|
#include <gltfio/AssetLoader.h>
|
||||||
#include <gltfio/ResourceLoader.h>
|
#include <gltfio/ResourceLoader.h>
|
||||||
|
#include <gltfio/math.h>
|
||||||
#include <utils/NameComponentManager.h>
|
#include <utils/NameComponentManager.h>
|
||||||
|
|
||||||
template class std::vector<float>;
|
template class std::vector<float>;
|
||||||
@@ -47,13 +48,17 @@ namespace flutter_filament
|
|||||||
float durationInSecs = 0;
|
float durationInSecs = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// @brief
|
||||||
|
/// 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 : AnimationStatus
|
||||||
{
|
{
|
||||||
int index = -1;
|
int index = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Use this to construct a dynamic (i.e. non-glTF embedded) morph target animation.
|
// The status of a morph target animation created dynamically at runtime (not glTF embedded).
|
||||||
//
|
//
|
||||||
struct MorphAnimation : AnimationStatus
|
struct MorphAnimation : AnimationStatus
|
||||||
{
|
{
|
||||||
@@ -66,7 +71,7 @@ namespace flutter_filament
|
|||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Use this to construct a dynamic (i.e. non-glTF embedded) bone/joint animation.
|
// The status of a skeletal animation created dynamically at runtime (not glTF embedded).
|
||||||
//
|
//
|
||||||
struct BoneAnimation : AnimationStatus
|
struct BoneAnimation : AnimationStatus
|
||||||
{
|
{
|
||||||
@@ -75,6 +80,8 @@ namespace flutter_filament
|
|||||||
int lengthInFrames;
|
int lengthInFrames;
|
||||||
float frameLengthInMs = 0;
|
float frameLengthInMs = 0;
|
||||||
std::vector<math::mat4f> frameData;
|
std::vector<math::mat4f> frameData;
|
||||||
|
float fadeOutInSecs = 0;
|
||||||
|
float fadeInInSecs = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AnimationComponent
|
struct AnimationComponent
|
||||||
@@ -149,7 +156,6 @@ namespace flutter_filament
|
|||||||
|
|
||||||
void update()
|
void update()
|
||||||
{
|
{
|
||||||
auto now = high_resolution_clock::now();
|
|
||||||
|
|
||||||
for (auto it = begin(); it < end(); it++)
|
for (auto it = begin(); it < end(); it++)
|
||||||
{
|
{
|
||||||
@@ -162,6 +168,7 @@ namespace flutter_filament
|
|||||||
|
|
||||||
if (std::holds_alternative<FilamentInstance *>(animationComponent.target))
|
if (std::holds_alternative<FilamentInstance *>(animationComponent.target))
|
||||||
{
|
{
|
||||||
|
|
||||||
auto target = std::get<FilamentInstance *>(animationComponent.target);
|
auto target = std::get<FilamentInstance *>(animationComponent.target);
|
||||||
auto animator = target->getAnimator();
|
auto animator = target->getAnimator();
|
||||||
auto &gltfAnimations = animationComponent.gltfAnimations;
|
auto &gltfAnimations = animationComponent.gltfAnimations;
|
||||||
@@ -170,6 +177,7 @@ namespace flutter_filament
|
|||||||
if(gltfAnimations.size() > 0) {
|
if(gltfAnimations.size() > 0) {
|
||||||
for (int i = ((int)gltfAnimations.size()) - 1; i >= 0; i--)
|
for (int i = ((int)gltfAnimations.size()) - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
|
auto now = high_resolution_clock::now();
|
||||||
|
|
||||||
auto animationStatus = animationComponent.gltfAnimations[i];
|
auto animationStatus = animationComponent.gltfAnimations[i];
|
||||||
|
|
||||||
@@ -197,23 +205,33 @@ namespace flutter_filament
|
|||||||
animator->updateBoneMatrices();
|
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--)
|
for (int i = (int)boneAnimations.size() - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
auto animationStatus = boneAnimations[i];
|
auto animationStatus = boneAnimations[i];
|
||||||
|
|
||||||
auto elapsedInSecs = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - animationStatus.start).count()) / 1000.0f;
|
auto now = high_resolution_clock::now();
|
||||||
|
|
||||||
if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs)
|
auto elapsedInMillis = float(std::chrono::duration_cast<std::chrono::milliseconds>(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))
|
||||||
{
|
{
|
||||||
Log("Bone animation %d finished", i);
|
if(!animationStatus.loop) {
|
||||||
boneAnimations.erase(boneAnimations.begin() + i);
|
Log("Bone animation %d finished", i);
|
||||||
continue;
|
boneAnimations.erase(boneAnimations.begin() + i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float elapsedFrames = elapsedInSecs * 1000.0f / animationStatus.frameLengthInMs;
|
// 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 = static_cast<int>(elapsedFrames) % animationStatus.lengthInFrames;
|
int currFrame = std::floor(elapsedInFrames);
|
||||||
float delta = elapsedFrames - currFrame;
|
|
||||||
int nextFrame = currFrame;
|
int nextFrame = currFrame;
|
||||||
|
|
||||||
// offset from the end if reverse
|
// offset from the end if reverse
|
||||||
@@ -240,27 +258,65 @@ namespace flutter_filament
|
|||||||
nextFrame = currFrame;
|
nextFrame = currFrame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
currFrame = std::clamp(currFrame, 0, animationStatus.lengthInFrames - 1);
|
||||||
|
nextFrame = std::clamp(nextFrame, 0, animationStatus.lengthInFrames - 1);
|
||||||
|
|
||||||
// linear interpolation for this animation
|
float frameDelta = elapsedInFrames - currFrame;
|
||||||
math::mat4f curr = (1 - delta) * animationStatus.frameData[currFrame];
|
|
||||||
math::mat4f next = delta * animationStatus.frameData[nextFrame];
|
// linearly interpolate this animation between its last/current frames
|
||||||
math::mat4f localTransform = curr + next;
|
// 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];
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("fadeDelta %f ", fadeDelta);
|
||||||
|
|
||||||
auto jointTransform = _transformManager.getInstance(joint);
|
auto jointTransform = _transformManager.getInstance(joint);
|
||||||
auto currentTransform = _transformManager.getTransform(jointTransform);
|
|
||||||
|
|
||||||
// linear interpolation between the current transform (e.g. if set by the gltf frame)
|
// linearly interpolate this animation between its current (interpolated) frame and the current transform (i.e. as set by the gltf frame)
|
||||||
math::mat4f curr2 = (1 - delta) * animationStatus.frameData[currFrame];
|
// // if we are fading in or out, apply a delta
|
||||||
math::mat4f next2 = delta * currentTransform;
|
if (fadeDelta >= 0.0f && fadeDelta <= 1.0f) {
|
||||||
math::mat4f localTransform2 = curr + next;
|
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, localTransform2);
|
_transformManager.setTransform(jointTransform, composeMatrix(newTranslation, newRotation, newScale));
|
||||||
|
|
||||||
animator->updateBoneMatrices();
|
animator->updateBoneMatrices();
|
||||||
|
|
||||||
if (animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs)
|
if (animationStatus.loop && elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs))
|
||||||
{
|
{
|
||||||
animationStatus.start = now;
|
animationStatus.start = now;
|
||||||
}
|
}
|
||||||
@@ -269,6 +325,8 @@ namespace flutter_filament
|
|||||||
for (int i = (int)morphAnimations.size() - 1; i >= 0; i--)
|
for (int i = (int)morphAnimations.size() - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
auto now = high_resolution_clock::now();
|
||||||
|
|
||||||
auto animationStatus = morphAnimations[i];
|
auto animationStatus = morphAnimations[i];
|
||||||
|
|
||||||
auto elapsedInSecs = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - animationStatus.start).count()) / 1000.0f;
|
auto elapsedInSecs = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - animationStatus.start).count()) / 1000.0f;
|
||||||
|
|||||||
@@ -422,9 +422,11 @@ extern "C"
|
|||||||
int boneIndex,
|
int boneIndex,
|
||||||
const float *const frameData,
|
const float *const frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs)
|
float frameLengthInMs,
|
||||||
|
float fadeOutInSecs,
|
||||||
|
float fadeInInSecs)
|
||||||
{
|
{
|
||||||
((SceneManager *)sceneManager)->addBoneAnimation(asset, skinIndex, boneIndex, frameData, numFrames, frameLengthInMs);
|
((SceneManager *)sceneManager)->addBoneAnimation(asset, skinIndex, boneIndex, frameData, numFrames, frameLengthInMs, fadeOutInSecs, fadeInInSecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE void set_post_processing(void *const viewer, bool enabled)
|
EMSCRIPTEN_KEEPALIVE void set_post_processing(void *const viewer, bool enabled)
|
||||||
|
|||||||
@@ -1025,7 +1025,8 @@ namespace flutter_filament
|
|||||||
int boneIndex,
|
int boneIndex,
|
||||||
const float *const frameData,
|
const float *const frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs)
|
float frameLengthInMs,
|
||||||
|
float fadeOutInSecs, float fadeInInSecs)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(_mutex);
|
std::lock_guard lock(_mutex);
|
||||||
|
|
||||||
@@ -1079,6 +1080,8 @@ namespace flutter_filament
|
|||||||
animation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
|
animation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
|
||||||
animation.lengthInFrames = numFrames;
|
animation.lengthInFrames = numFrames;
|
||||||
animation.frameLengthInMs = frameLengthInMs;
|
animation.frameLengthInMs = frameLengthInMs;
|
||||||
|
animation.fadeOutInSecs = fadeOutInSecs;
|
||||||
|
animation.fadeInInSecs = fadeInInSecs;
|
||||||
animation.skinIndex = skinIndex;
|
animation.skinIndex = skinIndex;
|
||||||
if(!_animationComponentManager->hasComponent(instance->getRoot())) {
|
if(!_animationComponentManager->hasComponent(instance->getRoot())) {
|
||||||
Log("ERROR: specified entity is not animatable (has no animation component attached).");
|
Log("ERROR: specified entity is not animatable (has no animation component attached).");
|
||||||
|
|||||||
Reference in New Issue
Block a user