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
|
/// Currently, only [Space.ParentBone] and [Space.Model] are supported; if you want
|
||||||
/// to transform to another space, you will need to do so manually.
|
/// 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,
|
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].
|
/// Gets the entity representing the bone at [boneIndex]/[skinIndex].
|
||||||
@@ -307,10 +319,10 @@ abstract class AbstractFilamentViewer {
|
|||||||
Future setTransform(FilamentEntity entity, Matrix4 transform);
|
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]).
|
/// returned by [loadGlb/loadGltf]).
|
||||||
/// Under the hood, this just calls [updateBoneMatrices] on the Animator
|
/// Under the hood, this just calls [updateBoneMatrices] on the Animator
|
||||||
/// instance of the relevant FilamentInstance (which uses the local
|
/// instance of the relevant FilamentInstance (which uses the local
|
||||||
/// bone transform and the inverse bind matrix to set the bone matrix).
|
/// bone transform and the inverse bind matrix to set the bone matrix).
|
||||||
///
|
///
|
||||||
Future updateBoneMatrices(FilamentEntity entity);
|
Future updateBoneMatrices(FilamentEntity entity);
|
||||||
@@ -320,7 +332,8 @@ abstract class AbstractFilamentViewer {
|
|||||||
/// Don't call this manually unless you know what you're doing.
|
/// Don't call this manually unless you know what you're doing.
|
||||||
///
|
///
|
||||||
Future setBoneTransform(
|
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.
|
/// Removes/destroys the specified entity from the scene.
|
||||||
|
|||||||
@@ -460,8 +460,17 @@ external void reset_to_rest_pose(
|
|||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
ffi.Void Function(ffi.Pointer<ffi.Void>, EntityId, ffi.Int, ffi.Int,
|
ffi.Void Function(
|
||||||
ffi.Pointer<ffi.Float>, ffi.Int, ffi.Float, ffi.Float, ffi.Float)>(
|
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',
|
symbol: 'add_bone_animation',
|
||||||
assetId: 'package:dart_filament/dart_filament.dart')
|
assetId: 'package:dart_filament/dart_filament.dart')
|
||||||
external void add_bone_animation(
|
external void add_bone_animation(
|
||||||
@@ -474,6 +483,7 @@ external void add_bone_animation(
|
|||||||
double frameLengthInMs,
|
double frameLengthInMs,
|
||||||
double fadeOutInSecs,
|
double fadeOutInSecs,
|
||||||
double fadeInInSecs,
|
double fadeInInSecs,
|
||||||
|
double maxDelta,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
@@ -1804,8 +1814,10 @@ typedef LoadFilamentResourceIntoOutPointerFunction = ffi.Void Function(
|
|||||||
typedef DartLoadFilamentResourceIntoOutPointerFunction = void Function(
|
typedef DartLoadFilamentResourceIntoOutPointerFunction = void Function(
|
||||||
ffi.Pointer<ffi.Char> uri, ffi.Pointer<ResourceBuffer> out);
|
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.
|
/// This header replicates most of the methods in DartFilamentApi.h.
|
||||||
/// 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.
|
/// 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 EntityId = ffi.Int32;
|
||||||
typedef DartEntityId = int;
|
typedef DartEntityId = int;
|
||||||
typedef _ManipulatorMode = ffi.Int32;
|
typedef _ManipulatorMode = ffi.Int32;
|
||||||
|
|||||||
@@ -548,6 +548,7 @@ external void reset_to_rest_pose(
|
|||||||
ffi.Int,
|
ffi.Int,
|
||||||
ffi.Float,
|
ffi.Float,
|
||||||
ffi.Float,
|
ffi.Float,
|
||||||
|
ffi.Float,
|
||||||
ffi.Float)>(symbol: '_add_bone_animation', assetId: 'dart_filament')
|
ffi.Float)>(symbol: '_add_bone_animation', assetId: 'dart_filament')
|
||||||
external void add_bone_animation(
|
external void add_bone_animation(
|
||||||
ffi.Pointer<ffi.Void> sceneManager,
|
ffi.Pointer<ffi.Void> sceneManager,
|
||||||
@@ -559,6 +560,7 @@ external void add_bone_animation(
|
|||||||
double frameLengthInMs,
|
double frameLengthInMs,
|
||||||
double fadeOutInSecs,
|
double fadeOutInSecs,
|
||||||
double fadeInInSecs,
|
double fadeInInSecs,
|
||||||
|
double maxDelta,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ffi.Native<
|
@ffi.Native<
|
||||||
@@ -1788,8 +1790,10 @@ typedef FreeFilamentResourceFromOwnerFunction = ffi.Void Function(
|
|||||||
typedef DartFreeFilamentResourceFromOwnerFunction = void Function(
|
typedef DartFreeFilamentResourceFromOwnerFunction = void Function(
|
||||||
ResourceBuffer, ffi.Pointer<ffi.Void>);
|
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.
|
/// This header replicates most of the methods in DartFilamentApi.h.
|
||||||
/// 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.
|
/// 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 EntityId = ffi.Int32;
|
||||||
typedef DartEntityId = int;
|
typedef DartEntityId = int;
|
||||||
typedef _ManipulatorMode = ffi.Int32;
|
typedef _ManipulatorMode = ffi.Int32;
|
||||||
|
|||||||
@@ -70,8 +70,10 @@ class DartFilamentJSExportViewer {
|
|||||||
JSPromise removeSkybox() => viewer.removeSkybox().toJS;
|
JSPromise removeSkybox() => viewer.removeSkybox().toJS;
|
||||||
|
|
||||||
@JSExport()
|
@JSExport()
|
||||||
JSPromise loadIbl(String lightingPath, {double intensity = 30000}) =>
|
JSPromise loadIbl(String lightingPath, double intensity) {
|
||||||
viewer.loadIbl(lightingPath, intensity: intensity).toJS;
|
print("Loading IBL from $lightingPath with intensity $intensity");
|
||||||
|
return viewer.loadIbl(lightingPath, intensity: intensity).toJS;
|
||||||
|
}
|
||||||
|
|
||||||
@JSExport()
|
@JSExport()
|
||||||
JSPromise rotateIbl(JSArray<JSNumber> rotation) {
|
JSPromise rotateIbl(JSArray<JSNumber> rotation) {
|
||||||
@@ -266,7 +268,8 @@ class DartFilamentJSExportViewer {
|
|||||||
JSNumber spaceEnum,
|
JSNumber spaceEnum,
|
||||||
JSNumber skinIndex,
|
JSNumber skinIndex,
|
||||||
JSNumber fadeInInSecs,
|
JSNumber fadeInInSecs,
|
||||||
JSNumber fadeOutInSecs) {
|
JSNumber fadeOutInSecs,
|
||||||
|
JSNumber maxDelta) {
|
||||||
var frameDataDart = frameData.toDart
|
var frameDataDart = frameData.toDart
|
||||||
.map((frame) => frame.toDart
|
.map((frame) => frame.toDart
|
||||||
.map((v) {
|
.map((v) {
|
||||||
@@ -463,23 +466,29 @@ class DartFilamentJSExportViewer {
|
|||||||
JSPromise moveCameraToAsset(FilamentEntity entity) =>
|
JSPromise moveCameraToAsset(FilamentEntity entity) =>
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
// viewer.moveCameraToAsset(entity)).toJS;
|
// viewer.moveCameraToAsset(entity)).toJS;
|
||||||
|
|
||||||
@JSExport()
|
@JSExport()
|
||||||
JSPromise setViewFrustumCulling(JSBoolean enabled) =>
|
JSPromise setViewFrustumCulling(JSBoolean enabled) =>
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
// viewer.setViewFrustumCulling(enabled).toJS;
|
// viewer.setViewFrustumCulling(enabled).toJS;
|
||||||
|
|
||||||
@JSExport()
|
@JSExport()
|
||||||
JSPromise setCameraExposure(
|
JSPromise setCameraExposure(
|
||||||
double aperture, double shutterSpeed, double sensitivity) =>
|
double aperture, double shutterSpeed, double sensitivity) =>
|
||||||
viewer.setCameraExposure(aperture, shutterSpeed, sensitivity).toJS;
|
viewer.setCameraExposure(aperture, shutterSpeed, sensitivity).toJS;
|
||||||
|
|
||||||
@JSExport()
|
@JSExport()
|
||||||
JSPromise setCameraRotation(JSArray<JSNumber> quaternion) {
|
JSPromise setCameraRotation(JSArray<JSNumber> quaternion) {
|
||||||
var dartVals = quaternion.toDart;
|
var dartVals = quaternion.toDart;
|
||||||
return viewer.setCameraRotation(v64.Quaternion(dartVals[0].toDartDouble, dartVals[1].toDartDouble, dartVals[2].toDartDouble, dartVals[3].toDartDouble)).toJS;
|
return viewer
|
||||||
|
.setCameraRotation(v64.Quaternion(
|
||||||
|
dartVals[0].toDartDouble,
|
||||||
|
dartVals[1].toDartDouble,
|
||||||
|
dartVals[2].toDartDouble,
|
||||||
|
dartVals[3].toDartDouble))
|
||||||
|
.toJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JSExport()
|
@JSExport()
|
||||||
JSPromise setCameraModelMatrix(JSArray<JSNumber> matrix) {
|
JSPromise setCameraModelMatrix(JSArray<JSNumber> matrix) {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
|
|||||||
@@ -151,7 +151,8 @@ extension type DartFilamentJSShim(JSObject _) implements JSObject {
|
|||||||
JSNumber spaceEnum,
|
JSNumber spaceEnum,
|
||||||
JSNumber skinIndex,
|
JSNumber skinIndex,
|
||||||
JSNumber fadeInInSecs,
|
JSNumber fadeInInSecs,
|
||||||
JSNumber fadeOutInSecs);
|
JSNumber fadeOutInSecs,
|
||||||
|
JSNumber maxDelta);
|
||||||
|
|
||||||
@JS('removeEntity')
|
@JS('removeEntity')
|
||||||
external JSPromise removeEntity(FilamentEntity entity);
|
external JSPromise removeEntity(FilamentEntity entity);
|
||||||
|
|||||||
@@ -289,7 +289,8 @@ class JsInteropFilamentViewer implements AbstractFilamentViewer {
|
|||||||
FilamentEntity entity, BoneAnimationData animation,
|
FilamentEntity entity, BoneAnimationData animation,
|
||||||
{int skinIndex = 0,
|
{int skinIndex = 0,
|
||||||
double fadeInInSecs = 0.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 boneNames = animation.bones.map((n) => n.toJS).toList().toJS;
|
||||||
var frameData = animation.frameData
|
var frameData = animation.frameData
|
||||||
.map((frame) => frame
|
.map((frame) => frame
|
||||||
@@ -316,7 +317,8 @@ class JsInteropFilamentViewer implements AbstractFilamentViewer {
|
|||||||
animation.space.index.toJS,
|
animation.space.index.toJS,
|
||||||
skinIndex.toJS,
|
skinIndex.toJS,
|
||||||
fadeInInSecs.toJS,
|
fadeInInSecs.toJS,
|
||||||
fadeOutInSecs.toJS)
|
fadeOutInSecs.toJS,
|
||||||
|
maxDelta)
|
||||||
.toDart;
|
.toDart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -677,7 +677,8 @@ class FilamentViewer extends AbstractFilamentViewer {
|
|||||||
Future addBoneAnimation(FilamentEntity entity, BoneAnimationData animation,
|
Future addBoneAnimation(FilamentEntity entity, BoneAnimationData animation,
|
||||||
{int skinIndex = 0,
|
{int skinIndex = 0,
|
||||||
double fadeOutInSecs = 0.0,
|
double fadeOutInSecs = 0.0,
|
||||||
double fadeInInSecs = 0.0}) async {
|
double fadeInInSecs = 0.0,
|
||||||
|
double maxDelta=1.0}) async {
|
||||||
if (animation.space != Space.Bone &&
|
if (animation.space != Space.Bone &&
|
||||||
animation.space != Space.ParentWorldRotation) {
|
animation.space != Space.ParentWorldRotation) {
|
||||||
throw UnimplementedError("TODO - support ${animation.space}");
|
throw UnimplementedError("TODO - support ${animation.space}");
|
||||||
@@ -758,7 +759,8 @@ class FilamentViewer extends AbstractFilamentViewer {
|
|||||||
numFrames,
|
numFrames,
|
||||||
animation.frameLengthInMs,
|
animation.frameLengthInMs,
|
||||||
fadeOutInSecs,
|
fadeOutInSecs,
|
||||||
fadeInInSecs);
|
fadeInInSecs,
|
||||||
|
maxDelta);
|
||||||
}
|
}
|
||||||
allocator.free(data);
|
allocator.free(data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ extern "C"
|
|||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs,
|
float frameLengthInMs,
|
||||||
float fadeOutInSecs,
|
float fadeOutInSecs,
|
||||||
float fadeInInSecs);
|
float fadeInInSecs,
|
||||||
|
float maxDelta);
|
||||||
EMSCRIPTEN_KEEPALIVE void get_local_transform(void *sceneManager,
|
EMSCRIPTEN_KEEPALIVE void get_local_transform(void *sceneManager,
|
||||||
EntityId entityId, float* const);
|
EntityId entityId, float* const);
|
||||||
EMSCRIPTEN_KEEPALIVE void get_rest_local_transforms(void *sceneManager,
|
EMSCRIPTEN_KEEPALIVE void get_rest_local_transforms(void *sceneManager,
|
||||||
|
|||||||
@@ -118,8 +118,9 @@ namespace flutter_filament
|
|||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs,
|
float frameLengthInMs,
|
||||||
float fadeOutInSecs,
|
float fadeOutInSecs,
|
||||||
float fadeInInSecs
|
float fadeInInSecs,
|
||||||
);
|
float maxDelta
|
||||||
|
);
|
||||||
|
|
||||||
std::unique_ptr<std::vector<math::mat4f>> getBoneRestTranforms(EntityId entityId, int skinIndex);
|
std::unique_ptr<std::vector<math::mat4f>> getBoneRestTranforms(EntityId entityId, int skinIndex);
|
||||||
void resetBones(EntityId entityId);
|
void resetBones(EntityId entityId);
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ namespace flutter_filament
|
|||||||
std::vector<math::mat4f> frameData;
|
std::vector<math::mat4f> frameData;
|
||||||
float fadeOutInSecs = 0;
|
float fadeOutInSecs = 0;
|
||||||
float fadeInInSecs = 0;
|
float fadeInInSecs = 0;
|
||||||
|
float maxDelta = 1.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AnimationComponent
|
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
|
// 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;
|
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) {
|
if(fadeDelta > 1.0f) {
|
||||||
fadeDelta = 1 - ((elapsedInSecs - animationStatus.durationInSecs - animationStatus.fadeInInSecs) / animationStatus.fadeOutInSecs);
|
fadeDelta = 1 - ((elapsedInSecs - animationStatus.durationInSecs - animationStatus.fadeInInSecs) / animationStatus.fadeOutInSecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fadeDelta = std::clamp(fadeDelta, 0.0f, animationStatus.maxDelta);
|
||||||
|
|
||||||
auto jointTransform = _transformManager.getInstance(joint);
|
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)
|
// 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,
|
int numFrames,
|
||||||
float frameLengthInMs,
|
float frameLengthInMs,
|
||||||
float fadeOutInSecs,
|
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)
|
EMSCRIPTEN_KEEPALIVE void set_post_processing(void *const viewer, bool enabled)
|
||||||
|
|||||||
@@ -1024,7 +1024,9 @@ namespace flutter_filament
|
|||||||
const float *const frameData,
|
const float *const frameData,
|
||||||
int numFrames,
|
int numFrames,
|
||||||
float frameLengthInMs,
|
float frameLengthInMs,
|
||||||
float fadeOutInSecs, float fadeInInSecs)
|
float fadeOutInSecs,
|
||||||
|
float fadeInInSecs,
|
||||||
|
float maxDelta)
|
||||||
{
|
{
|
||||||
std::lock_guard lock(_mutex);
|
std::lock_guard lock(_mutex);
|
||||||
|
|
||||||
@@ -1072,7 +1074,6 @@ namespace flutter_filament
|
|||||||
}
|
}
|
||||||
|
|
||||||
animation.frameLengthInMs = frameLengthInMs;
|
animation.frameLengthInMs = frameLengthInMs;
|
||||||
|
|
||||||
animation.start = std::chrono::high_resolution_clock::now();
|
animation.start = std::chrono::high_resolution_clock::now();
|
||||||
animation.reverse = false;
|
animation.reverse = false;
|
||||||
animation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
|
animation.durationInSecs = (frameLengthInMs * numFrames) / 1000.0f;
|
||||||
@@ -1080,6 +1081,7 @@ namespace flutter_filament
|
|||||||
animation.frameLengthInMs = frameLengthInMs;
|
animation.frameLengthInMs = frameLengthInMs;
|
||||||
animation.fadeOutInSecs = fadeOutInSecs;
|
animation.fadeOutInSecs = fadeOutInSecs;
|
||||||
animation.fadeInInSecs = fadeInInSecs;
|
animation.fadeInInSecs = fadeInInSecs;
|
||||||
|
animation.maxDelta = maxDelta;
|
||||||
animation.skinIndex = skinIndex;
|
animation.skinIndex = skinIndex;
|
||||||
if(!_animationComponentManager->hasComponent(instance->getRoot())) {
|
if(!_animationComponentManager->hasComponent(instance->getRoot())) {
|
||||||
Log("ERROR: specified entity is not animatable (has no animation component attached).");
|
Log("ERROR: specified entity is not animatable (has no animation component attached).");
|
||||||
|
|||||||
Reference in New Issue
Block a user