diff --git a/dart_filament/lib/dart_filament/abstract_filament_viewer.dart b/dart_filament/lib/dart_filament/abstract_filament_viewer.dart index 65e8115e..813c8931 100644 --- a/dart_filament/lib/dart_filament/abstract_filament_viewer.dart +++ b/dart_filament/lib/dart_filament/abstract_filament_viewer.dart @@ -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. diff --git a/dart_filament/lib/dart_filament/compatibility/native/dart_filament.g.dart b/dart_filament/lib/dart_filament/compatibility/native/dart_filament.g.dart index 3936caa2..039c5e9d 100644 --- a/dart_filament/lib/dart_filament/compatibility/native/dart_filament.g.dart +++ b/dart_filament/lib/dart_filament/compatibility/native/dart_filament.g.dart @@ -460,8 +460,17 @@ external void reset_to_rest_pose( ); @ffi.Native< - ffi.Void Function(ffi.Pointer, EntityId, ffi.Int, ffi.Int, - ffi.Pointer, ffi.Int, ffi.Float, ffi.Float, ffi.Float)>( + ffi.Void Function( + ffi.Pointer, + EntityId, + ffi.Int, + ffi.Int, + ffi.Pointer, + 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 uri, ffi.Pointer 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; diff --git a/dart_filament/lib/dart_filament/compatibility/web/dart_filament.g.dart b/dart_filament/lib/dart_filament/compatibility/web/dart_filament.g.dart index a2bf4c0b..9cb6b062 100644 --- a/dart_filament/lib/dart_filament/compatibility/web/dart_filament.g.dart +++ b/dart_filament/lib/dart_filament/compatibility/web/dart_filament.g.dart @@ -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 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); -/// 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; diff --git a/dart_filament/lib/dart_filament/compatibility/web/interop/dart_filament_js_export_type.dart b/dart_filament/lib/dart_filament/compatibility/web/interop/dart_filament_js_export_type.dart index 016b29ab..ceb8e34b 100644 --- a/dart_filament/lib/dart_filament/compatibility/web/interop/dart_filament_js_export_type.dart +++ b/dart_filament/lib/dart_filament/compatibility/web/interop/dart_filament_js_export_type.dart @@ -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 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 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 matrix) { throw UnimplementedError(); diff --git a/dart_filament/lib/dart_filament/compatibility/web/interop/dart_filament_js_extension_type.dart b/dart_filament/lib/dart_filament/compatibility/web/interop/dart_filament_js_extension_type.dart index 1bc0ad22..e7f265e9 100644 --- a/dart_filament/lib/dart_filament/compatibility/web/interop/dart_filament_js_extension_type.dart +++ b/dart_filament/lib/dart_filament/compatibility/web/interop/dart_filament_js_extension_type.dart @@ -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); diff --git a/dart_filament/lib/dart_filament/compatibility/web/interop/js_interop_filament_viewer.dart b/dart_filament/lib/dart_filament/compatibility/web/interop/js_interop_filament_viewer.dart index 9b516820..458dc8a3 100644 --- a/dart_filament/lib/dart_filament/compatibility/web/interop/js_interop_filament_viewer.dart +++ b/dart_filament/lib/dart_filament/compatibility/web/interop/js_interop_filament_viewer.dart @@ -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; } diff --git a/dart_filament/lib/dart_filament/filament_viewer_impl.dart b/dart_filament/lib/dart_filament/filament_viewer_impl.dart index d27dad09..eac0d502 100644 --- a/dart_filament/lib/dart_filament/filament_viewer_impl.dart +++ b/dart_filament/lib/dart_filament/filament_viewer_impl.dart @@ -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); } diff --git a/dart_filament/native/include/DartFilamentApi.h b/dart_filament/native/include/DartFilamentApi.h index bb98763b..621bea7c 100644 --- a/dart_filament/native/include/DartFilamentApi.h +++ b/dart_filament/native/include/DartFilamentApi.h @@ -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, diff --git a/dart_filament/native/include/SceneManager.hpp b/dart_filament/native/include/SceneManager.hpp index 69bf2c6b..b26e0024 100644 --- a/dart_filament/native/include/SceneManager.hpp +++ b/dart_filament/native/include/SceneManager.hpp @@ -118,8 +118,9 @@ namespace flutter_filament int numFrames, float frameLengthInMs, float fadeOutInSecs, - float fadeInInSecs - ); + float fadeInInSecs, + float maxDelta + ); std::unique_ptr> getBoneRestTranforms(EntityId entityId, int skinIndex); void resetBones(EntityId entityId); diff --git a/dart_filament/native/include/components/AnimationComponentManager.hpp b/dart_filament/native/include/components/AnimationComponentManager.hpp index b74cd859..b07cf73b 100644 --- a/dart_filament/native/include/components/AnimationComponentManager.hpp +++ b/dart_filament/native/include/components/AnimationComponentManager.hpp @@ -82,6 +82,7 @@ namespace flutter_filament std::vector 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) diff --git a/dart_filament/native/src/DartFilamentApi.cpp b/dart_filament/native/src/DartFilamentApi.cpp index 5fd72ae7..9e78dd4d 100644 --- a/dart_filament/native/src/DartFilamentApi.cpp +++ b/dart_filament/native/src/DartFilamentApi.cpp @@ -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) diff --git a/dart_filament/native/src/SceneManager.cpp b/dart_filament/native/src/SceneManager.cpp index 4ddf9fde..9ef561b0 100644 --- a/dart_filament/native/src/SceneManager.cpp +++ b/dart_filament/native/src/SceneManager.cpp @@ -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).");