move animations to animation_tools_dart

This commit is contained in:
Nick Fisher
2024-04-26 11:03:30 +08:00
parent 7f365f985d
commit ce425a44f5
3 changed files with 0 additions and 354 deletions

View File

@@ -1,155 +0,0 @@
import 'package:flutter_filament/filament/animations/animation_data.dart';
import 'package:vector_math/vector_math.dart';
class AnimationBuilder {
// BoneAnimationData? BoneAnimationData;
double _frameLengthInMs = 0;
double _duration = 0;
double? _interpMorphStart;
double? _interpMorphEnd;
double? _interpMorphStartValue;
double? _interpMorphEndValue;
// List<BoneAnimationData>? _BoneAnimationDatas = null;
String meshName;
late List<String> availableMorphs;
late List<int> _morphTargets;
AnimationBuilder(
{required this.availableMorphs,
required this.meshName,
required int framerate}) {
_frameLengthInMs = 1000 / framerate;
}
MorphAnimationData build() {
if (availableMorphs.isEmpty || _duration == 0 || _frameLengthInMs == 0) {
throw Exception();
}
int numFrames = _duration * 1000 ~/ _frameLengthInMs;
final morphData =
List<double>.filled((numFrames * _morphTargets.length).toInt(), 0.0);
var frameStart = (_interpMorphStart! * 1000) ~/ _frameLengthInMs;
var frameEnd = (_interpMorphEnd! * 1000) ~/ _frameLengthInMs;
for (int i = frameStart; i < frameEnd; i++) {
var linear = (i - frameStart) / frameEnd;
var val = ((1 - linear) * _interpMorphStartValue!) +
(linear * _interpMorphEndValue!);
for (int j = 0; j < _morphTargets.length; j++) {
morphData[(i * _morphTargets.length) + j] = val;
}
}
return MorphAnimationData(
[meshName],
morphData,
_morphTargets.map((i) => availableMorphs[i]).toList(),
_frameLengthInMs);
}
AnimationBuilder setDuration(double secs) {
_duration = secs;
return this;
}
AnimationBuilder setMorphTargets(List<String> names) {
_morphTargets = names.map((name) => availableMorphs.indexOf(name)).toList();
return this;
}
AnimationBuilder interpolateMorphWeights(
double start, double end, double startValue, double endValue) {
_interpMorphStart = start;
_interpMorphEnd = end;
_interpMorphStartValue = startValue;
_interpMorphEndValue = endValue;
return this;
}
AnimationBuilder interpolateBoneTransform(
String boneName,
String meshName,
double start,
double end,
Vector3 transStart,
Vector3 transEnd,
Quaternion quatStart,
Quaternion quatEnd) {
var translations = <Vector3>[];
var quats = <Quaternion>[];
var frameStart = (start * 1000) ~/ _frameLengthInMs;
var frameEnd = (end * 1000) ~/ _frameLengthInMs;
int numFrames = _duration * 1000 ~/ _frameLengthInMs;
if (frameEnd > numFrames) {
throw Exception();
}
for (int i = 0; i < numFrames; i++) {
if (i >= frameStart && i < frameEnd) {
var linear = (i - frameStart) / (frameEnd - frameStart);
translations.add(Vector3(
((1 - linear) * transStart.x) + (linear * transEnd.x),
((1 - linear) * transStart.y) + (linear * transEnd.y),
((1 - linear) * transStart.z) + (linear * transEnd.z),
));
quats.add(Quaternion(
((1 - linear) * quatStart.x) + (linear * quatEnd.x),
((1 - linear) * quatStart.y) + (linear * quatEnd.y),
((1 - linear) * quatStart.z) + (linear * quatEnd.z),
((1 - linear) * quatStart.w) + (linear * quatEnd.w),
));
} else {
translations.add(Vector3.zero());
quats.add(Quaternion.identity());
}
}
throw Exception();
// var boneFrameData = BoneTransformFrameData(translations, quats);
// _BoneAnimationDatas ??= <BoneAnimationData>[];
// var frameData = List<List<double>>.generate(
// numFrames, (index) => boneFrameData.getFrameData(index).toList());
// var animData = Float32List.fromList(frameData.expand((x) => x).toList());
// _BoneAnimationDatas!.add(DartBoneAnimationData([boneName], [meshName], animData));
// return this;
}
}
class BoneTransformFrameData {
final List<Vector3> translations;
final List<Quaternion> quaternions;
///
/// The length of [translations] and [quaternions] must be the same;
/// each entry represents the Vec3/Quaternion for the given frame.
///
BoneTransformFrameData(this.translations, this.quaternions) {
if (translations.length != quaternions.length) {
throw Exception("Length of translation/quaternion frames must match");
}
}
Iterable<double> getFrameData(int frame) sync* {
yield translations[frame].x;
yield translations[frame].y;
yield translations[frame].z;
yield quaternions[frame].x;
yield quaternions[frame].y;
yield quaternions[frame].z;
yield quaternions[frame].w;
}
}

View File

@@ -1,61 +0,0 @@
import 'package:vector_math/vector_math_64.dart';
///
/// Specifies frame data (i.e. weights) to animate the morph targets contained in [morphTargets] under a mesh named [mesh].
/// [data] is laid out as numFrames x numMorphTargets.
/// Each frame is [numMorphTargets] in length, where the index of each weight corresponds to the respective index in [morphTargets].
/// [morphTargets] must be some subset of the actual morph targets under [mesh] (though the order of these does not need to match).
///
class MorphAnimationData {
final List<String> meshNames;
final List<String> morphTargets;
final List<double> data;
MorphAnimationData(
this.meshNames, this.data, this.morphTargets, this.frameLengthInMs) {
assert(data.length == morphTargets.length * numFrames);
}
int get numMorphTargets => morphTargets.length;
int get numFrames => data.length ~/ numMorphTargets;
final double frameLengthInMs;
Iterable<double> getData(String morphName) sync* {
int index = morphTargets.indexOf(morphName);
if (index == -1) {
throw Exception("No data for morph $morphName");
}
for (int i = 0; i < numFrames; i++) {
yield data[(i * numMorphTargets) + index];
}
}
}
///
/// Model class for bone animation frame data.
/// To create dynamic/runtime bone animations (as distinct from animations embedded in a glTF asset), create an instance containing the relevant
/// data and pass to the [setBoneAnimation] method on a [FilamentController].
/// [frameData] is laid out as [locX, locY, locZ, rotW, rotX, rotY, rotZ]
///
class BoneAnimationData {
final List<String> bones;
final List<String> meshNames;
final List<List<Quaternion>> rotationFrameData;
final List<List<Vector3>> translationFrameData;
double frameLengthInMs;
final bool isModelSpace;
BoneAnimationData(this.bones, this.meshNames, this.rotationFrameData,
this.translationFrameData, this.frameLengthInMs,
{this.isModelSpace = false});
int get numFrames => rotationFrameData.length;
BoneAnimationData frame(int frame) {
return BoneAnimationData(bones, meshNames, [rotationFrameData[frame]],
[translationFrameData[frame]], frameLengthInMs,
isModelSpace: isModelSpace);
}
}

View File

@@ -1,138 +0,0 @@
import 'dart:math';
import 'package:flutter_filament/filament/animations/animation_data.dart';
import 'package:vector_math/vector_math_64.dart';
enum RotationMode { ZYX, XYZ }
enum Axes { Filament, Blender }
Map<RotationMode, Matrix3> _permutations = {
RotationMode.XYZ: Matrix3.identity()
// RotationMode.ZYX:Matrix3.columns([],[],[]),
};
class BVHParser {
static Map<String, String> parseARPRemap(String arpRemapData) {
final remap = <String, String>{};
var data = arpRemapData.split("\n");
for (int i = 0; i < data.length;) {
var srcBone = data[i].split("%")[0];
if (srcBone.isNotEmpty && srcBone != "None") {
var targetBone = data[i + 1].trim();
remap[targetBone] = srcBone;
}
i += 5;
}
return remap;
}
static BoneAnimationData parse(String data, List<String> meshNames,
{Map<String, String>? remap,
RegExp? boneRegex,
RotationMode rotationMode = RotationMode.ZYX,
Vector3? rootTranslationOffset,
axes = Axes.Filament}) {
// parse the list/hierarchy of bones
final bones = <String>[];
double frameLengthInMs = 0.0;
var iter = data.split("\n").iterator;
while (iter.moveNext()) {
final line = iter.current.trim();
if (line.startsWith('ROOT') || line.startsWith('JOINT')) {
var bone = line.split(' ')[1];
if (remap?.containsKey(bone) == true) {
print("Remapping $bone to ${remap![bone]!}");
bone = remap![bone]!;
}
bones.add(bone);
} else if (line.startsWith('Frame Time:')) {
var frameTime = line.split(' ').last.trim();
frameLengthInMs =
double.parse(frameTime) * 1000; // Convert to milliseconds
print("Got frame time $frameTime frameLengthInMs $frameLengthInMs");
break;
}
}
// filter out any bones that don't match the regexp (if provided)
final boneIndices = boneRegex == null
? List<int>.generate(bones.length, (index) => index)
: bones.indexed
.where((bone) => boneRegex.hasMatch(bone.$2))
.map((bone) => bone.$1);
// the remaining lines contain the actual animation data
// we assume the first six are LOCX LOCY LOCZ ROTZ ROTY ROTX for the root bone, then ROTZ ROTY ROTX for the remainder
final translationData = <List<Vector3>>[];
final rotationData = <List<Quaternion>>[];
while (iter.moveNext()) {
final line = iter.current;
if (line.isEmpty) {
break;
}
var parseResult = _parseFrameData(line,
axes: axes, rootTranslationOffset: rootTranslationOffset);
rotationData.add(
boneIndices.map((idx) => parseResult.rotationData[idx]).toList());
translationData.add(<Vector3>[parseResult.translationData] +
List<Vector3>.filled(bones.length - 1, Vector3.zero()));
}
return BoneAnimationData(boneIndices.map((idx) => bones[idx]).toList(),
meshNames, rotationData, translationData, frameLengthInMs,
isModelSpace: true);
}
static ({List<Quaternion> rotationData, Vector3 translationData})
_parseFrameData(String frameLine,
{Vector3? rootTranslationOffset, axes = Axes.Filament}) {
final frameValues = <double>[];
for (final entry in frameLine.split(RegExp(r'\s+'))) {
if (entry.isNotEmpty) {
frameValues.add(double.parse(entry));
}
}
// first 3 values are root node position (translation), remainder are ZYX rotatons
// this is hardcoded assumption for BVH files generated by momask only; won't work for any other animations in general
// Blender exports BVH using same coordinate system (i.e. Z is up, Y points into screen)
// but Filament uses Y-up/Z forward
late Vector3 rootTranslation;
late Vector3 Z, Y, X;
if (axes == Axes.Blender) {
rootTranslation =
Vector3(frameValues[0], frameValues[2], -frameValues[1]);
Z = Vector3(0, 1, 0);
Y = Vector3(0, 0, -1);
X = Vector3(1, 0, 0);
} else {
rootTranslation = Vector3(
frameValues[0],
frameValues[1],
frameValues[2],
);
Z = Vector3(0, 0, 1);
Y = Vector3(0, 1, 0);
X = Vector3(1, 0, 0);
}
if (rootTranslationOffset != null) {
rootTranslation -= rootTranslationOffset;
}
List<Quaternion> frameData = [];
for (int i = 3; i < frameValues.length; i += 3) {
var raw = frameValues.sublist(i, i + 3);
var z = Quaternion.axisAngle(Z, radians(raw[0]));
var y = Quaternion.axisAngle(Y, radians(raw[1]));
var x = Quaternion.axisAngle(X, radians(raw[2]));
var rotation = z * y * x;
frameData.add(rotation.normalized());
}
return (rotationData: frameData, translationData: rootTranslation);
}
static double radians(double degrees) => degrees * (pi / 180.0);
}