move animations to animation_tools_dart
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user