allow adding AnimationComponent/morph target animations to arbitrary entities

This commit is contained in:
Nick Fisher
2024-04-26 10:55:56 +08:00
parent 83296ef582
commit b5a36cc8d8
2 changed files with 83 additions and 40 deletions

View File

@@ -3,6 +3,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_filament/filament/entities/gizmo.dart'; import 'package:flutter_filament/filament/entities/gizmo.dart';
@@ -266,13 +267,17 @@ abstract class FilamentController {
Future rotateEnd(); 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). /// 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( Future setMorphTargetWeights(FilamentEntity entity, List<double> weights);
FilamentEntity entity, String meshName, List<double> weights);
///
/// Gets the names of all morph targets for the mesh [meshName] under [entity].
///
Future<List<String>> getMorphTargetNames( Future<List<String>> getMorphTargetNames(
FilamentEntity entity, String meshName); 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. /// It is permissible for [animation] to omit any targets that do exist under [meshName]; these simply won't be animated.
/// ///
Future setMorphAnimationData( Future setMorphAnimationData(
FilamentEntity entity, MorphAnimationData animation); FilamentEntity entity, MorphAnimationData animation,
{List<String>? targetMeshNames});
/// ///
/// Resets all bones in the given entity to their rest pose. /// Resets all bones in the given entity to their rest pose.

View File

@@ -3,6 +3,7 @@ import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'dart:developer' as dev; import 'dart:developer' as dev;
import 'package:animation_tools_dart/animation_tools_dart.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
@@ -783,20 +784,31 @@ class FilamentControllerFFI extends FilamentController {
@override @override
Future setMorphTargetWeights( Future setMorphTargetWeights(
FilamentEntity entity, String meshName, List<double> weights) async { FilamentEntity entity, List<double> weights) async {
if (_viewer == null) { if (_viewer == null) {
throw Exception("No viewer available, ignoring"); throw Exception("No viewer available, ignoring");
} }
if (weights.isEmpty) {
throw Exception("Weights must not be empty");
}
var weightsPtr = allocator<Float>(weights.length); var weightsPtr = allocator<Float>(weights.length);
for (int i = 0; i < weights.length; i++) { 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<Char>();
set_morph_target_weights_ffi( var success = await _withBoolCallback((cb) {
_sceneManager!, entity, meshNamePtr, weightsPtr, weights.length); set_morph_target_weights_ffi(
_sceneManager!, entity, weightsPtr, weights.length, cb);
});
allocator.free(weightsPtr); allocator.free(weightsPtr);
allocator.free(meshNamePtr);
if (!success) {
throw Exception(
"Failed to set morph target weights, check logs for details");
}
} }
@override @override
@@ -807,6 +819,7 @@ class FilamentControllerFFI extends FilamentController {
} }
var names = <String>[]; var names = <String>[];
var meshNamePtr = meshName.toNativeUtf8().cast<Char>(); var meshNamePtr = meshName.toNativeUtf8().cast<Char>();
var count = await _withIntCallback((callback) => var count = await _withIntCallback((callback) =>
get_morph_target_name_count_ffi( get_morph_target_name_count_ffi(
_sceneManager!, entity, meshNamePtr, callback)); _sceneManager!, entity, meshNamePtr, callback));
@@ -851,51 +864,73 @@ class FilamentControllerFFI extends FilamentController {
@override @override
Future setMorphAnimationData( Future setMorphAnimationData(
FilamentEntity entity, MorphAnimationData animation) async { FilamentEntity entity, MorphAnimationData animation,
{List<String>? targetMeshNames}) async {
if (_viewer == null) { if (_viewer == null) {
throw Exception("No viewer available, ignoring"); throw Exception("No viewer available, ignoring");
} }
var dataPtr = allocator<Float>(animation.data.length); var meshNames = await getChildEntityNames(entity, renderableOnly: true);
for (int i = 0; i < animation.data.length; i++) { var meshEntities = await getChildEntities(entity, true);
dataPtr.elementAt(i).value = animation.data[i];
}
Pointer<Int> idxPtr = allocator<Int>(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) { if (targetMeshNames?.contains(meshName) == false) {
// the morph targets in [animation] might be a subset of those that actually exist in the mesh (and might not have the same order) continue;
// 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.
var meshMorphTargets = await getMorphTargetNames(entity, meshName); var meshMorphTargets = await getMorphTargetNames(entity, meshName);
for (int i = 0; i < animation.numMorphTargets; i++) { var intersection = animation.morphTargets
var index = meshMorphTargets.indexOf(animation.morphTargets[i]); .toSet()
if (index == -1) { .intersection(meshMorphTargets.toSet())
allocator.free(dataPtr); .toList();
allocator.free(idxPtr);
throw Exception( if (intersection.isEmpty) {
"Morph target ${animation.morphTargets[i]} is specified in the animation but could not be found in the mesh $meshName under entity $entity"); throw Exception(
} "No morph targets specified in animation are present on targeted mesh");
idxPtr.elementAt(i).value = index;
} }
var meshNamePtr = var indices =
meshName.toNativeUtf8(allocator: allocator).cast<Char>(); 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<Float>(frameData.length);
dataPtr
.asTypedList(frameData.length)
.setRange(0, frameData.length, frameData);
final idxPtr = allocator<Int>(indices.length);
for (int i = 0; i < indices.length; i++) {
idxPtr[i] = indices[i];
}
var result = set_morph_animation(
_sceneManager!, _sceneManager!,
entity, meshEntity,
meshNamePtr,
dataPtr, dataPtr,
idxPtr, idxPtr,
animation.numMorphTargets, indices.length,
animation.numFrames, animation.numFrames,
(animation.frameLengthInMs)); (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 @override
@@ -1574,7 +1609,9 @@ class FilamentControllerFFI extends FilamentController {
@override @override
Future addAnimationComponent(FilamentEntity entity) async { Future addAnimationComponent(FilamentEntity entity) async {
add_animation_component(_sceneManager!, entity); if (!add_animation_component(_sceneManager!, entity)) {
throw Exception("Failed to add animation component");
}
} }
@override @override