split animation components into GltfAnimation/MorphAnimation/BoneAnimation

This commit is contained in:
Nick Fisher
2025-05-17 10:11:55 +08:00
parent f9d09e17ef
commit c98e604e76
12 changed files with 1109 additions and 483 deletions

View File

@@ -2935,20 +2935,52 @@ external void AnimationManager_update(
int frameTimeInNanos, int frameTimeInNanos,
); );
@ffi.Native<
ffi.Bool Function(
ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>)>(isLeaf: true)
external bool AnimationManager_addGltfAnimationComponent(
ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> tSceneAsset,
);
@ffi.Native<
ffi.Bool Function(
ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>)>(isLeaf: true)
external bool AnimationManager_removeGltfAnimationComponent(
ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> tSceneAsset,
);
@ffi.Native<ffi.Void Function(ffi.Pointer<TAnimationManager>, EntityId)>( @ffi.Native<ffi.Void Function(ffi.Pointer<TAnimationManager>, EntityId)>(
isLeaf: true) isLeaf: true)
external void AnimationManager_addAnimationComponent( external void AnimationManager_addMorphAnimationComponent(
ffi.Pointer<TAnimationManager> tAnimationManager, ffi.Pointer<TAnimationManager> tAnimationManager,
int entityId, int entityId,
); );
@ffi.Native<ffi.Void Function(ffi.Pointer<TAnimationManager>, EntityId)>( @ffi.Native<ffi.Void Function(ffi.Pointer<TAnimationManager>, EntityId)>(
isLeaf: true) isLeaf: true)
external void AnimationManager_removeAnimationComponent( external void AnimationManager_removeMorphAnimationComponent(
ffi.Pointer<TAnimationManager> tAnimationManager, ffi.Pointer<TAnimationManager> tAnimationManager,
int entityId, int entityId,
); );
@ffi.Native<
ffi.Bool Function(
ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>)>(isLeaf: true)
external bool AnimationManager_addBoneAnimationComponent(
ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> tSceneAsset,
);
@ffi.Native<
ffi.Bool Function(
ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>)>(isLeaf: true)
external bool AnimationManager_removeBoneAnimationComponent(
ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> tSceneAsset,
);
@ffi.Native< @ffi.Native<
ffi.Bool Function( ffi.Bool Function(
ffi.Pointer<TAnimationManager>, ffi.Pointer<TAnimationManager>,
@@ -2984,7 +3016,7 @@ external void AnimationManager_resetToRestPose(
); );
@ffi.Native< @ffi.Native<
ffi.Void Function( ffi.Bool Function(
ffi.Pointer<TAnimationManager>, ffi.Pointer<TAnimationManager>,
ffi.Pointer<TSceneAsset>, ffi.Pointer<TSceneAsset>,
ffi.Int, ffi.Int,
@@ -2995,7 +3027,7 @@ external void AnimationManager_resetToRestPose(
ffi.Float, ffi.Float,
ffi.Float, ffi.Float,
ffi.Float)>(isLeaf: true) ffi.Float)>(isLeaf: true)
external void AnimationManager_addBoneAnimation( external bool AnimationManager_addBoneAnimation(
ffi.Pointer<TAnimationManager> tAnimationManager, ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> tSceneAsset, ffi.Pointer<TSceneAsset> tSceneAsset,
int skinIndex, int skinIndex,
@@ -3041,7 +3073,7 @@ external void AnimationManager_getInverseBindMatrix(
); );
@ffi.Native< @ffi.Native<
ffi.Void Function( ffi.Bool Function(
ffi.Pointer<TAnimationManager>, ffi.Pointer<TAnimationManager>,
ffi.Pointer<TSceneAsset>, ffi.Pointer<TSceneAsset>,
ffi.Int, ffi.Int,
@@ -3050,9 +3082,9 @@ external void AnimationManager_getInverseBindMatrix(
ffi.Bool, ffi.Bool,
ffi.Float, ffi.Float,
ffi.Float)>(isLeaf: true) ffi.Float)>(isLeaf: true)
external void AnimationManager_playAnimation( external bool AnimationManager_playGltfAnimation(
ffi.Pointer<TAnimationManager> tAnimationManager, ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> sceneAsset, ffi.Pointer<TSceneAsset> tSceneAsset,
int index, int index,
bool loop, bool loop,
bool reverse, bool reverse,
@@ -3062,9 +3094,9 @@ external void AnimationManager_playAnimation(
); );
@ffi.Native< @ffi.Native<
ffi.Void Function(ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>, ffi.Bool Function(ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>,
ffi.Int)>(isLeaf: true) ffi.Int)>(isLeaf: true)
external void AnimationManager_stopAnimation( external bool AnimationManager_stopGltfAnimation(
ffi.Pointer<TAnimationManager> tAnimationManager, ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> sceneAsset, ffi.Pointer<TSceneAsset> sceneAsset,
int index, int index,
@@ -3073,7 +3105,7 @@ external void AnimationManager_stopAnimation(
@ffi.Native< @ffi.Native<
ffi.Float Function(ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>, ffi.Float Function(ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>,
ffi.Int)>(isLeaf: true) ffi.Int)>(isLeaf: true)
external double AnimationManager_getAnimationDuration( external double AnimationManager_getGltfAnimationDuration(
ffi.Pointer<TAnimationManager> tAnimationManager, ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> sceneAsset, ffi.Pointer<TSceneAsset> sceneAsset,
int animationIndex, int animationIndex,
@@ -3082,7 +3114,7 @@ external double AnimationManager_getAnimationDuration(
@ffi.Native< @ffi.Native<
ffi.Int Function( ffi.Int Function(
ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>)>(isLeaf: true) ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>)>(isLeaf: true)
external int AnimationManager_getAnimationCount( external int AnimationManager_getGltfAnimationCount(
ffi.Pointer<TAnimationManager> tAnimationManager, ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> sceneAsset, ffi.Pointer<TSceneAsset> sceneAsset,
); );
@@ -3090,7 +3122,7 @@ external int AnimationManager_getAnimationCount(
@ffi.Native< @ffi.Native<
ffi.Void Function(ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>, ffi.Void Function(ffi.Pointer<TAnimationManager>, ffi.Pointer<TSceneAsset>,
ffi.Pointer<ffi.Char>, ffi.Int)>(isLeaf: true) ffi.Pointer<ffi.Char>, ffi.Int)>(isLeaf: true)
external void AnimationManager_getAnimationName( external void AnimationManager_getGltfAnimationName(
ffi.Pointer<TAnimationManager> tAnimationManager, ffi.Pointer<TAnimationManager> tAnimationManager,
ffi.Pointer<TSceneAsset> sceneAsset, ffi.Pointer<TSceneAsset> sceneAsset,
ffi.Pointer<ffi.Char> outPtr, ffi.Pointer<ffi.Char> outPtr,

View File

@@ -1,9 +1,6 @@
import 'dart:typed_data';
import 'package:vector_math/vector_math_64.dart' as v64; import 'package:vector_math/vector_math_64.dart' as v64;
import 'package:animation_tools_dart/src/bone_animation_data.dart'; import 'package:animation_tools_dart/src/bone_animation_data.dart';
import 'package:animation_tools_dart/src/morph_animation_data.dart'; import 'package:animation_tools_dart/src/morph_animation_data.dart';
import 'package:thermion_dart/src/filament/src/interface/layers.dart';
import 'package:thermion_dart/src/bindings/bindings.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_scene.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_scene.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart'; import 'package:thermion_dart/src/filament/src/implementation/ffi_texture.dart';
@@ -159,7 +156,7 @@ class BackgroundImage extends ThermionAsset {
} }
@override @override
Future addAnimationComponent(ThermionEntity entity) { Future addAnimationComponent() {
// TODO: implement addAnimationComponent // TODO: implement addAnimationComponent
throw UnimplementedError(); throw UnimplementedError();
} }
@@ -256,7 +253,7 @@ class BackgroundImage extends ThermionAsset {
} }
@override @override
Future removeAnimationComponent(ThermionEntity entity) { Future removeAnimationComponent() {
// TODO: implement removeAnimationComponent // TODO: implement removeAnimationComponent
throw UnimplementedError(); throw UnimplementedError();
} }
@@ -355,4 +352,6 @@ class BackgroundImage extends ThermionAsset {
Future<v64.Aabb3> getBoundingBox() { Future<v64.Aabb3> getBoundingBox() {
throw UnimplementedError(); throw UnimplementedError();
} }
} }

View File

@@ -493,16 +493,13 @@ class FFIAsset extends ThermionAsset {
if (weights.isEmpty) { if (weights.isEmpty) {
throw Exception("Weights must not be empty"); throw Exception("Weights must not be empty");
} }
var weightsPtr = allocate<Float>(weights.length); var weightsF32 = Float32List.fromList(weights);
for (int i = 0; i < weights.length; i++) {
weightsPtr[i] = weights[i];
}
var success = await withBoolCallback((cb) { var success = await withBoolCallback((cb) {
AnimationManager_setMorphTargetWeightsRenderThread( AnimationManager_setMorphTargetWeightsRenderThread(
animationManager, entity, weightsPtr, weights.length, cb); animationManager, entity, weightsF32.address, weights.length, cb);
}); });
free(weightsPtr); weightsF32.free();
if (!success) { if (!success) {
throw Exception( throw Exception(
@@ -521,6 +518,10 @@ class FFIAsset extends ThermionAsset {
var count = AnimationManager_getMorphTargetNameCount( var count = AnimationManager_getMorphTargetNameCount(
animationManager, asset, entity); animationManager, asset, entity);
if (count < 0) {
throw Exception("Failed to retrieve morph target name count");
}
var outPtr = allocate<Char>(255); var outPtr = allocate<Char>(255);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
AnimationManager_getMorphTargetName( AnimationManager_getMorphTargetName(
@@ -559,13 +560,17 @@ class FFIAsset extends ThermionAsset {
/// ///
/// ///
@override @override
Future<List<String>> getAnimationNames() async { Future<List<String>> getGltfAnimationNames() async {
var animationCount = var animationCount =
AnimationManager_getAnimationCount(animationManager, asset); AnimationManager_getGltfAnimationCount(animationManager, asset);
if (animationCount == -1) {
throw Exception("This is not a glTF asset");
}
var names = <String>[]; var names = <String>[];
var outPtr = allocate<Char>(255); var outPtr = allocate<Char>(255);
for (int i = 0; i < animationCount; i++) { for (int i = 0; i < animationCount; i++) {
AnimationManager_getAnimationName(animationManager, asset, outPtr, i); AnimationManager_getGltfAnimationName(animationManager, asset, outPtr, i);
names.add(outPtr.cast<Utf8>().toDartString()); names.add(outPtr.cast<Utf8>().toDartString());
} }
free(outPtr); free(outPtr);
@@ -577,8 +582,8 @@ class FFIAsset extends ThermionAsset {
/// ///
/// ///
@override @override
Future<double> getAnimationDuration(int animationIndex) async { Future<double> getGltfAnimationDuration(int animationIndex) async {
return AnimationManager_getAnimationDuration( return AnimationManager_getGltfAnimationDuration(
animationManager, asset, animationIndex); animationManager, asset, animationIndex);
} }
@@ -586,12 +591,12 @@ class FFIAsset extends ThermionAsset {
/// ///
/// ///
Future<double> getAnimationDurationByName(String name) async { Future<double> getAnimationDurationByName(String name) async {
var animations = await getAnimationNames(); var animations = await getGltfAnimationNames();
var index = animations.indexOf(name); var index = animations.indexOf(name);
if (index == -1) { if (index == -1) {
throw Exception("Failed to find animation $name"); throw Exception("Failed to find animation $name");
} }
return getAnimationDuration(index); return getGltfAnimationDuration(index);
} }
/// ///
@@ -904,47 +909,52 @@ class FFIAsset extends ThermionAsset {
/// ///
/// ///
@override @override
Future playAnimation(int index, Future playGltfAnimation(int index,
{bool loop = false, {bool loop = false,
bool reverse = false, bool reverse = false,
bool replaceActive = true, bool replaceActive = true,
double crossfade = 0.0, double crossfade = 0.0,
double startOffset = 0.0}) async { double startOffset = 0.0}) async {
AnimationManager_playAnimation(animationManager, asset, index, loop, if (!AnimationManager_playGltfAnimation(animationManager, asset, index,
reverse, replaceActive, crossfade, startOffset); loop, reverse, replaceActive, crossfade, startOffset)) {
throw Exception("Failed to play glTF animation. Check logs for details");
}
} }
/// ///
/// ///
/// ///
@override @override
Future stopAnimation(int animationIndex) async { Future stopGltfAnimation(int animationIndex) async {
AnimationManager_stopAnimation(animationManager, asset, animationIndex); if (!AnimationManager_stopGltfAnimation(
animationManager, asset, animationIndex)) {
throw Exception("Failed to stop glTF animation. Check logs for details");
}
} }
/// ///
/// ///
/// ///
@override @override
Future stopAnimationByName(String name) async { Future stopGltfAnimationByName(String name) async {
var animations = await getAnimationNames(); var animations = await getGltfAnimationNames();
await stopAnimation(animations.indexOf(name)); await stopGltfAnimation(animations.indexOf(name));
} }
/// ///
/// ///
/// ///
@override @override
Future playAnimationByName(String name, Future playGltfAnimationByName(String name,
{bool loop = false, {bool loop = false,
bool reverse = false, bool reverse = false,
bool replaceActive = true, bool replaceActive = true,
double crossfade = 0.0, double crossfade = 0.0,
bool wait = false}) async { bool wait = false}) async {
var animations = await getAnimationNames(); var animations = await getGltfAnimationNames();
var index = animations.indexOf(name); var index = animations.indexOf(name);
var duration = await getAnimationDuration(index); var duration = await getGltfAnimationDuration(index);
await playAnimation(index, await playGltfAnimation(index,
loop: loop, loop: loop,
reverse: reverse, reverse: reverse,
replaceActive: replaceActive, replaceActive: replaceActive,
@@ -967,14 +977,14 @@ class FFIAsset extends ThermionAsset {
/// ///
/// ///
@override @override
Future addAnimationComponent(ThermionEntity entity) async { Future addAnimationComponent() async {
AnimationManager_addAnimationComponent(animationManager, entity); AnimationManager_addGltfAnimationComponent(animationManager, this.asset);
} }
/// ///
/// ///
/// ///
Future removeAnimationComponent(ThermionEntity entity) async { Future removeAnimationComponent() async {
AnimationManager_removeAnimationComponent(animationManager, entity); AnimationManager_removeGltfAnimationComponent(animationManager, this.asset);
} }
} }

View File

@@ -66,8 +66,8 @@ abstract class ThermionAsset {
/// ///
/// The dimensions of the bounding box for this asset. /// The dimensions of the bounding box for this asset.
/// This is independent of the boundingBoxAsset (which is used to visualize /// This is independent of the boundingBoxAsset (which is used to visualize
/// the bounding box in the scene); you do not need to call /// the bounding box in the scene); you do not need to call
/// [createBoundingBoxAsset] before this method. /// [createBoundingBoxAsset] before this method.
Future<Aabb3> getBoundingBox(); Future<Aabb3> getBoundingBox();
@@ -153,36 +153,46 @@ abstract class ThermionAsset {
/// ///
/// Schedules the glTF animation at [index] in [asset] to start playing on the next frame. /// Schedules the glTF animation at [index] in [asset] to start playing on the next frame.
/// ///
Future playAnimation(int index, Future playGltfAnimation(int index,
{bool loop = false, {bool loop = false,
bool reverse = false, bool reverse = false,
bool replaceActive = true, bool replaceActive = true,
double crossfade = 0.0, double crossfade = 0.0,
double startOffset = 0.0}); double startOffset = 0.0}) {
throw UnimplementedError();
}
/// ///
/// Schedules the glTF animation at [index] in [entity] to start playing on the next frame. /// Schedules the glTF animation at [index] in [entity] to start playing on the next frame.
/// ///
Future playAnimationByName(String name, Future playGltfAnimationByName(String name,
{bool loop = false, {bool loop = false,
bool reverse = false, bool reverse = false,
bool replaceActive = true, bool replaceActive = true,
double crossfade = 0.0}); double crossfade = 0.0}) {
throw UnimplementedError();
}
/// ///
/// ///
/// ///
Future setGltfAnimationFrame(int index, int animationFrame); Future setGltfAnimationFrame(int index, int animationFrame) {
throw UnimplementedError();
}
/// ///
/// ///
/// ///
Future stopAnimation(int animationIndex); Future stopGltfAnimation(int animationIndex) {
throw UnimplementedError();
}
/// ///
/// ///
/// ///
Future stopAnimationByName(String name); Future stopGltfAnimationByName(String name) {
throw UnimplementedError();
}
/// ///
/// Set the weights for all morph targets in [entity] to [weights]. /// Set the weights for all morph targets in [entity] to [weights].
@@ -191,27 +201,37 @@ abstract class ThermionAsset {
/// IMPORTANT - this accepts the actual ThermionEntity with the relevant morph targets (unlike [getMorphTargetNames], which uses the parent entity and the child mesh name). /// IMPORTANT - this accepts the actual ThermionEntity with the relevant morph targets (unlike [getMorphTargetNames], which uses the parent entity and the child mesh name).
/// Use [getChildEntityByName] if you are setting the weights for a child mesh. /// Use [getChildEntityByName] if you are setting the weights for a child mesh.
/// ///
Future setMorphTargetWeights(ThermionEntity entity, List<double> weights); Future setMorphTargetWeights(ThermionEntity entity, List<double> weights) {
throw UnimplementedError();
}
/// ///
/// Gets the names of all morph targets for [entity] (which must be a renderable entity) /// Gets the names of all morph targets for [entity] (which must be a renderable entity)
/// ///
Future<List<String>> getMorphTargetNames({ThermionEntity? entity}); Future<List<String>> getMorphTargetNames({ThermionEntity? entity}) {
throw UnimplementedError();
}
/// ///
/// Gets the names of all bones for the skin at [skinIndex]. /// Gets the names of all bones for the skin at [skinIndex].
/// ///
Future<List<String>> getBoneNames({int skinIndex = 0}); Future<List<String>> getBoneNames({int skinIndex = 0}) {
throw UnimplementedError();
}
/// ///
/// Gets the names of all glTF animations embedded in the specified entity. /// Gets the names of all glTF animations embedded in the specified entity.
/// ///
Future<List<String>> getAnimationNames(); Future<List<String>> getGltfAnimationNames() {
throw UnimplementedError();
}
/// ///
/// Returns the length (in seconds) of the animation at the given index. /// Returns the length (in seconds) of the animation at the given index.
/// ///
Future<double> getAnimationDuration(int animationIndex); Future<double> getGltfAnimationDuration(int animationIndex) {
throw UnimplementedError();
}
/// ///
/// Construct animation(s) for every entity under [asset]. If [targetMeshNames] is provided, only entities with matching names will be animated. /// Construct animation(s) for every entity under [asset]. If [targetMeshNames] is provided, only entities with matching names will be animated.
@@ -221,12 +241,16 @@ abstract class ThermionAsset {
/// It is permissible for [animation] to omit any targets that do exist under [meshName]; these simply won't be animated. /// It is permissible for [animation] to omit any targets that do exist under [meshName]; these simply won't be animated.
/// ///
Future setMorphAnimationData(MorphAnimationData animation, Future setMorphAnimationData(MorphAnimationData animation,
{List<String>? targetMeshNames}); {List<String>? targetMeshNames}) {
throw UnimplementedError();
}
/// ///
/// Clear all current morph animations for [entity]. /// Clear all current morph animations for [entity].
/// ///
Future clearMorphAnimationData(ThermionEntity entity); Future clearMorphAnimationData(ThermionEntity entity) {
throw UnimplementedError();
}
/// ///
/// Resets all bones in the given entity to their rest pose. /// Resets all bones in the given entity to their rest pose.
@@ -307,10 +331,10 @@ abstract class ThermionAsset {
/// An [entity] will only be animatable after an animation component is attached. /// An [entity] will only be animatable after an animation component is attached.
/// Any calls to [playAnimation]/[setBoneAnimation]/[setMorphAnimation] will have no visual effect until [addAnimationComponent] has been called on the instance. /// Any calls to [playAnimation]/[setBoneAnimation]/[setMorphAnimation] will have no visual effect until [addAnimationComponent] has been called on the instance.
/// ///
Future addAnimationComponent(ThermionEntity entity); Future addAnimationComponent();
/// ///
/// Removes an animation component from [entity]. /// Removes an animation component from [entity].
/// ///
Future removeAnimationComponent(ThermionEntity entity); Future removeAnimationComponent();
} }

View File

@@ -32,14 +32,7 @@ namespace thermion
typedef std::chrono::time_point<std::chrono::high_resolution_clock> time_point_t; typedef std::chrono::time_point<std::chrono::high_resolution_clock> time_point_t;
enum AnimationType struct Animation
{
MORPH,
BONE,
GLTF
};
struct AnimationStatus
{ {
time_point_t start = time_point_t::max(); time_point_t start = time_point_t::max();
float startOffset; float startOffset;
@@ -52,7 +45,7 @@ namespace thermion
/// The status of an animation embedded in a glTF object. /// 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. /// @param index refers to the index of the animation in the animations property of the underlying object.
/// ///
struct GltfAnimation : AnimationStatus struct GltfAnimation : Animation
{ {
int index = -1; int index = -1;
}; };
@@ -60,24 +53,18 @@ namespace thermion
// //
// The status of a morph target animation created dynamically at runtime (not glTF embedded). // The status of a morph target animation created dynamically at runtime (not glTF embedded).
// //
struct MorphAnimation : AnimationStatus struct MorphAnimation : Animation
{ {
utils::Entity meshTarget; int lengthInFrames;
int numFrames = -1;
float frameLengthInMs = 0; float frameLengthInMs = 0;
std::vector<float> frameData; std::vector<float> frameData;
std::vector<int> morphIndices; std::vector<int> morphIndices;
int lengthInFrames;
}; };
// struct BoneAnimation : Animation {
// The status of a skeletal animation created dynamically at runtime (not glTF embedded). int lengthInFrames;
//
struct BoneAnimation : AnimationStatus
{
size_t boneIndex; size_t boneIndex;
size_t skinIndex = 0; size_t skinIndex = 0;
int lengthInFrames;
float frameLengthInMs = 0; float frameLengthInMs = 0;
std::vector<math::mat4f> frameData; std::vector<math::mat4f> frameData;
float fadeOutInSecs = 0; float fadeOutInSecs = 0;
@@ -85,276 +72,86 @@ namespace thermion
float maxDelta = 1.0f; float maxDelta = 1.0f;
}; };
struct AnimationComponent /// @brief
///
///
struct BoneAnimationComponent
{ {
std::variant<FilamentInstance *, Entity> target; FilamentInstance * target;
std::vector<GltfAnimation> gltfAnimations; std::vector<BoneAnimation> animations;
std::vector<MorphAnimation> morphAnimations; };
std::vector<BoneAnimation> boneAnimations;
/// @brief
///
///
struct MorphAnimationComponent
{
std::vector<MorphAnimation> animations;
};
/// @brief
///
///
struct GltfAnimationComponent
{
FilamentInstance * target;
// the index of the last active glTF animation, // the index of the last active glTF animation,
// used to cross-fade // used to cross-fade
int fadeGltfAnimationIndex = -1; int fadeGltfAnimationIndex = -1;
float fadeDuration = 0.0f; float fadeDuration = 0.0f;
float fadeOutAnimationStart = 0.0f; float fadeOutAnimationStart = 0.0f;
std::vector<GltfAnimation> animations;
}; };
class AnimationComponentManager : public utils::SingleInstanceComponentManager<AnimationComponent>
{
filament::TransformManager &_transformManager; class GltfAnimationComponentManager : public utils::SingleInstanceComponentManager<GltfAnimationComponent> {
filament::RenderableManager &_renderableManager; public:
GltfAnimationComponentManager(
filament::TransformManager &transformManager,
filament::RenderableManager &renderableManager) :
mTransformManager(transformManager), mRenderableManager(renderableManager) {};
~GltfAnimationComponentManager() = default;
void addAnimationComponent(FilamentInstance *target);
void removeAnimationComponent(FilamentInstance *target);
void update();
public: private:
AnimationComponentManager( filament::TransformManager &mTransformManager;
filament::TransformManager &transformManager, filament::RenderableManager &mRenderableManager;
filament::RenderableManager &renderableManager) : _transformManager(transformManager),
_renderableManager(renderableManager){};
void addAnimationComponent(std::variant<FilamentInstance *, Entity> target)
{
AnimationComponent animationComponent;
animationComponent.target = target;
EntityInstanceBase::Type componentInstance;
if (std::holds_alternative<FilamentInstance *>(target))
{
auto instance = std::get<FilamentInstance *>(target);
if(!hasComponent(instance->getRoot())) {
componentInstance = addComponent(instance->getRoot());
this->elementAt<0>(componentInstance) = animationComponent;
}
}
else
{
auto entity = std::get<Entity>(target);
if(!hasComponent(entity)) {
componentInstance = addComponent(entity);
this->elementAt<0>(componentInstance) = animationComponent;
}
}
}
void removeAnimationComponent(std::variant<FilamentInstance *, Entity> target)
{
AnimationComponent animationComponent;
animationComponent.target = target;
EntityInstanceBase::Type componentInstance;
if (std::holds_alternative<FilamentInstance *>(target))
{
auto instance = std::get<FilamentInstance *>(target);
if(hasComponent(instance->getRoot())) {
removeComponent(instance->getRoot());
}
} else {
auto entity = std::get<Entity>(target);
if(hasComponent(entity)) {
removeComponent(entity);
}
}
}
void update()
{
for (auto it = begin(); it < end(); it++)
{
const auto &entity = getEntity(it);
auto componentInstance = getInstance(entity);
auto &animationComponent = elementAt<0>(componentInstance);
auto &morphAnimations = animationComponent.morphAnimations;
if (std::holds_alternative<FilamentInstance *>(animationComponent.target))
{
auto target = std::get<FilamentInstance *>(animationComponent.target);
auto animator = target->getAnimator();
auto &gltfAnimations = animationComponent.gltfAnimations;
auto &boneAnimations = animationComponent.boneAnimations;
if(gltfAnimations.size() > 0) {
for (int i = ((int)gltfAnimations.size()) - 1; i >= 0; i--)
{
auto now = high_resolution_clock::now();
auto animationStatus = animationComponent.gltfAnimations[i];
auto elapsedInSecs = animationStatus.startOffset + float(std::chrono::duration_cast<std::chrono::milliseconds>(now - animationStatus.start).count()) / 1000.0f;
if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs)
{
animator->applyAnimation(animationStatus.index, animationStatus.durationInSecs - 0.001);
animator->updateBoneMatrices();
gltfAnimations.erase(gltfAnimations.begin() + i);
animationComponent.fadeGltfAnimationIndex = -1;
continue;
}
animator->applyAnimation(animationStatus.index, elapsedInSecs);
if (animationComponent.fadeGltfAnimationIndex != -1 && elapsedInSecs < animationComponent.fadeDuration)
{
// cross-fade
auto fadeFromTime = animationComponent.fadeOutAnimationStart + elapsedInSecs;
auto alpha = elapsedInSecs / animationComponent.fadeDuration;
animator->applyCrossFade(animationComponent.fadeGltfAnimationIndex, fadeFromTime, alpha);
}
}
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--)
{
auto animationStatus = boneAnimations[i];
auto now = high_resolution_clock::now();
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))
{
if(!animationStatus.loop) {
boneAnimations.erase(boneAnimations.begin() + i);
continue;
}
}
// 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 = std::floor(elapsedInFrames);
int nextFrame = currFrame;
// offset from the end if reverse
if (animationStatus.reverse)
{
currFrame = animationStatus.lengthInFrames - currFrame;
if (currFrame > 0)
{
nextFrame = currFrame - 1;
}
else
{
nextFrame = 0;
}
}
else
{
if (currFrame < animationStatus.lengthInFrames - 1)
{
nextFrame = currFrame + 1;
}
else
{
nextFrame = currFrame;
}
}
currFrame = std::clamp(currFrame, 0, animationStatus.lengthInFrames - 1);
nextFrame = std::clamp(nextFrame, 0, animationStatus.lengthInFrames - 1);
float frameDelta = elapsedInFrames - currFrame;
// linearly interpolate this animation between its last/current frames
// 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];
// 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);
}
fadeDelta = std::clamp(fadeDelta, 0.0f, animationStatus.maxDelta);
auto jointTransform = _transformManager.getInstance(joint);
// linearly interpolate this animation between its current (interpolated) frame and the current transform (i.e. as set by the gltf frame)
// // if we are fading in or out, apply a delta
if (fadeDelta >= 0.0f && fadeDelta <= 1.0f) {
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, composeMatrix(newTranslation, newRotation, newScale));
animator->updateBoneMatrices();
if (animationStatus.loop && elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs))
{
animationStatus.start = now;
}
}
}
for (int i = (int)morphAnimations.size() - 1; i >= 0; i--)
{
auto now = high_resolution_clock::now();
auto animationStatus = morphAnimations[i];
auto elapsedInSecs = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - animationStatus.start).count()) / 1000.0f;
if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs)
{
morphAnimations.erase(morphAnimations.begin() + i);
continue;
}
int frameNumber = static_cast<int>(elapsedInSecs * 1000.0f / animationStatus.frameLengthInMs) % animationStatus.lengthInFrames;
// offset from the end if reverse
if (animationStatus.reverse)
{
frameNumber = animationStatus.lengthInFrames - frameNumber;
}
auto baseOffset = frameNumber * animationStatus.morphIndices.size();
for (int i = 0; i < animationStatus.morphIndices.size(); i++)
{
auto morphIndex = animationStatus.morphIndices[i];
// set the weights appropriately
_renderableManager.setMorphWeights(
_renderableManager.getInstance(animationStatus.meshTarget),
animationStatus.frameData.data() + baseOffset + i,
1,
morphIndex);
}
}
}
}
}; };
class BoneAnimationComponentManager : public utils::SingleInstanceComponentManager<BoneAnimationComponent> {
public:
BoneAnimationComponentManager(
filament::TransformManager &transformManager,
filament::RenderableManager &renderableManager) :
mTransformManager(transformManager), mRenderableManager(renderableManager) {};
~BoneAnimationComponentManager() {};
void addAnimationComponent(FilamentInstance *target);
void removeAnimationComponent(FilamentInstance *target);
void update();
private:
filament::TransformManager &mTransformManager;
filament::RenderableManager &mRenderableManager;
};
class MorphAnimationComponentManager : public utils::SingleInstanceComponentManager<MorphAnimationComponent> {
public:
MorphAnimationComponentManager(
filament::TransformManager &transformManager,
filament::RenderableManager &renderableManager) :
mTransformManager(transformManager), mRenderableManager(renderableManager) {};
~MorphAnimationComponentManager() {};
void addAnimationComponent(Entity entity);
void removeAnimationComponent(Entity entity);
void update();
private:
filament::TransformManager &mTransformManager;
filament::RenderableManager &mRenderableManager;
};
} }

View File

@@ -31,7 +31,7 @@ namespace thermion
AnimationManager( AnimationManager(
Engine *engine, Engine *engine,
Scene *scene); Scene *scene);
~AnimationManager(); ~AnimationManager() = default;
/// @brief /// @brief
/// ///
@@ -158,18 +158,39 @@ namespace thermion
float getGltfAnimationDuration(GltfSceneAssetInstance *instance, int animationIndex); float getGltfAnimationDuration(GltfSceneAssetInstance *instance, int animationIndex);
/// @brief /// @brief
/// @param entity /// @param instance
/// @return /// @return
bool addAnimationComponent(EntityId entity); bool addGltfAnimationComponent(GltfSceneAssetInstance *instance);
/// @brief /// @brief
/// @param entity /// @param instance
void removeAnimationComponent(EntityId entity); void removeGltfAnimationComponent(GltfSceneAssetInstance *instance);
/// @brief
/// @param instance
/// @return
bool addBoneAnimationComponent(GltfSceneAssetInstance *instance);
/// @brief
/// @param instance
void removeBoneAnimationComponent(GltfSceneAssetInstance *instance);
/// @brief
/// @param asset
/// @return
bool addMorphAnimationComponent(utils::Entity entity);
/// @brief
/// @param asset
void removeMorphAnimationComponent(utils::Entity entity);
private: private:
Engine *_engine = nullptr; Engine *_engine = nullptr;
Scene *_scene = nullptr; Scene *_scene = nullptr;
std::mutex _mutex; std::mutex _mutex;
std::unique_ptr<AnimationComponentManager> _animationComponentManager = std::nullptr_t(); std::unique_ptr<GltfAnimationComponentManager> _gltfAnimationComponentManager = std::nullptr_t();
std::unique_ptr<MorphAnimationComponentManager> _morphAnimationComponentManager = std::nullptr_t();
std::unique_ptr<BoneAnimationComponentManager> _boneAnimationComponentManager = std::nullptr_t();
}; };
} }

View File

@@ -4,6 +4,8 @@
#include "Log.hpp" #include "Log.hpp"
#include <utils/Entity.h>
#include "c_api/APIExport.h" #include "c_api/APIExport.h"
#include "scene/AnimationManager.hpp" #include "scene/AnimationManager.hpp"
@@ -26,15 +28,65 @@ extern "C"
animationManager->update(frameTimeInNanos); animationManager->update(frameTimeInNanos);
} }
EMSCRIPTEN_KEEPALIVE void AnimationManager_addAnimationComponent(TAnimationManager *tAnimationManager, EntityId entityId) EMSCRIPTEN_KEEPALIVE bool AnimationManager_addGltfAnimationComponent(TAnimationManager *tAnimationManager, TSceneAsset *tSceneAsset)
{ {
auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf || !sceneAsset->isInstance()) {
return false;
}
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
animationManager->addAnimationComponent(entityId);
animationManager->addGltfAnimationComponent(reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset));
return true;
} }
EMSCRIPTEN_KEEPALIVE void AnimationManager_removeAnimationComponent(TAnimationManager *tAnimationManager, EntityId entityId) EMSCRIPTEN_KEEPALIVE bool AnimationManager_removeGltfAnimationComponent(TAnimationManager *tAnimationManager, TSceneAsset *tSceneAsset)
{
auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf || !sceneAsset->isInstance()) {
return false;
}
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
animationManager->removeGltfAnimationComponent(reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset));
return true;
}
EMSCRIPTEN_KEEPALIVE bool AnimationManager_addBoneAnimationComponent(TAnimationManager *tAnimationManager, TSceneAsset *tSceneAsset)
{
auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf || !sceneAsset->isInstance()) {
return false;
}
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
animationManager->addBoneAnimationComponent(reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset));
return true;
}
EMSCRIPTEN_KEEPALIVE bool AnimationManager_removeBoneAnimationComponent(TAnimationManager *tAnimationManager, TSceneAsset *tSceneAsset)
{
auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf || !sceneAsset->isInstance()) {
return false;
}
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
animationManager->removeBoneAnimationComponent(reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset));
return true;
}
EMSCRIPTEN_KEEPALIVE void AnimationManager_addMorphAnimationComponent(TAnimationManager *tAnimationManager, EntityId entity)
{ {
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
animationManager->removeAnimationComponent(entityId); animationManager->addMorphAnimationComponent(utils::Entity::import(entity));
}
EMSCRIPTEN_KEEPALIVE void AnimationManager_removeMorphAnimationComponent(TAnimationManager *tAnimationManager, EntityId entity)
{
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
animationManager->removeMorphAnimationComponent(utils::Entity::import(entity));
} }
EMSCRIPTEN_KEEPALIVE bool AnimationManager_setMorphAnimation( EMSCRIPTEN_KEEPALIVE bool AnimationManager_setMorphAnimation(
@@ -66,26 +118,26 @@ extern "C"
EMSCRIPTEN_KEEPALIVE bool AnimationManager_clearMorphAnimation(TAnimationManager *tAnimationManager, EntityId entityId) EMSCRIPTEN_KEEPALIVE bool AnimationManager_clearMorphAnimation(TAnimationManager *tAnimationManager, EntityId entityId)
{ {
auto *animManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto *animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
auto entity = utils::Entity::import(entityId); auto entity = utils::Entity::import(entityId);
animManager->clearMorphAnimationBuffer(entity); animationManager->clearMorphAnimationBuffer(entity);
return true; return true;
} }
EMSCRIPTEN_KEEPALIVE void AnimationManager_resetToRestPose(TAnimationManager *tAnimationManager, TSceneAsset *sceneAsset) EMSCRIPTEN_KEEPALIVE void AnimationManager_resetToRestPose(TAnimationManager *tAnimationManager, TSceneAsset *sceneAsset)
{ {
auto *animManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto *animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
auto asset = reinterpret_cast<SceneAsset *>(sceneAsset); auto asset = reinterpret_cast<SceneAsset *>(sceneAsset);
if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance())
{ {
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset); auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset);
animManager->resetToRestPose(instance); animationManager->resetToRestPose(instance);
} }
} }
EMSCRIPTEN_KEEPALIVE void AnimationManager_addBoneAnimation( EMSCRIPTEN_KEEPALIVE bool AnimationManager_addBoneAnimation(
TAnimationManager *tAnimationManager, TAnimationManager *tAnimationManager,
TSceneAsset *sceneAsset, TSceneAsset *tSceneAsset,
int skinIndex, int skinIndex,
int boneIndex, int boneIndex,
const float *const frameData, const float *const frameData,
@@ -95,13 +147,24 @@ extern "C"
float fadeInInSecs, float fadeInInSecs,
float maxDelta) float maxDelta)
{ {
auto *animManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
auto asset = reinterpret_cast<SceneAsset *>(sceneAsset); if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) {
if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) return false;
{
animManager->addBoneAnimation(reinterpret_cast<GltfSceneAssetInstance *>(asset), skinIndex, boneIndex, frameData, numFrames, frameLengthInMs,
fadeOutInSecs, fadeInInSecs, maxDelta);
} }
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
GltfSceneAssetInstance *instance;
if (sceneAsset->isInstance())
{
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset);
} else {
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset->getInstanceAt(0));
}
animationManager->addBoneAnimationComponent(instance);
animationManager->addBoneAnimation(instance, skinIndex, boneIndex, frameData, numFrames, frameLengthInMs, fadeOutInSecs, fadeInInSecs, maxDelta);
return true;
} }
EMSCRIPTEN_KEEPALIVE EntityId AnimationManager_getBone( EMSCRIPTEN_KEEPALIVE EntityId AnimationManager_getBone(
@@ -110,11 +173,11 @@ extern "C"
int skinIndex, int skinIndex,
int boneIndex) int boneIndex)
{ {
auto *animManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto *animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
auto asset = reinterpret_cast<SceneAsset *>(sceneAsset); auto asset = reinterpret_cast<SceneAsset *>(sceneAsset);
if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance())
{ {
auto entities = animManager->getBoneEntities(reinterpret_cast<GltfSceneAssetInstance *>(asset), skinIndex); auto entities = animationManager->getBoneEntities(reinterpret_cast<GltfSceneAssetInstance *>(asset), skinIndex);
if (boneIndex < entities.size()) if (boneIndex < entities.size())
{ {
return utils::Entity::smuggle(entities[boneIndex]); return utils::Entity::smuggle(entities[boneIndex]);
@@ -131,12 +194,12 @@ extern "C"
float *const out, float *const out,
int numBones) int numBones)
{ {
auto *animManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto *animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
auto asset = reinterpret_cast<SceneAsset *>(sceneAsset); auto asset = reinterpret_cast<SceneAsset *>(sceneAsset);
if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance())
{ {
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset); auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset);
const auto transforms = animManager->getBoneRestTranforms(instance, skinIndex); const auto transforms = animationManager->getBoneRestTranforms(instance, skinIndex);
auto numTransforms = transforms.size(); auto numTransforms = transforms.size();
if (numTransforms != numBones) if (numTransforms != numBones)
{ {
@@ -164,12 +227,12 @@ extern "C"
int boneIndex, int boneIndex,
float *const out) float *const out)
{ {
auto *animManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto *animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
auto asset = reinterpret_cast<SceneAsset *>(sceneAsset); auto asset = reinterpret_cast<SceneAsset *>(sceneAsset);
if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance())
{ {
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset); auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset);
auto transform = animManager->getInverseBindMatrix(instance, skinIndex, boneIndex); auto transform = animationManager->getInverseBindMatrix(instance, skinIndex, boneIndex);
for (int colNum = 0; colNum < 4; colNum++) for (int colNum = 0; colNum < 4; colNum++)
{ {
for (int rowNum = 0; rowNum < 4; rowNum++) for (int rowNum = 0; rowNum < 4; rowNum++)
@@ -180,9 +243,9 @@ extern "C"
} }
} }
EMSCRIPTEN_KEEPALIVE void AnimationManager_playAnimation( EMSCRIPTEN_KEEPALIVE bool AnimationManager_playGltfAnimation(
TAnimationManager *tAnimationManager, TAnimationManager *tAnimationManager,
TSceneAsset *sceneAsset, TSceneAsset *tSceneAsset,
int index, int index,
bool loop, bool loop,
bool reverse, bool reverse,
@@ -190,29 +253,48 @@ extern "C"
float crossfade, float crossfade,
float startOffset) float startOffset)
{ {
auto *animManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
auto asset = reinterpret_cast<SceneAsset *>(sceneAsset);
if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) {
{ return false;
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset);
animManager->playGltfAnimation(instance, index, loop, reverse, replaceActive, crossfade, startOffset);
} }
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
GltfSceneAssetInstance *instance;
if (sceneAsset->isInstance())
{
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset);
} else {
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset->getInstanceAt(0));
}
animationManager->addGltfAnimationComponent(instance);
animationManager->playGltfAnimation(instance, index, loop, reverse, replaceActive, crossfade, startOffset);
return true;
} }
EMSCRIPTEN_KEEPALIVE void AnimationManager_stopAnimation( EMSCRIPTEN_KEEPALIVE bool AnimationManager_stopGltfAnimation(
TAnimationManager *tAnimationManager, TAnimationManager *tAnimationManager,
TSceneAsset *sceneAsset, TSceneAsset *tSceneAsset,
int index) int index)
{ {
auto *animManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
auto asset = reinterpret_cast<SceneAsset *>(sceneAsset); if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) {
if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) return false;
{
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset);
animManager->stopGltfAnimation(instance, index);
} }
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
GltfSceneAssetInstance *instance;
if (sceneAsset->isInstance())
{
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset);
} else {
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset->getInstanceAt(0));
}
animationManager->stopGltfAnimation(instance, index);
return true;
} }
EMSCRIPTEN_KEEPALIVE void AnimationManager_setGltfAnimationFrame( EMSCRIPTEN_KEEPALIVE void AnimationManager_setGltfAnimationFrame(
@@ -221,41 +303,79 @@ extern "C"
int animationIndex, int animationIndex,
int frame) int frame)
{ {
auto *animManager = reinterpret_cast<AnimationManager *>(tAnimationManager); auto *animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
auto asset = reinterpret_cast<SceneAsset *>(tSceneAsset); auto asset = reinterpret_cast<SceneAsset *>(tSceneAsset);
if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance()) if (asset->getType() == SceneAsset::SceneAssetType::Gltf && asset->isInstance())
{ {
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset); auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(asset);
animManager->setGltfAnimationFrame(instance, animationIndex, frame); animationManager->setGltfAnimationFrame(instance, animationIndex, frame);
} }
} }
EMSCRIPTEN_KEEPALIVE float AnimationManager_getAnimationDuration( EMSCRIPTEN_KEEPALIVE float AnimationManager_getGltfAnimationDuration(
TAnimationManager *tAnimationManager, TAnimationManager *tAnimationManager,
TSceneAsset *sceneAsset, TSceneAsset *tSceneAsset,
int animationIndex) int animationIndex)
{ {
auto instance = ((GltfSceneAssetInstance *)sceneAsset); auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
return ((AnimationManager *)tAnimationManager)->getGltfAnimationDuration(instance, animationIndex);
if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) {
return false;
}
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
GltfSceneAssetInstance *instance;
if (sceneAsset->isInstance())
{
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset);
} else {
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset->getInstanceAt(0));
}
return animationManager->getGltfAnimationDuration(instance, animationIndex);
} }
EMSCRIPTEN_KEEPALIVE int AnimationManager_getAnimationCount( EMSCRIPTEN_KEEPALIVE int AnimationManager_getGltfAnimationCount(
TAnimationManager *tAnimationManager, TAnimationManager *tAnimationManager,
TSceneAsset *sceneAsset) TSceneAsset *tSceneAsset)
{ {
auto instance = ((GltfSceneAssetInstance *)sceneAsset);
auto names = ((AnimationManager *)tAnimationManager)->getGltfAnimationNames(instance); auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) {
return -1;
}
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
GltfSceneAssetInstance *instance;
if(sceneAsset->isInstance()) {
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset);
} else {
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset->getInstanceAt(0));
}
auto names = animationManager->getGltfAnimationNames(instance);
TRACE("Animation count : %d", names.size());
return (int)names.size(); return (int)names.size();
} }
EMSCRIPTEN_KEEPALIVE void AnimationManager_getAnimationName( EMSCRIPTEN_KEEPALIVE void AnimationManager_getGltfAnimationName(
TAnimationManager *tAnimationManager, TAnimationManager *tAnimationManager,
TSceneAsset *sceneAsset, TSceneAsset *tSceneAsset,
char *const outPtr, char *const outPtr,
int index) int index)
{ {
auto instance = ((GltfSceneAssetInstance *)sceneAsset); auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
auto names = ((AnimationManager *)tAnimationManager)->getGltfAnimationNames(instance); if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) {
strcpy(outPtr, "FILAMENT_ERROR_NOT_FOUND");
return;
}
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
GltfSceneAssetInstance *instance;
if(sceneAsset->isInstance()) {
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset);
} else {
instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset->getInstanceAt(0));
}
auto names = animationManager->getGltfAnimationNames(instance);
std::string name = names[index]; std::string name = names[index];
strcpy(outPtr, name.c_str()); strcpy(outPtr, name.c_str());
} }
@@ -278,8 +398,7 @@ extern "C"
{ {
auto instance = ((GltfSceneAssetInstance *)sceneAsset); auto instance = ((GltfSceneAssetInstance *)sceneAsset);
auto entities = ((AnimationManager *)tAnimationManager)->getBoneEntities(instance, skinIndex); auto entities = ((AnimationManager *)tAnimationManager)->getBoneEntities(instance, skinIndex);
// Note: This needs implementation of a method to get bone names from entities
// Current source doesn't show how bone names are retrieved
} }
EMSCRIPTEN_KEEPALIVE bool AnimationManager_updateBoneMatrices( EMSCRIPTEN_KEEPALIVE bool AnimationManager_updateBoneMatrices(
@@ -293,11 +412,27 @@ extern "C"
EMSCRIPTEN_KEEPALIVE int AnimationManager_getMorphTargetNameCount( EMSCRIPTEN_KEEPALIVE int AnimationManager_getMorphTargetNameCount(
TAnimationManager *tAnimationManager, TAnimationManager *tAnimationManager,
TSceneAsset *sceneAsset, TSceneAsset *tSceneAsset,
EntityId childEntity) EntityId childEntity)
{ {
auto asset = ((GltfSceneAsset *)sceneAsset); auto sceneAsset = reinterpret_cast<SceneAsset *>(tSceneAsset);
auto names = ((AnimationManager *)tAnimationManager)->getMorphTargetNames(asset, childEntity);
if(sceneAsset->getType() != SceneAsset::SceneAssetType::Gltf) {
return -1;
}
auto animationManager = reinterpret_cast<AnimationManager *>(tAnimationManager);
GltfSceneAsset *gltfAsset;
if (sceneAsset->isInstance())
{
auto *instance = reinterpret_cast<GltfSceneAssetInstance *>(sceneAsset);
gltfAsset = reinterpret_cast<GltfSceneAsset *>(instance->getInstanceOwner());
} else {
gltfAsset = reinterpret_cast<GltfSceneAsset *>(sceneAsset);
}
auto names = animationManager->getMorphTargetNames(gltfAsset, childEntity);
return (int)names.size(); return (int)names.size();
} }

View File

@@ -0,0 +1,154 @@
#include <chrono>
#include <variant>
#include "components/AnimationComponentManager.hpp"
#include "Log.hpp"
namespace thermion
{
void BoneAnimationComponentManager::addAnimationComponent(FilamentInstance *target) {
if(!hasComponent(target->getRoot())) {
EntityInstanceBase::Type componentInstance = addComponent(target->getRoot());
this->elementAt<0>(componentInstance) = { target };
}
}
void BoneAnimationComponentManager::removeAnimationComponent(FilamentInstance *target) {
if(hasComponent(target->getRoot())) {
removeComponent(target->getRoot());
}
}
void BoneAnimationComponentManager::update() {
TRACE("Updating with %d components", getComponentCount());
for (auto it = begin(); it < end(); it++)
{
const auto &entity = getEntity(it);
auto componentInstance = getInstance(entity);
auto &animationComponent = elementAt<0>(componentInstance);
auto &boneAnimations = animationComponent.animations;
auto target = animationComponent.target;
auto animator = target->getAnimator();
///
/// 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--)
{
auto animationStatus = boneAnimations[i];
auto now = high_resolution_clock::now();
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))
{
if(!animationStatus.loop) {
boneAnimations.erase(boneAnimations.begin() + i);
continue;
}
}
// 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 = std::floor(elapsedInFrames);
int nextFrame = currFrame;
// offset from the end if reverse
if (animationStatus.reverse)
{
currFrame = animationStatus.lengthInFrames - currFrame;
if (currFrame > 0)
{
nextFrame = currFrame - 1;
}
else
{
nextFrame = 0;
}
}
else
{
if (currFrame < animationStatus.lengthInFrames - 1)
{
nextFrame = currFrame + 1;
}
else
{
nextFrame = currFrame;
}
}
currFrame = std::clamp(currFrame, 0, animationStatus.lengthInFrames - 1);
nextFrame = std::clamp(nextFrame, 0, animationStatus.lengthInFrames - 1);
float frameDelta = elapsedInFrames - currFrame;
// linearly interpolate this animation between its last/current frames
// 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];
// 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);
}
fadeDelta = std::clamp(fadeDelta, 0.0f, animationStatus.maxDelta);
auto jointTransform = mTransformManager.getInstance(joint);
// linearly interpolate this animation between its current (interpolated) frame and the current transform (i.e. as set by the gltf frame)
// // if we are fading in or out, apply a delta
if (fadeDelta >= 0.0f && fadeDelta <= 1.0f) {
math::float3 fadeScale;
math::quatf fadeRotation;
math::float3 fadeTranslation;
auto currentTransform = mTransformManager.getTransform(jointTransform);
decomposeMatrix(currentTransform, &fadeTranslation, &fadeRotation, &fadeScale);
newScale = mix(fadeScale, newScale, fadeDelta);
newRotation = slerp(fadeRotation, newRotation, fadeDelta);
newTranslation = mix(fadeTranslation, newTranslation, fadeDelta);
}
mTransformManager.setTransform(jointTransform, composeMatrix(newTranslation, newRotation, newScale));
animator->updateBoneMatrices();
if (animationStatus.loop && elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs))
{
animationStatus.start = now;
}
}
}
}
}

View File

@@ -0,0 +1,308 @@
#include <chrono>
#include <variant>
#include "components/AnimationComponentManager.hpp"
#include "Log.hpp"
namespace thermion
{
void GltfAnimationComponentManager::addAnimationComponent(FilamentInstance *target) {
if(!hasComponent(target->getRoot())) {
EntityInstanceBase::Type componentInstance = addComponent(target->getRoot());
this->elementAt<0>(componentInstance) = { target };
}
}
void GltfAnimationComponentManager::removeAnimationComponent(FilamentInstance *target) {
if(hasComponent(target->getRoot())) {
removeComponent(target->getRoot());
}
}
void GltfAnimationComponentManager::update() {
TRACE("Updating with %d components", getComponentCount());
for (auto it = begin(); it < end(); it++)
{
const auto &entity = getEntity(it);
auto componentInstance = getInstance(entity);
auto &animationComponent = elementAt<0>(componentInstance);
auto target = animationComponent.target;
auto animator = target->getAnimator();
auto &gltfAnimations = animationComponent.animations;
for (int i = ((int)gltfAnimations.size()) - 1; i >= 0; i--)
{
auto now = high_resolution_clock::now();
auto animationStatus = gltfAnimations[i];
auto elapsedInSecs = animationStatus.startOffset + float(std::chrono::duration_cast<std::chrono::milliseconds>(now - animationStatus.start).count()) / 1000.0f;
if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs)
{
animator->applyAnimation(animationStatus.index, animationStatus.durationInSecs - 0.001);
animator->updateBoneMatrices();
gltfAnimations.erase(gltfAnimations.begin() + i);
animationComponent.fadeGltfAnimationIndex = -1;
continue;
}
animator->applyAnimation(animationStatus.index, elapsedInSecs);
if (animationComponent.fadeGltfAnimationIndex != -1 && elapsedInSecs < animationComponent.fadeDuration)
{
// cross-fade
auto fadeFromTime = animationComponent.fadeOutAnimationStart + elapsedInSecs;
auto alpha = elapsedInSecs / animationComponent.fadeDuration;
animator->applyCrossFade(animationComponent.fadeGltfAnimationIndex, fadeFromTime, alpha);
}
}
animator->updateBoneMatrices();
}
}
}
// void AnimationComponentManager::addGltfAnimationComponent(FilamentInstance *target) {
// if(!hasComponent(target->getRoot())) {
// EntityInstanceBase::Type componentInstance;
// componentInstance = addComponent(instance->getRoot());
// this->elementAt<0>(componentInstance) = GltfAnimationComponent animationComponent { target };
// }
// }
// // }
// // else
// // {
// // auto entity = std::get<Entity>(target);
// // if(!hasComponent(entity)) {
// // componentInstance = addComponent(entity);
// // this->elementAt<0>(componentInstance) = animationComponent;
// // }
// // }
// // }
// void AnimationComponentManager::removeAnimationComponent(std::variant<FilamentInstance *, Entity> target)
// {
// AnimationComponent animationComponent;
// animationComponent.target = target;
// EntityInstanceBase::Type componentInstance;
// if (std::holds_alternative<FilamentInstance *>(target))
// {
// auto instance = std::get<FilamentInstance *>(target);
// if(hasComponent(instance->getRoot())) {
// removeComponent(instance->getRoot());
// }
// } else {
// auto entity = std::get<Entity>(target);
// if(hasComponent(entity)) {
// removeComponent(entity);
// }
// }
// }
// void AnimationComponentManager::update()
// {
// for (auto it = begin(); it < end(); it++)
// {
// const auto &entity = getEntity(it);
// auto componentInstance = getInstance(entity);
// auto &animationComponent = elementAt<0>(componentInstance);
// auto &morphAnimations = animationComponent.morphAnimations;
// if (std::holds_alternative<FilamentInstance *>(animationComponent.target))
// {
// auto target = std::get<FilamentInstance *>(animationComponent.target);
// auto animator = target->getAnimator();
// auto &gltfAnimations = animationComponent.gltfAnimations;
// auto &boneAnimations = animationComponent.boneAnimations;
// if(gltfAnimations.size() > 0) {
// for (int i = ((int)gltfAnimations.size()) - 1; i >= 0; i--)
// {
// auto now = high_resolution_clock::now();
// auto animationStatus = animationComponent.gltfAnimations[i];
// auto elapsedInSecs = animationStatus.startOffset + float(std::chrono::duration_cast<std::chrono::milliseconds>(now - animationStatus.start).count()) / 1000.0f;
// if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs)
// {
// animator->applyAnimation(animationStatus.index, animationStatus.durationInSecs - 0.001);
// animator->updateBoneMatrices();
// gltfAnimations.erase(gltfAnimations.begin() + i);
// animationComponent.fadeGltfAnimationIndex = -1;
// continue;
// }
// animator->applyAnimation(animationStatus.index, elapsedInSecs);
// if (animationComponent.fadeGltfAnimationIndex != -1 && elapsedInSecs < animationComponent.fadeDuration)
// {
// // cross-fade
// auto fadeFromTime = animationComponent.fadeOutAnimationStart + elapsedInSecs;
// auto alpha = elapsedInSecs / animationComponent.fadeDuration;
// animator->applyCrossFade(animationComponent.fadeGltfAnimationIndex, fadeFromTime, alpha);
// }
// }
// 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--)
// {
// auto animationStatus = boneAnimations[i];
// auto now = high_resolution_clock::now();
// 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))
// {
// if(!animationStatus.loop) {
// boneAnimations.erase(boneAnimations.begin() + i);
// continue;
// }
// }
// // 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 = std::floor(elapsedInFrames);
// int nextFrame = currFrame;
// // offset from the end if reverse
// if (animationStatus.reverse)
// {
// currFrame = animationStatus.lengthInFrames - currFrame;
// if (currFrame > 0)
// {
// nextFrame = currFrame - 1;
// }
// else
// {
// nextFrame = 0;
// }
// }
// else
// {
// if (currFrame < animationStatus.lengthInFrames - 1)
// {
// nextFrame = currFrame + 1;
// }
// else
// {
// nextFrame = currFrame;
// }
// }
// currFrame = std::clamp(currFrame, 0, animationStatus.lengthInFrames - 1);
// nextFrame = std::clamp(nextFrame, 0, animationStatus.lengthInFrames - 1);
// float frameDelta = elapsedInFrames - currFrame;
// // linearly interpolate this animation between its last/current frames
// // 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];
// // 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);
// }
// fadeDelta = std::clamp(fadeDelta, 0.0f, animationStatus.maxDelta);
// auto jointTransform = _transformManager.getInstance(joint);
// // linearly interpolate this animation between its current (interpolated) frame and the current transform (i.e. as set by the gltf frame)
// // // if we are fading in or out, apply a delta
// if (fadeDelta >= 0.0f && fadeDelta <= 1.0f) {
// 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, composeMatrix(newTranslation, newRotation, newScale));
// animator->updateBoneMatrices();
// if (animationStatus.loop && elapsedInSecs >= (animationStatus.durationInSecs + animationStatus.fadeInInSecs + animationStatus.fadeOutInSecs))
// {
// animationStatus.start = now;
// }
// }
// }
// for (int i = (int)morphAnimations.size() - 1; i >= 0; i--)
// {
// auto now = high_resolution_clock::now();
// auto animationStatus = morphAnimations[i];
// auto elapsedInSecs = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - animationStatus.start).count()) / 1000.0f;
// if (!animationStatus.loop && elapsedInSecs >= animationStatus.durationInSecs)
// {
// morphAnimations.erase(morphAnimations.begin() + i);
// continue;
// }
// int frameNumber = static_cast<int>(elapsedInSecs * 1000.0f / animationStatus.frameLengthInMs) % animationStatus.lengthInFrames;
// // offset from the end if reverse
// if (animationStatus.reverse)
// {
// frameNumber = animationStatus.lengthInFrames - frameNumber;
// }
// auto baseOffset = frameNumber * animationStatus.morphIndices.size();
// for (int i = 0; i < animationStatus.morphIndices.size(); i++)
// {
// auto morphIndex = animationStatus.morphIndices[i];
// // set the weights appropriately
// _renderableManager.setMorphWeights(
// _renderableManager.getInstance(animationStatus.meshTarget),
// animationStatus.frameData.data() + baseOffset + i,
// 1,
// morphIndex);
// }
// }
// }
// };
// }

View File

@@ -0,0 +1,76 @@
#include <chrono>
#include <variant>
#include "components/AnimationComponentManager.hpp"
#include "Log.hpp"
namespace thermion
{
void MorphAnimationComponentManager::addAnimationComponent(utils::Entity target) {
if(!hasComponent(target)) {
EntityInstanceBase::Type componentInstance = addComponent(target);
this->elementAt<0>(componentInstance) = MorphAnimationComponent { };
}
}
void MorphAnimationComponentManager::removeAnimationComponent(utils::Entity target) {
if(hasComponent(target)) {
removeComponent(target);
}
}
void MorphAnimationComponentManager::update() {
TRACE("Updating %d morph animation components", getComponentCount());
for (auto it = begin(); it < end(); it++)
{
const auto &entity = getEntity(it);
auto componentInstance = getInstance(entity);
auto &animationComponent = elementAt<0>(componentInstance);
auto &animations = animationComponent.animations;
TRACE("Component has %d animations", animations.size());
for (int i = (int)animations.size() - 1; i >= 0; i--)
{
auto now = high_resolution_clock::now();
auto &animation = animationComponent.animations[i];
auto elapsedInSecs = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - animation.start).count()) / 1000.0f;
if (!animation.loop && elapsedInSecs >= animation.durationInSecs)
{
animations.erase(animations.begin() + i);
TRACE("Animation %d completed", i);
continue;
}
int frameNumber = static_cast<int>(elapsedInSecs * 1000.0f / animation.frameLengthInMs) % animation.lengthInFrames;
// offset from the end if reverse
if (animation.reverse)
{
frameNumber = animation.lengthInFrames - frameNumber;
}
auto baseOffset = frameNumber * animation.morphIndices.size();
for (int i = 0; i < animation.morphIndices.size(); i++)
{
auto morphIndex = animation.morphIndices[i];
auto renderableInstance = mRenderableManager.getInstance(entity);
mRenderableManager.setMorphWeights(
renderableInstance,
animation.frameData.data() + baseOffset + i,
1,
morphIndex);
}
}
}
}
}

View File

@@ -11,6 +11,8 @@
#include "Log.hpp" #include "Log.hpp"
#include "components/AnimationComponentManager.hpp"
#include "components/AnimationComponentManager.hpp"
#include "scene/AnimationManager.hpp" #include "scene/AnimationManager.hpp"
#include "scene/SceneAsset.hpp" #include "scene/SceneAsset.hpp"
#include "scene/GltfSceneAssetInstance.hpp" #include "scene/GltfSceneAssetInstance.hpp"
@@ -25,12 +27,9 @@ namespace thermion
{ {
auto &transformManager = _engine->getTransformManager(); auto &transformManager = _engine->getTransformManager();
auto &renderableManager = _engine->getRenderableManager(); auto &renderableManager = _engine->getRenderableManager();
_animationComponentManager = std::make_unique<AnimationComponentManager>(transformManager, renderableManager); _gltfAnimationComponentManager = std::make_unique<GltfAnimationComponentManager>(transformManager, renderableManager);
} _morphAnimationComponentManager = std::make_unique<MorphAnimationComponentManager>(transformManager, renderableManager);
_boneAnimationComponentManager = std::make_unique<BoneAnimationComponentManager>(transformManager, renderableManager);
AnimationManager::~AnimationManager()
{
_animationComponentManager = std::nullptr_t();
} }
bool AnimationManager::setMorphAnimationBuffer( bool AnimationManager::setMorphAnimationBuffer(
@@ -41,16 +40,20 @@ namespace thermion
int numFrames, int numFrames,
float frameLengthInMs) float frameLengthInMs)
{ {
std::lock_guard lock(_mutex); std::lock_guard lock(_mutex);
if (!_animationComponentManager->hasComponent(entity)) if (!_morphAnimationComponentManager->hasComponent(entity))
{ {
_animationComponentManager->addAnimationComponent(entity); _morphAnimationComponentManager->addAnimationComponent(entity);
} }
auto animationComponentInstance = _morphAnimationComponentManager->getInstance(entity);
auto &animationComponent = _morphAnimationComponentManager->elementAt<0>(animationComponentInstance);
auto &morphAnimations = animationComponent.animations;
MorphAnimation morphAnimation; MorphAnimation morphAnimation;
morphAnimation.meshTarget = entity;
morphAnimation.frameData.clear(); morphAnimation.frameData.clear();
morphAnimation.frameData.insert( morphAnimation.frameData.insert(
morphAnimation.frameData.begin(), morphAnimation.frameData.begin(),
@@ -65,15 +68,12 @@ namespace thermion
morphAnimation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f; morphAnimation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
morphAnimation.start = high_resolution_clock::now(); morphAnimation.start = high_resolution_clock::now();
morphAnimation.lengthInFrames = static_cast<int>( morphAnimation.lengthInFrames = numFrames;
morphAnimation.durationInSecs * 1000.0f /
frameLengthInMs);
auto animationComponentInstance = _animationComponentManager->getInstance(entity);
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance);
auto &morphAnimations = animationComponent.morphAnimations;
morphAnimations.emplace_back(morphAnimation); morphAnimations.emplace_back(morphAnimation);
auto& foo = morphAnimations[morphAnimations.size() - 1];
return true; return true;
} }
@@ -82,9 +82,9 @@ namespace thermion
{ {
std::lock_guard lock(_mutex); std::lock_guard lock(_mutex);
auto animationComponentInstance = _animationComponentManager->getInstance(entity); auto animationComponentInstance = _morphAnimationComponentManager->getInstance(entity);
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance); auto &animationComponent = _morphAnimationComponentManager->elementAt<0>(animationComponentInstance);
auto &morphAnimations = animationComponent.morphAnimations; auto &morphAnimations = animationComponent.animations;
morphAnimations.clear(); morphAnimations.clear();
} }
@@ -309,17 +309,17 @@ namespace thermion
animation.fadeInInSecs = fadeInInSecs; animation.fadeInInSecs = fadeInInSecs;
animation.maxDelta = maxDelta; animation.maxDelta = maxDelta;
animation.skinIndex = skinIndex; animation.skinIndex = skinIndex;
if (!_animationComponentManager->hasComponent(instance->getInstance()->getRoot())) if (!_boneAnimationComponentManager->hasComponent(instance->getInstance()->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).");
return false; return false;
} }
auto animationComponentInstance = _animationComponentManager->getInstance(instance->getInstance()->getRoot()); auto animationComponentInstance = _boneAnimationComponentManager->getInstance(instance->getInstance()->getRoot());
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance); auto &animationComponent = _boneAnimationComponentManager->elementAt<0>(animationComponentInstance);
auto &boneAnimations = animationComponent.boneAnimations; // auto &boneAnimations = animationComponent.boneAnimations;
boneAnimations.emplace_back(animation); // boneAnimations.emplace_back(animation);
return true; return true;
} }
@@ -334,27 +334,30 @@ namespace thermion
return; return;
} }
if (!_animationComponentManager->hasComponent(instance->getEntity())) if (!_gltfAnimationComponentManager->hasComponent(instance->getEntity()))
{ {
_gltfAnimationComponentManager->addComponent(instance->getEntity());
Log("ERROR: specified entity is not animatable (has no animation component attached)."); Log("ERROR: specified entity is not animatable (has no animation component attached).");
return; return;
} }
auto animationComponentInstance = _animationComponentManager->getInstance(instance->getEntity()); auto animationComponentInstance = _gltfAnimationComponentManager->getInstance(instance->getEntity());
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance); auto &animationComponent = _gltfAnimationComponentManager->elementAt<0>(animationComponentInstance);
animationComponent.target = instance->getInstance();
if (replaceActive) if (replaceActive)
{ {
if (animationComponent.gltfAnimations.size() > 0) if (animationComponent.animations.size() > 0)
{ {
auto &last = animationComponent.gltfAnimations.back(); auto &last = animationComponent.animations.back();
animationComponent.fadeGltfAnimationIndex = last.index; animationComponent.fadeGltfAnimationIndex = last.index;
animationComponent.fadeDuration = crossfade; animationComponent.fadeDuration = crossfade;
auto now = high_resolution_clock::now(); auto now = high_resolution_clock::now();
auto elapsedInSecs = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - last.start).count()) / 1000.0f; auto elapsedInSecs = float(std::chrono::duration_cast<std::chrono::milliseconds>(now - last.start).count()) / 1000.0f;
animationComponent.fadeOutAnimationStart = elapsedInSecs; animationComponent.fadeOutAnimationStart = elapsedInSecs;
animationComponent.gltfAnimations.clear(); animationComponent.animations.clear();
} }
else else
{ {
@@ -384,9 +387,9 @@ namespace thermion
bool found = false; bool found = false;
// don't play the animation if it's already running // don't play the animation if it's already running
for (int i = 0; i < animationComponent.gltfAnimations.size(); i++) for (int i = 0; i < animationComponent.animations.size(); i++)
{ {
if (animationComponent.gltfAnimations[i].index == index) if (animationComponent.animations[i].index == index)
{ {
found = true; found = true;
break; break;
@@ -394,22 +397,22 @@ namespace thermion
} }
if (!found) if (!found)
{ {
animationComponent.gltfAnimations.push_back(animation); animationComponent.animations.push_back(animation);
} }
} }
void AnimationManager::stopGltfAnimation(GltfSceneAssetInstance *instance, int index) void AnimationManager::stopGltfAnimation(GltfSceneAssetInstance *instance, int index)
{ {
auto animationComponentInstance = _animationComponentManager->getInstance(instance->getEntity()); auto animationComponentInstance = _gltfAnimationComponentManager->getInstance(instance->getEntity());
auto &animationComponent = _animationComponentManager->elementAt<0>(animationComponentInstance); auto &animationComponent = _gltfAnimationComponentManager->elementAt<0>(animationComponentInstance);
auto erased = std::remove_if(animationComponent.gltfAnimations.begin(), auto erased = std::remove_if(animationComponent.animations.begin(),
animationComponent.gltfAnimations.end(), animationComponent.animations.end(),
[=](GltfAnimation &anim) [=](GltfAnimation &anim)
{ return anim.index == index; }); { return anim.index == index; });
animationComponent.gltfAnimations.erase(erased, animationComponent.animations.erase(erased,
animationComponent.gltfAnimations.end()); animationComponent.animations.end());
return; return;
} }
@@ -417,6 +420,7 @@ namespace thermion
{ {
RenderableManager &rm = _engine->getRenderableManager(); RenderableManager &rm = _engine->getRenderableManager();
auto renderableInstance = rm.getInstance(entity); auto renderableInstance = rm.getInstance(entity);
rm.setMorphWeights( rm.setMorphWeights(
renderableInstance, renderableInstance,
weights, weights,
@@ -477,7 +481,9 @@ namespace thermion
void AnimationManager::update(uint64_t frameTimeInNanos) void AnimationManager::update(uint64_t frameTimeInNanos)
{ {
std::lock_guard lock(_mutex); std::lock_guard lock(_mutex);
_animationComponentManager->update(); _gltfAnimationComponentManager->update();
_morphAnimationComponentManager->update();
_boneAnimationComponentManager->update();
} }
math::mat4f AnimationManager::getInverseBindMatrix(GltfSceneAssetInstance *instance, int skinIndex, int boneIndex) math::mat4f AnimationManager::getInverseBindMatrix(GltfSceneAssetInstance *instance, int skinIndex, int boneIndex)
@@ -507,15 +513,37 @@ namespace thermion
return true; return true;
} }
bool AnimationManager::addAnimationComponent(EntityId entity) bool AnimationManager::addGltfAnimationComponent(GltfSceneAssetInstance *instance)
{ {
_animationComponentManager->addAnimationComponent(utils::Entity::import(entity)); _gltfAnimationComponentManager->addAnimationComponent(instance->getInstance());
return true; return true;
} }
void AnimationManager::removeAnimationComponent(EntityId entity) void AnimationManager::removeGltfAnimationComponent(GltfSceneAssetInstance *instance)
{ {
_animationComponentManager->removeComponent(utils::Entity::import(entity)); _gltfAnimationComponentManager->removeAnimationComponent(instance->getInstance());
}
bool AnimationManager::addBoneAnimationComponent(GltfSceneAssetInstance *instance)
{
_boneAnimationComponentManager->addAnimationComponent(instance->getInstance());
return true;
}
void AnimationManager::removeBoneAnimationComponent(GltfSceneAssetInstance *instance)
{
_boneAnimationComponentManager->removeAnimationComponent(instance->getInstance());
}
bool AnimationManager::addMorphAnimationComponent(utils::Entity entity)
{
_morphAnimationComponentManager->addAnimationComponent(entity);
return true;
}
void AnimationManager::removeMorphAnimationComponent(utils::Entity entity)
{
_morphAnimationComponentManager->removeAnimationComponent(entity);
} }
} }

View File

@@ -3,45 +3,87 @@ import 'dart:typed_data';
import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:thermion_dart/src/bindings/bindings.dart'; import 'package:thermion_dart/src/bindings/bindings.dart';
import 'package:thermion_dart/src/filament/src/implementation/ffi_asset.dart';
import 'package:thermion_dart/src/viewer/src/ffi/src/thermion_viewer_ffi.dart';
import 'package:thermion_dart/thermion_dart.dart'; import 'package:thermion_dart/thermion_dart.dart';
import 'helpers.dart'; import 'helpers.dart';
void main() async { void main() async {
final testHelper = TestHelper("animation"); final testHelper = TestHelper("animation");
await testHelper.setup(); await testHelper.setup();
group('morph animation tests', () {
test('retrieve morph target names', () async {
await testHelper.withViewer((viewer) async {
final cube = await viewer.loadGltf(
"${testHelper.testDir}/assets/cube_with_morph_targets.glb");
final childEntities = await cube.getChildEntities();
var morphTargets =
await cube.getMorphTargetNames(entity: childEntities.first);
expect(morphTargets.length, 1);
expect(morphTargets.first, "Key 1");
});
});
test('set morph target weights', () async { test('get morph target names', () async {
await testHelper.withViewer((viewer) async { await testHelper.withViewer((viewer) async {
final cube = await viewer.loadGltf( var cube = await viewer.loadGltf("${testHelper.testDir}/assets/cube.glb");
"${testHelper.testDir}/assets/cube_with_morph_targets.glb"); var morphTargets = await cube.getMorphTargetNames();
print(await cube.getChildEntityNames()); expect(morphTargets.length, 0);
await viewer.addToScene(cube);
await testHelper.capture(viewer.view, "cube_no_morph");
final childEntities = await cube.getChildEntities(); var childEntities = await cube.getChildEntities();
var childEntity = childEntities.first;
var morphData = MorphAnimationData( morphTargets = await cube.getMorphTargetNames(entity: childEntity);
Float32List.fromList([1.0]), ["Key 1"], expect(morphTargets.length, 0);
frameLengthInMs: 1000.0 / 60.0);
await cube.addAnimationComponent(childEntities.first); cube = await viewer
await cube.setMorphAnimationData(morphData); .loadGltf("${testHelper.testDir}/assets/cube_with_morph_targets.glb");
await viewer.render(); morphTargets = await cube.getMorphTargetNames();
await testHelper.capture(viewer.view, "cube_with_morph"); expect(morphTargets.length, 0);
}, bg: kRed);
childEntities = await cube.getChildEntities();
morphTargets =
await cube.getMorphTargetNames(entity: childEntities.first);
expect(morphTargets.length, 1);
expect(morphTargets.first, "Key 1");
}); });
}); });
test('set morph target weights', () async {
await testHelper.withViewer((viewer) async {
final cube = await viewer.loadGltf(
"${testHelper.testDir}/assets/cube_with_morph_targets.glb");
await viewer.addToScene(cube);
await testHelper.capture(viewer.view, "cube_no_morph");
await cube.setMorphTargetWeights((await cube.getChildEntities()).first, [1.0]);
await testHelper.capture(viewer.view, "cube_with_morph");
}, bg:kRed, cameraPosition: Vector3(3, 2, 6));
});
test('set morph target animation', () async {
await testHelper.withViewer((viewer) async {
final cube = await viewer.loadGltf(
"${testHelper.testDir}/assets/cube_with_morph_targets.glb");
await viewer.addToScene(cube);
var morphData = MorphAnimationData(Float32List.fromList([1.0]), ["Key 1"],
frameLengthInMs: 1000.0 / 60.0);
await cube.setMorphAnimationData(morphData);
await viewer.render();
await testHelper.capture(viewer.view, "cube_with_morph_animation");
}, bg:kRed, cameraPosition: Vector3(3, 2, -6));
});
test('get gltf animation names', () async {
await testHelper.withViewer((viewer) async {
final cube = await viewer
.loadGltf("${testHelper.testDir}/assets/cube_with_morph_targets.glb");
await viewer.addToScene(cube);
await testHelper.capture(viewer.view, "gltf_animation_stopped");
final animationNames = await cube.getGltfAnimationNames();
expect(animationNames.first, "CubeAction");
await cube.playGltfAnimation(0);
await Future.delayed(Duration(seconds: 1));
await viewer.render();
await testHelper.capture(viewer.view, "gltf_animation_started");
}, bg: kRed);
});
} }