From b5a36cc8d81c27be883acbb374da365705b2826a Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Fri, 26 Apr 2024 10:55:56 +0800 Subject: [PATCH] allow adding AnimationComponent/morph target animations to arbitrary entities --- lib/filament/filament_controller.dart | 16 +++- lib/filament/filament_controller_ffi.dart | 107 +++++++++++++++------- 2 files changed, 83 insertions(+), 40 deletions(-) diff --git a/lib/filament/filament_controller.dart b/lib/filament/filament_controller.dart index 93fff6b7..5b6b8dbc 100644 --- a/lib/filament/filament_controller.dart +++ b/lib/filament/filament_controller.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_filament/filament/entities/gizmo.dart'; @@ -266,13 +267,17 @@ abstract class FilamentController { Future rotateEnd(); /// - /// Set the weights for all morph targets under node [meshName] in [entity] to [weights]. + /// Set the weights for all morph targets in [entity] to [weights]. /// Note that [weights] must contain values for ALL morph targets, but no exception will be thrown if you don't do so (you'll just get incorrect results). - /// If you only want to set one value, set all others to zero (check [getMorphTargetNames] if you need the get a list of all morph targets.) + /// If you only want to set one value, set all others to zero (check [getMorphTargetNames] if you need the get a list of all morph targets). + /// IMPORTANT - this accepts the actual FilamentEntity with the relevant morph targets (unlike [getMorphTargetNames], which uses the parent entity and the child mesh name). + /// Use [getChildEntityByName] if you are setting the weights for a child mesh. /// - Future setMorphTargetWeights( - FilamentEntity entity, String meshName, List weights); + Future setMorphTargetWeights(FilamentEntity entity, List weights); + /// + /// Gets the names of all morph targets for the mesh [meshName] under [entity]. + /// Future> getMorphTargetNames( FilamentEntity entity, String meshName); @@ -291,7 +296,8 @@ abstract class FilamentController { /// It is permissible for [animation] to omit any targets that do exist under [meshName]; these simply won't be animated. /// Future setMorphAnimationData( - FilamentEntity entity, MorphAnimationData animation); + FilamentEntity entity, MorphAnimationData animation, + {List? targetMeshNames}); /// /// Resets all bones in the given entity to their rest pose. diff --git a/lib/filament/filament_controller_ffi.dart b/lib/filament/filament_controller_ffi.dart index c6513929..bc6e149e 100644 --- a/lib/filament/filament_controller_ffi.dart +++ b/lib/filament/filament_controller_ffi.dart @@ -3,6 +3,7 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:ui' as ui; import 'dart:developer' as dev; +import 'package:animation_tools_dart/animation_tools_dart.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:ffi/ffi.dart'; @@ -783,20 +784,31 @@ class FilamentControllerFFI extends FilamentController { @override Future setMorphTargetWeights( - FilamentEntity entity, String meshName, List weights) async { + FilamentEntity entity, List weights) async { if (_viewer == null) { throw Exception("No viewer available, ignoring"); } + + if (weights.isEmpty) { + throw Exception("Weights must not be empty"); + } var weightsPtr = allocator(weights.length); for (int i = 0; i < weights.length; i++) { - weightsPtr.elementAt(i).value = weights[i]; + weightsPtr[i] = weights[i]; } - var meshNamePtr = meshName.toNativeUtf8(allocator: allocator).cast(); - set_morph_target_weights_ffi( - _sceneManager!, entity, meshNamePtr, weightsPtr, weights.length); + + var success = await _withBoolCallback((cb) { + set_morph_target_weights_ffi( + _sceneManager!, entity, weightsPtr, weights.length, cb); + }); + allocator.free(weightsPtr); - allocator.free(meshNamePtr); + + if (!success) { + throw Exception( + "Failed to set morph target weights, check logs for details"); + } } @override @@ -807,6 +819,7 @@ class FilamentControllerFFI extends FilamentController { } var names = []; var meshNamePtr = meshName.toNativeUtf8().cast(); + var count = await _withIntCallback((callback) => get_morph_target_name_count_ffi( _sceneManager!, entity, meshNamePtr, callback)); @@ -851,51 +864,73 @@ class FilamentControllerFFI extends FilamentController { @override Future setMorphAnimationData( - FilamentEntity entity, MorphAnimationData animation) async { + FilamentEntity entity, MorphAnimationData animation, + {List? targetMeshNames}) async { if (_viewer == null) { throw Exception("No viewer available, ignoring"); } - var dataPtr = allocator(animation.data.length); - for (int i = 0; i < animation.data.length; i++) { - dataPtr.elementAt(i).value = animation.data[i]; - } + var meshNames = await getChildEntityNames(entity, renderableOnly: true); + var meshEntities = await getChildEntities(entity, true); - Pointer idxPtr = allocator(animation.morphTargets.length); + // Entities are not guaranteed to have the same morph targets (or share the same order), + // either from each other, or from those specified in [animation]. + // We therefore set morph targets separately for each mesh. + // For each mesh, allocate enough memory to hold FxM 32-bit floats + // (where F is the number of Frames, and M is the number of morph targets in the mesh). + // we call [extract] on [animation] to return frame data only for morph targets that present in both the mesh and the animation + for (int i = 0; i < meshNames.length; i++) { + var meshName = meshNames[i]; + var meshEntity = meshEntities[i]; - for (var meshName in animation.meshNames) { - // the morph targets in [animation] might be a subset of those that actually exist in the mesh (and might not have the same order) - // we don't want to reorder the data (?? or do we? this is probably more efficient for the backend?) - // so let's get the actual list of morph targets from the mesh and pass the relevant indices to the native side. + if (targetMeshNames?.contains(meshName) == false) { + continue; + } var meshMorphTargets = await getMorphTargetNames(entity, meshName); - for (int i = 0; i < animation.numMorphTargets; i++) { - var index = meshMorphTargets.indexOf(animation.morphTargets[i]); - if (index == -1) { - allocator.free(dataPtr); - allocator.free(idxPtr); - throw Exception( - "Morph target ${animation.morphTargets[i]} is specified in the animation but could not be found in the mesh $meshName under entity $entity"); - } - idxPtr.elementAt(i).value = index; + var intersection = animation.morphTargets + .toSet() + .intersection(meshMorphTargets.toSet()) + .toList(); + + if (intersection.isEmpty) { + throw Exception( + "No morph targets specified in animation are present on targeted mesh"); } - var meshNamePtr = - meshName.toNativeUtf8(allocator: allocator).cast(); + var indices = + intersection.map((m) => meshMorphTargets.indexOf(m)).toList(); - set_morph_animation( + var frameData = animation.extract(morphTargets: intersection); + + assert(frameData.length == animation.numFrames * intersection.length); + + var dataPtr = allocator(frameData.length); + + dataPtr + .asTypedList(frameData.length) + .setRange(0, frameData.length, frameData); + + final idxPtr = allocator(indices.length); + + for (int i = 0; i < indices.length; i++) { + idxPtr[i] = indices[i]; + } + + var result = set_morph_animation( _sceneManager!, - entity, - meshNamePtr, + meshEntity, dataPtr, idxPtr, - animation.numMorphTargets, + indices.length, animation.numFrames, (animation.frameLengthInMs)); - allocator.free(meshNamePtr); + allocator.free(dataPtr); + allocator.free(idxPtr); + if (!result) { + throw Exception("Failed to set morph animation data for ${meshName}"); + } } - allocator.free(dataPtr); - allocator.free(idxPtr); } @override @@ -1574,7 +1609,9 @@ class FilamentControllerFFI extends FilamentController { @override Future addAnimationComponent(FilamentEntity entity) async { - add_animation_component(_sceneManager!, entity); + if (!add_animation_component(_sceneManager!, entity)) { + throw Exception("Failed to add animation component"); + } } @override