diff --git a/example/lib/main.dart b/example/lib/main.dart index ccb12e57..237269a4 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -81,6 +81,11 @@ class _MyAppState extends State { onPressed: () => _filamentController.playAnimation(0, loop: _loop), child: const Text('play animation')), + ElevatedButton( + onPressed: () { + _filamentController.stopAnimation(); + }, + child: const Text('stop animation')), Checkbox( onChanged: (_) => setState(() { _loop = !_loop; @@ -108,13 +113,15 @@ class _MyAppState extends State { final numWeights = 8; final totalFrames = framerate * totalSecs; final frames = List.generate( - totalFrames, - (frame) => List.filled( - numWeights, frame / totalFrames)) - .reduce((accum, next) => accum + next); + totalFrames, + (frame) => + List.filled(numWeights, frame / totalFrames)); _filamentController.animate( - frames, numWeights, framerate.toDouble()); + frames.reduce((a, b) => a + b), + numWeights, + totalFrames, + 1000 / framerate.toDouble()); }, child: const Text('animate weights')), Builder( diff --git a/ios/src/FilamentViewer.cpp b/ios/src/FilamentViewer.cpp index 3963b40f..94669b4b 100644 --- a/ios/src/FilamentViewer.cpp +++ b/ios/src/FilamentViewer.cpp @@ -539,9 +539,11 @@ namespace polyvox Log("Set viewport to width: %d height: %d scaleFactor : %f", width, height, contentScaleFactor); } - void FilamentViewer::animateWeights(float *data, int numWeights, int numFrames, float frameRate) + void FilamentViewer::animateWeights(float *data, int numWeights, int numFrames, float frameLengthInMs) { - morphAnimationBuffer = std::make_unique(data, numWeights, numFrames, 1000 / frameRate); + // assert numWeights == asset.numWeights ? + Log("Making morph animation buffer with %d weights across %d frames and frame length %f ms ", numWeights, numFrames, frameLengthInMs); + morphAnimationBuffer = std::make_unique(data, numWeights, numFrames, frameLengthInMs); } void FilamentViewer::updateMorphAnimation() @@ -549,20 +551,19 @@ namespace polyvox if (morphAnimationBuffer->frameIndex >= morphAnimationBuffer->numFrames) { + duration dur = high_resolution_clock::now() - morphAnimationBuffer->startTime; + Log("Morph animation completed in %f ms (%d frames at framerate %f), final frame was %d", dur.count(), morphAnimationBuffer->numFrames, 1000 / morphAnimationBuffer->frameLengthInMs, morphAnimationBuffer->frameIndex); morphAnimationBuffer = nullptr; - return; - } - - if (morphAnimationBuffer->frameIndex == -1) + } else if (morphAnimationBuffer->frameIndex == -1) { morphAnimationBuffer->frameIndex++; - morphAnimationBuffer->startTime = std::chrono::high_resolution_clock::now(); + morphAnimationBuffer->startTime = high_resolution_clock::now(); applyWeights(morphAnimationBuffer->frameData, morphAnimationBuffer->numWeights); } else { - std::chrono::duration dur = std::chrono::high_resolution_clock::now() - morphAnimationBuffer->startTime; - int frameIndex = dur.count() / morphAnimationBuffer->frameLength; + duration dur = high_resolution_clock::now() - morphAnimationBuffer->startTime; + int frameIndex = static_cast(dur.count() / morphAnimationBuffer->frameLengthInMs); if (frameIndex != morphAnimationBuffer->frameIndex) { morphAnimationBuffer->frameIndex = frameIndex; @@ -579,15 +580,20 @@ namespace polyvox } } + void FilamentViewer::stopAnimation() { + // TODO - does this need to be threadsafe? + embeddedAnimationBuffer = nullptr; + } + void FilamentViewer::updateEmbeddedAnimation() { - duration dur = duration_cast>(std::chrono::high_resolution_clock::now() - embeddedAnimationBuffer->lastTime); + duration dur = duration_cast>(high_resolution_clock::now() - embeddedAnimationBuffer->lastTime); float startTime = 0; if(!embeddedAnimationBuffer->hasStarted) { embeddedAnimationBuffer->hasStarted = true; - embeddedAnimationBuffer->lastTime = std::chrono::high_resolution_clock::now(); + embeddedAnimationBuffer->lastTime = high_resolution_clock::now(); } else if(dur.count() >= embeddedAnimationBuffer->duration) { if(embeddedAnimationBuffer->loop) { - embeddedAnimationBuffer->lastTime = std::chrono::high_resolution_clock::now(); + embeddedAnimationBuffer->lastTime = high_resolution_clock::now(); } else { embeddedAnimationBuffer = nullptr; return; diff --git a/ios/src/FilamentViewer.hpp b/ios/src/FilamentViewer.hpp index 5d48a1f6..a41d5886 100644 --- a/ios/src/FilamentViewer.hpp +++ b/ios/src/FilamentViewer.hpp @@ -76,12 +76,12 @@ namespace polyvox { MorphAnimationBuffer(float* frameData, int numWeights, int numFrames, - float frameLength) : frameData(frameData), numWeights(numWeights), numFrames(numFrames), frameLength(frameLength) { + float frameLength) : frameData(frameData), numWeights(numWeights), numFrames(numFrames), frameLengthInMs(frameLength) { } int frameIndex = -1; int numFrames; - float frameLength; + float frameLengthInMs; time_point_t startTime; float* frameData; @@ -102,10 +102,32 @@ namespace polyvox { unique_ptr> getTargetNames(const char* meshName); unique_ptr> getAnimationNames(); Manipulator* manipulator; + + + /// + /// Manually set the weights for all morph targets in the assets to the provided values. + /// See [animateWeights] if you want to automatically + /// void applyWeights(float* weights, int count); - void animateWeights(float* data, int numWeights, int length, float frameRate); + /// + /// Update the asset's morph target weights every "frame" (which is an arbitrary length of time, i.e. this is not the same as a frame at the framerate of the underlying rendering framework). + /// Accordingly: + /// length(data) = numWeights * numFrames + /// total_animation_duration_in_ms = number_of_frames * frameLengthInMs + /// + void animateWeights(float* data, int numWeights, int numFrames, float frameLengthInMs); + + /// + /// Play an embedded animation (i.e. an animation node embedded in the GLTF asset). If [loop] is true, the animation will repeat indefinitely. + /// void playAnimation(int index, bool loop); + + /// + /// Immediately stop the currently playing animation. NOOP if no animation is playing. + /// + void stopAnimation(); + bool setCamera(const char* nodeName); void destroySwapChain(); void createSwapChain(void* surface); diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index 6b839ddc..2da99dd8 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -17,16 +17,18 @@ abstract class FilamentController { Future> getTargetNames(String meshName); Future> getAnimationNames(); Future releaseSourceAssets(); - Future playAnimation(int index, {bool loop=false}); + Future playAnimation(int index, {bool loop = false}); + Future stopAnimation(); Future setCamera(String name); /// - /// Set the weights of all morph targets in the mesh to the specified weights at successive frames (where [framerate] is the number of times per second the weights should be updated). + /// Set the weights of all morph targets in the mesh to the specified weights at successive frames (where each frame requires a duration of [frameLengthInMs]. /// Accepts a list of doubles representing a sequence of "frames", stacked end-to-end. /// Each frame is [numWeights] in length, where each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame. /// In other words, weights is a contiguous sequence of floats of size W*F, where W is the number of weights and F is the number of frames /// - Future animate(List weights, int numWeights, double frameRate); + Future animate( + List data, int numWeights, int numFrames, double frameLengthInMs); Future createMorpher(String meshName, List primitives); Future zoom(double z); } @@ -45,7 +47,7 @@ class PolyvoxFilamentController extends FilamentController { _channel = MethodChannel("app.polyvox.filament/filament_view_$id"); _channel.setMethodCallHandler((call) async { print("Received Filament method channel call : ${call.method}"); - if(call.method == "ready") { + if (call.method == "ready") { onFilamentViewCreatedHandler?.call(_id); return Future.value(true); } else { @@ -105,14 +107,15 @@ class PolyvoxFilamentController extends FilamentController { } Future> getAnimationNames() async { - var result = (await _channel.invokeMethod("getAnimationNames")) - .cast(); + var result = + (await _channel.invokeMethod("getAnimationNames")).cast(); return result; } - Future animate(List weights, int numWeights, double frameRate) async { - await _channel - .invokeMethod("animateWeights", [weights, numWeights, frameRate]); + Future animate(List weights, int numWeights, int numFrames, + double frameLengthInMs) async { + await _channel.invokeMethod( + "animateWeights", [weights, numWeights, numFrames, frameLengthInMs]); } Future releaseSourceAssets() async { @@ -127,8 +130,12 @@ class PolyvoxFilamentController extends FilamentController { await _channel.invokeMethod("createMorpher", [meshName, primitives]); } - Future playAnimation(int index, {bool loop=false}) async { - await _channel.invokeMethod("playAnimation", [index,loop]); + Future playAnimation(int index, {bool loop = false}) async { + await _channel.invokeMethod("playAnimation", [index, loop]); + } + + Future stopAnimation() async { + await _channel.invokeMethod("stopAnimation"); } Future setCamera(String name) async {