allow adding AnimationComponent/morph target animations to arbitrary entities
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user