addBoneAnimation method

  1. @override
Future addBoneAnimation(
  1. ThermionEntity entity,
  2. BoneAnimationData animation, {
  3. int skinIndex = 0,
  4. double fadeInInSecs = 0.0,
  5. double fadeOutInSecs = 0.0,
  6. double maxDelta = 1.0,
})
override

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);
}