fix dynamic bone animations

This commit is contained in:
Nick Fisher
2023-04-28 20:53:16 +08:00
parent 62c4be0563
commit bbb3cb5727
13 changed files with 170 additions and 130 deletions

View File

@@ -9,9 +9,9 @@ import 'package:vector_math/vector_math.dart';
///
class BoneAnimationData {
final String boneName;
final String meshName;
final List<String> meshNames;
final Float32List frameData;
double frameLengthInMs;
BoneAnimationData(
this.boneName, this.meshName, this.frameData, this.frameLengthInMs);
this.boneName, this.meshNames, this.frameData, this.frameLengthInMs);
}

View File

@@ -46,11 +46,21 @@ class BoneDriver {
rotation.x *= weight;
rotation.y *= weight;
rotation.z *= weight;
rotation.w = 1;
return rotation;
}).toList();
yield rotations.fold(
rotations.first, (Quaternion a, Quaternion b) => a * b);
if (frameNum == 0) {
print(rotations);
}
var result = rotations.fold(
rotations.first, (Quaternion a, Quaternion b) => a + b);
result.w = 1;
print("RESULT $result");
yield result;
// .normalized();
// todo - bone translations
}
}

View File

@@ -11,11 +11,12 @@ import 'package:vector_math/vector_math.dart';
/// A class for loading animation data from a single CSV and allocating between morph/bone animation with help.
///
class DynamicAnimation {
final MorphAnimationData morphAnimation;
final MorphAnimationData? morphAnimation;
final List<BoneAnimationData> boneAnimation;
factory DynamicAnimation.load(String meshName, String csvPath,
factory DynamicAnimation.load(String? meshName, String csvPath,
{List<BoneDriver>? boneDrivers,
List<String>? boneMeshes,
String? boneDriverConfigPath,
double? framerate}) {
// create a MorphAnimationData instance from the given CSV
@@ -23,8 +24,9 @@ class DynamicAnimation {
var frameLengthInMs = 1000 / (framerate ?? 60.0);
var morphNames = llf
.item1; //.where((name) => !boneDrivers.any((element) => element.blendshape == name));
var morphAnimationData =
MorphAnimationData(meshName, llf.item2, morphNames, frameLengthInMs);
var morphAnimationData = MorphAnimationData(
meshName ?? "NULL", llf.item2, morphNames, frameLengthInMs);
final boneAnimations = <BoneAnimationData>[];
@@ -45,14 +47,14 @@ class DynamicAnimation {
// iterate over every bone driver
if (boneDrivers != null) {
for (var driver in boneDrivers) {
// get all frames for the single the blendshape
// collect the frame data for the blendshapes that this driver uses
var morphData = driver.transformations
.map((String blendshape, Transformation transformation) {
return MapEntry(
blendshape, morphAnimationData.getData(blendshape).toList());
});
// apply the driver to the blendshape weight
// apply the driver to the frame data
var transformedQ = driver.transform(morphData).toList();
// transform the quaternion to a Float32List
@@ -60,7 +62,7 @@ class DynamicAnimation {
// add to the list of boneAnimations
boneAnimations.add(BoneAnimationData(
driver.bone, meshName, transformedF, frameLengthInMs));
driver.bone, boneMeshes!, transformedF, frameLengthInMs));
}
}

View File

@@ -5,8 +5,9 @@ import 'package:vector_math/vector_math.dart';
BoneDriver getLiveLinkFaceBoneDrivers(String bone) {
return BoneDriver(bone, {
"HeadPitch": Transformation(Quaternion.axisAngle(Vector3(1, 0, 0), pi / 2)),
"HeadRoll": Transformation(Quaternion.axisAngle(Vector3(0, 0, 1), pi / 2)),
"HeadPitch":
Transformation(Quaternion.axisAngle(Vector3(0, 0, -1), pi / 3)),
"HeadRoll": Transformation(Quaternion.axisAngle(Vector3(1, 0, 0), pi / 2)),
"HeadYaw": Transformation(Quaternion.axisAngle(Vector3(0, 1, 0), pi / 2)),
});
}

View File

@@ -320,32 +320,26 @@ class FilamentController {
/// Animates morph target weights/bone transforms (where each frame requires a duration of [frameLengthInMs].
/// [morphWeights] is a list of doubles in frame-major format.
/// Each frame is [numWeights] in length, and each entry is the weight to be applied to the morph target located at that index in the mesh primitive at that frame.
/// for now we only allow animating a single bone (though multiple skinned targets are supported)
///
void setBoneAnimation(
FilamentEntity asset, List<BoneAnimationData> animations) async {
// for future compatibility, instances of BoneAnimationData can specify individual mesh targets
// however on the rendering side we currently only allow one set of frame data for one mesh target (though multiple bones are supported).
// this is a check that all animations are targeting the same mesh
assert(animations.map((e) => e.meshName).toSet().length == 1);
var data =
calloc<Float>(animations.length * animations.first.frameData.length);
FilamentEntity asset, BoneAnimationData animation) async {
var data = calloc<Float>(animation.frameData.length);
int offset = 0;
var numFrames = animations.first.frameData.length ~/ 7;
var boneNames = calloc<Pointer<Char>>(animations.length);
int animIdx = 0;
for (var animation in animations) {
if (animation.frameData.length ~/ 7 != numFrames) {
throw Exception(
"All bone animations must share the same animation frame data length.");
}
for (int i = 0; i < animation.frameData.length; i++) {
data.elementAt(offset).value = animation.frameData[i];
offset += 1;
}
boneNames.elementAt(animIdx).value =
animation.boneName.toNativeUtf8().cast<Char>();
animIdx++;
var numFrames = animation.frameData.length ~/ 7;
var boneNames = calloc<Pointer<Char>>(1);
boneNames.elementAt(0).value =
animation.boneName.toNativeUtf8().cast<Char>();
var meshNames = calloc<Pointer<Char>>(animation.meshNames.length);
for (int i = 0; i < animation.meshNames.length; i++) {
meshNames.elementAt(i).value =
animation.meshNames[i].toNativeUtf8().cast<Char>();
}
for (int i = 0; i < animation.frameData.length; i++) {
data.elementAt(offset).value = animation.frameData[i];
offset += 1;
}
_nativeLibrary.set_bone_animation(
@@ -353,10 +347,11 @@ class FilamentController {
asset,
data,
numFrames,
animations.length,
1,
boneNames,
animations.first.meshName.toNativeUtf8().cast<Char>(),
animations.first.frameLengthInMs);
meshNames,
animation.meshNames.length,
animation.frameLengthInMs);
calloc.free(data);
}

View File

@@ -659,7 +659,8 @@ class NativeLibrary {
int numFrames,
int numBones,
ffi.Pointer<ffi.Pointer<ffi.Char>> boneNames,
ffi.Pointer<ffi.Char> meshName,
ffi.Pointer<ffi.Pointer<ffi.Char>> meshName,
int numMeshTargets,
double frameLengthInMs,
) {
return _set_bone_animation(
@@ -670,6 +671,7 @@ class NativeLibrary {
numBones,
boneNames,
meshName,
numMeshTargets,
frameLengthInMs,
);
}
@@ -683,7 +685,8 @@ class NativeLibrary {
ffi.Int,
ffi.Int,
ffi.Pointer<ffi.Pointer<ffi.Char>>,
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Pointer<ffi.Char>>,
ffi.Int,
ffi.Float)>>('set_bone_animation');
late final _set_bone_animation = _set_bone_animationPtr.asFunction<
void Function(
@@ -693,7 +696,8 @@ class NativeLibrary {
int,
int,
ffi.Pointer<ffi.Pointer<ffi.Char>>,
ffi.Pointer<ffi.Char>,
ffi.Pointer<ffi.Pointer<ffi.Char>>,
int,
double)>();
void play_animation(