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: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<double> weights);
Future setMorphTargetWeights(FilamentEntity entity, List<double> weights);
///
/// Gets the names of all morph targets for the mesh [meshName] under [entity].
///
Future<List<String>> 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<String>? targetMeshNames});
///
/// 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: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<double> weights) async {
FilamentEntity entity, List<double> weights) async {
if (_viewer == null) {
throw Exception("No viewer available, ignoring");
}
if (weights.isEmpty) {
throw Exception("Weights must not be empty");
}
var weightsPtr = allocator<Float>(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<Char>();
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 = <String>[];
var meshNamePtr = meshName.toNativeUtf8().cast<Char>();
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<String>? targetMeshNames}) async {
if (_viewer == null) {
throw Exception("No viewer available, ignoring");
}
var dataPtr = allocator<Float>(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<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) {
// 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<Char>();
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<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!,
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