merge in work on controller from webjs branch
This commit is contained in:
@@ -47,7 +47,7 @@ class AnimationBuilder {
|
||||
}
|
||||
}
|
||||
return MorphAnimationData(
|
||||
meshName,
|
||||
[meshName],
|
||||
morphData,
|
||||
_morphTargets.map((i) => availableMorphs[i]).toList(),
|
||||
_frameLengthInMs);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
///
|
||||
@@ -43,10 +41,21 @@ class MorphAnimationData {
|
||||
/// [frameData] is laid out as [locX, locY, locZ, rotW, rotX, rotY, rotZ]
|
||||
///
|
||||
class BoneAnimationData {
|
||||
final String boneName;
|
||||
final List<String> bones;
|
||||
final List<String> meshNames;
|
||||
final List<Quaternion> frameData;
|
||||
final List<List<Quaternion>> rotationFrameData;
|
||||
final List<List<Vector3>> translationFrameData;
|
||||
double frameLengthInMs;
|
||||
BoneAnimationData(
|
||||
this.boneName, this.meshNames, this.frameData, this.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);
|
||||
}
|
||||
}
|
||||
|
||||
139
lib/animations/bvh.dart
Normal file
139
lib/animations/bvh.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter_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