addBoneAnimation method
- ThermionEntity entity,
- BoneAnimationData animation, {
- int skinIndex = 0,
- double fadeInInSecs = 0.0,
- double fadeOutInSecs = 0.0,
- double maxDelta = 1.0,
Enqueues and plays the animation
for the specified bone(s).
By default, frame data is interpreted as being in parent bone space;
a 45 degree around Y means the bone will rotate 45 degrees around the
Y axis of the parent bone in its current orientation.
(i.e NOT the parent bone's rest position!).
Currently, only Space.ParentBone
and Space.Model
are supported; if you want
to transform to another space, you will need to do so manually.
fadeInInSecs
/fadeOutInSecs
/maxDelta
are used to cross-fade between
the current active glTF animation ("animation1") and the animation you
set via this method ("animation2"). The bone orientations will be
linearly interpolated between animation1 and animation2; at time 0,
the orientation will be 100% animation1, at time fadeInInSecs
, the
animation will be ((1 - maxDelta) * animation1) + (maxDelta * animation2).
This will be applied in reverse after fadeOutInSecs
.
Implementation
@override
Future addBoneAnimation(ThermionEntity entity, BoneAnimationData animation,
{int skinIndex = 0,
double fadeInInSecs = 0.0,
double fadeOutInSecs = 0.0,
double maxDelta = 1.0}) async {
final boneNames = await getBoneNames(entity);
final bones = await getBones(entity);
var numBytes = animation.numFrames * 16 * 4;
var floatPtr = _module._malloc(numBytes);
var restLocalTransforms = await getRestLocalTransforms(entity);
for (int i = 0; i < animation.bones.length; i++) {
final boneName = animation.bones[i];
final entityBoneIndex = boneNames.indexOf(boneName);
var boneEntity = bones[entityBoneIndex];
var baseTransform = restLocalTransforms[entityBoneIndex];
var world = Matrix4.identity();
// this odd use of ! is intentional, without it, the WASM optimizer gets in trouble
var parentBoneEntity = (await getParent(boneEntity))!;
while (true) {
if (!bones.contains(parentBoneEntity!)) {
break;
}
world = restLocalTransforms[bones.indexOf(parentBoneEntity!)] * world;
parentBoneEntity = (await getParent(parentBoneEntity))!;
}
world = Matrix4.identity()..setRotation(world.getRotation());
var worldInverse = Matrix4.identity()..copyInverse(world);
for (int frameNum = 0; frameNum < animation.numFrames; frameNum++) {
var rotation = animation.frameData[frameNum][i].rotation;
var translation = animation.frameData[frameNum][i].translation;
var frameTransform =
Matrix4.compose(translation, rotation, Vector3.all(1.0));
var newLocalTransform = frameTransform.clone();
if (animation.space == Space.Bone) {
newLocalTransform = baseTransform * frameTransform;
} else if (animation.space == Space.ParentWorldRotation) {
newLocalTransform =
baseTransform * (worldInverse * frameTransform * world);
}
for (int j = 0; j < 16; j++) {
var offset = ((frameNum * 16) + j) * 4;
_module.setValue((floatPtr.toDartInt + offset).toJS,
newLocalTransform.storage[j].toJS, "float");
}
}
_module.ccall(
"add_bone_animation",
"void",
[
"void*".toJS,
"int".toJS,
"int".toJS,
"int".toJS,
"float*".toJS,
"int".toJS,
"float".toJS,
"float".toJS,
"float".toJS,
"float".toJS
].toJS,
[
_sceneManager!,
entity.toJS,
skinIndex.toJS,
entityBoneIndex.toJS,
floatPtr,
animation.numFrames.toJS,
animation.frameLengthInMs.toJS,
fadeOutInSecs.toJS,
fadeInInSecs.toJS,
maxDelta.toJS
].toJS,
null);
}
_module._free(floatPtr);
}