Merge pull request #35 from nmfisher/feature/bone-animation-blending
Blend between glTF and dynamic bone animations
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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).");
|
||||
|
||||
Reference in New Issue
Block a user