Merge pull request #35 from nmfisher/feature/bone-animation-blending

Blend between glTF and dynamic bone animations
This commit is contained in:
Nick Fisher
2024-06-16 15:13:12 +10:00
committed by GitHub
12 changed files with 84 additions and 33 deletions

View File

@@ -273,8 +273,20 @@ abstract class AbstractFilamentViewer {
/// Currently, only [Space.ParentBone] and [Space.Model] are supported; if you want
/// to transform to another space, you will need to do so manually.
///
/// [fadeInInSecs]/[fadeOutInSecs]/[maxDelta] are used to cross-fade between
/// the current active glTF animation ("animation1") and the animation you
/// set via this method ("animation2"). The bone orientations will be
/// linearly interpolated between animation1 and animation2; at time 0,
/// the orientation will be 100% animation1, at time [fadeInInSecs], the
/// animation will be ((1 - maxDelta) * animation1) + (maxDelta * animation2).
/// This will be applied in reverse after [fadeOutInSecs].
///
///
Future addBoneAnimation(FilamentEntity entity, BoneAnimationData animation,
{int skinIndex = 0, double fadeInInSecs=0.0, double fadeOutInSecs=0.0});
{int skinIndex = 0,
double fadeInInSecs = 0.0,
double fadeOutInSecs = 0.0,
double maxDelta = 1.0});
///
/// Gets the entity representing the bone at [boneIndex]/[skinIndex].
@@ -307,10 +319,10 @@ abstract class AbstractFilamentViewer {
Future setTransform(FilamentEntity entity, Matrix4 transform);
///
/// Updates the bone matrices for [entity] (which must be the FilamentEntity
/// Updates the bone matrices for [entity] (which must be the FilamentEntity
/// returned by [loadGlb/loadGltf]).
/// Under the hood, this just calls [updateBoneMatrices] on the Animator
/// instance of the relevant FilamentInstance (which uses the local
/// Under the hood, this just calls [updateBoneMatrices] on the Animator
/// instance of the relevant FilamentInstance (which uses the local
/// bone transform and the inverse bind matrix to set the bone matrix).
///
Future updateBoneMatrices(FilamentEntity entity);
@@ -320,7 +332,8 @@ abstract class AbstractFilamentViewer {
/// Don't call this manually unless you know what you're doing.
///
Future setBoneTransform(
FilamentEntity entity, int boneIndex, Matrix4 transform, { int skinIndex=0});
FilamentEntity entity, int boneIndex, Matrix4 transform,
{int skinIndex = 0});
///
/// Removes/destroys the specified entity from the scene.

View File

@@ -460,8 +460,17 @@ external void reset_to_rest_pose(
);
@ffi.Native<
ffi.Void Function(ffi.Pointer<ffi.Void>, EntityId, ffi.Int, ffi.Int,
ffi.Pointer<ffi.Float>, ffi.Int, ffi.Float, ffi.Float, ffi.Float)>(
ffi.Void Function(
ffi.Pointer<ffi.Void>,
EntityId,
ffi.Int,
ffi.Int,
ffi.Pointer<ffi.Float>,
ffi.Int,
ffi.Float,
ffi.Float,
ffi.Float,
ffi.Float)>(
symbol: 'add_bone_animation',
assetId: 'package:dart_filament/dart_filament.dart')
external void add_bone_animation(
@@ -474,6 +483,7 @@ external void add_bone_animation(
double frameLengthInMs,
double fadeOutInSecs,
double fadeInInSecs,
double maxDelta,
);
@ffi.Native<
@@ -1804,8 +1814,10 @@ typedef LoadFilamentResourceIntoOutPointerFunction = ffi.Void Function(
typedef DartLoadFilamentResourceIntoOutPointerFunction = void Function(
ffi.Pointer<ffi.Char> uri, ffi.Pointer<ResourceBuffer> out);
/// This header replicates most of the methods in FlutterFilamentApi.h, and is only intended to be used to generate client FFI bindings.
/// The intention is that calling one of these methods will call its respective method in FlutterFilamentApi.h, but wrapped in some kind of thread runner to ensure thread safety.
/// This header replicates most of the methods in DartFilamentApi.h.
/// It represents the interface for:
/// - invoking those methods that must be called on the main Filament engine thread
/// - setting up a render loop
typedef EntityId = ffi.Int32;
typedef DartEntityId = int;
typedef _ManipulatorMode = ffi.Int32;

View File

@@ -548,6 +548,7 @@ external void reset_to_rest_pose(
ffi.Int,
ffi.Float,
ffi.Float,
ffi.Float,
ffi.Float)>(symbol: '_add_bone_animation', assetId: 'dart_filament')
external void add_bone_animation(
ffi.Pointer<ffi.Void> sceneManager,
@@ -559,6 +560,7 @@ external void add_bone_animation(
double frameLengthInMs,
double fadeOutInSecs,
double fadeInInSecs,
double maxDelta,
);
@ffi.Native<
@@ -1788,8 +1790,10 @@ typedef FreeFilamentResourceFromOwnerFunction = ffi.Void Function(
typedef DartFreeFilamentResourceFromOwnerFunction = void Function(
ResourceBuffer, ffi.Pointer<ffi.Void>);
/// This header replicates most of the methods in FlutterFilamentApi.h, and is only intended to be used to generate client FFI bindings.
/// The intention is that calling one of these methods will call its respective method in FlutterFilamentApi.h, but wrapped in some kind of thread runner to ensure thread safety.
/// This header replicates most of the methods in DartFilamentApi.h.
/// It represents the interface for:
/// - invoking those methods that must be called on the main Filament engine thread
/// - setting up a render loop
typedef EntityId = ffi.Int32;
typedef DartEntityId = int;
typedef _ManipulatorMode = ffi.Int32;

View File

@@ -70,8 +70,10 @@ class DartFilamentJSExportViewer {
JSPromise removeSkybox() => viewer.removeSkybox().toJS;
@JSExport()
JSPromise loadIbl(String lightingPath, {double intensity = 30000}) =>
viewer.loadIbl(lightingPath, intensity: intensity).toJS;
JSPromise loadIbl(String lightingPath, double intensity) {
print("Loading IBL from $lightingPath with intensity $intensity");
return viewer.loadIbl(lightingPath, intensity: intensity).toJS;
}
@JSExport()
JSPromise rotateIbl(JSArray<JSNumber> rotation) {
@@ -266,7 +268,8 @@ class DartFilamentJSExportViewer {
JSNumber spaceEnum,
JSNumber skinIndex,
JSNumber fadeInInSecs,
JSNumber fadeOutInSecs) {
JSNumber fadeOutInSecs,
JSNumber maxDelta) {
var frameDataDart = frameData.toDart
.map((frame) => frame.toDart
.map((v) {
@@ -463,23 +466,29 @@ class DartFilamentJSExportViewer {
JSPromise moveCameraToAsset(FilamentEntity entity) =>
throw UnimplementedError();
// viewer.moveCameraToAsset(entity)).toJS;
@JSExport()
JSPromise setViewFrustumCulling(JSBoolean enabled) =>
throw UnimplementedError();
// viewer.setViewFrustumCulling(enabled).toJS;
@JSExport()
JSPromise setCameraExposure(
double aperture, double shutterSpeed, double sensitivity) =>
viewer.setCameraExposure(aperture, shutterSpeed, sensitivity).toJS;
@JSExport()
JSPromise setCameraRotation(JSArray<JSNumber> quaternion) {
var dartVals = quaternion.toDart;
return viewer.setCameraRotation(v64.Quaternion(dartVals[0].toDartDouble, dartVals[1].toDartDouble, dartVals[2].toDartDouble, dartVals[3].toDartDouble)).toJS;
var dartVals = quaternion.toDart;
return viewer
.setCameraRotation(v64.Quaternion(
dartVals[0].toDartDouble,
dartVals[1].toDartDouble,
dartVals[2].toDartDouble,
dartVals[3].toDartDouble))
.toJS;
}
@JSExport()
JSPromise setCameraModelMatrix(JSArray<JSNumber> matrix) {
throw UnimplementedError();

View File

@@ -151,7 +151,8 @@ extension type DartFilamentJSShim(JSObject _) implements JSObject {
JSNumber spaceEnum,
JSNumber skinIndex,
JSNumber fadeInInSecs,
JSNumber fadeOutInSecs);
JSNumber fadeOutInSecs,
JSNumber maxDelta);
@JS('removeEntity')
external JSPromise removeEntity(FilamentEntity entity);

View File

@@ -289,7 +289,8 @@ class JsInteropFilamentViewer implements AbstractFilamentViewer {
FilamentEntity entity, BoneAnimationData animation,
{int skinIndex = 0,
double fadeInInSecs = 0.0,
double fadeOutInSecs = 0.0}) async {
double fadeOutInSecs = 0.0,
double maxDelta=1.0}) async {
var boneNames = animation.bones.map((n) => n.toJS).toList().toJS;
var frameData = animation.frameData
.map((frame) => frame
@@ -316,7 +317,8 @@ class JsInteropFilamentViewer implements AbstractFilamentViewer {
animation.space.index.toJS,
skinIndex.toJS,
fadeInInSecs.toJS,
fadeOutInSecs.toJS)
fadeOutInSecs.toJS,
maxDelta)
.toDart;
}

View File

@@ -677,7 +677,8 @@ class FilamentViewer extends AbstractFilamentViewer {
Future addBoneAnimation(FilamentEntity entity, BoneAnimationData animation,
{int skinIndex = 0,
double fadeOutInSecs = 0.0,
double fadeInInSecs = 0.0}) async {
double fadeInInSecs = 0.0,
double maxDelta=1.0}) async {
if (animation.space != Space.Bone &&
animation.space != Space.ParentWorldRotation) {
throw UnimplementedError("TODO - support ${animation.space}");
@@ -758,7 +759,8 @@ class FilamentViewer extends AbstractFilamentViewer {
numFrames,
animation.frameLengthInMs,
fadeOutInSecs,
fadeInInSecs);
fadeInInSecs,
maxDelta);
}
allocator.free(data);
}

View File

@@ -149,7 +149,8 @@ extern "C"
int numFrames,
float frameLengthInMs,
float fadeOutInSecs,
float fadeInInSecs);
float fadeInInSecs,
float maxDelta);
EMSCRIPTEN_KEEPALIVE void get_local_transform(void *sceneManager,
EntityId entityId, float* const);
EMSCRIPTEN_KEEPALIVE void get_rest_local_transforms(void *sceneManager,

View File

@@ -118,8 +118,9 @@ namespace flutter_filament
int numFrames,
float frameLengthInMs,
float fadeOutInSecs,
float fadeInInSecs
);
float fadeInInSecs,
float maxDelta
);
std::unique_ptr<std::vector<math::mat4f>> getBoneRestTranforms(EntityId entityId, int skinIndex);
void resetBones(EntityId entityId);

View File

@@ -82,6 +82,7 @@ namespace flutter_filament
std::vector<math::mat4f> frameData;
float fadeOutInSecs = 0;
float fadeInInSecs = 0;
float maxDelta = 1.0f;
};
struct AnimationComponent
@@ -290,11 +291,13 @@ namespace flutter_filament
// 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 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)

View File

@@ -429,9 +429,10 @@ extern "C"
int numFrames,
float frameLengthInMs,
float fadeOutInSecs,
float fadeInInSecs)
float fadeInInSecs,
float maxDelta)
{
((SceneManager *)sceneManager)->addBoneAnimation(asset, skinIndex, boneIndex, frameData, numFrames, frameLengthInMs, fadeOutInSecs, fadeInInSecs);
((SceneManager *)sceneManager)->addBoneAnimation(asset, skinIndex, boneIndex, frameData, numFrames, frameLengthInMs, fadeOutInSecs, fadeInInSecs, maxDelta);
}
EMSCRIPTEN_KEEPALIVE void set_post_processing(void *const viewer, bool enabled)

View File

@@ -1024,7 +1024,9 @@ namespace flutter_filament
const float *const frameData,
int numFrames,
float frameLengthInMs,
float fadeOutInSecs, float fadeInInSecs)
float fadeOutInSecs,
float fadeInInSecs,
float maxDelta)
{
std::lock_guard lock(_mutex);
@@ -1072,7 +1074,6 @@ namespace flutter_filament
}
animation.frameLengthInMs = frameLengthInMs;
animation.start = std::chrono::high_resolution_clock::now();
animation.reverse = false;
animation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
@@ -1080,6 +1081,7 @@ namespace flutter_filament
animation.frameLengthInMs = frameLengthInMs;
animation.fadeOutInSecs = fadeOutInSecs;
animation.fadeInInSecs = fadeInInSecs;
animation.maxDelta = maxDelta;
animation.skinIndex = skinIndex;
if(!_animationComponentManager->hasComponent(instance->getRoot())) {
Log("ERROR: specified entity is not animatable (has no animation component attached).");