A pose with no scene
Open the live demo · Read the source · View on GitHub
Most of this set drives a clip onto scene nodes: AnimationPlayer needs
targets, and a target is normally a node in a running scene. Pose is the
other half. It is a hierarchy's local transforms as three flat arrays, sampled
straight from a clip with no scene node anywhere in the arithmetic — the
shape a bake, a retarget pass or an offline tool over a thousand frames wants,
where building a scene just to throw it away would be real allocation for a
question that is only arithmetic.
Step 1: The hierarchy, as arrays #
Two joints, a root and a tip, each with a parent index and a rest local
transform. Nothing here is a SceneNode.
_pose = Pose(
parents: <int>[-1, 0],
restTranslations: Float32List.fromList(<double>[
0.0,
0.0,
0.0,
0.0,
-1.2,
0.0,
]),
restRotations: Float32List.fromList(<double>[
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
1.0,
]),
restScales: Float32List.fromList(<double>[1.0, 1.0, 1.0, 1.0, 1.0, 1.0]),
);
Step 2: A clip to sample #
An ordinary swing on the root's rotation. Rotating a joint never moves the
joint itself, only whatever hangs off it — the same rule a real skeleton
follows — so this is what carries the tip through an arc rather than leaving
it in place. Pose reads the track exactly the way AnimationPlayer would,
through the same AnimationTrack.sample.
final Float32List times = Float32List.fromList(<double>[0.0, 0.75, 1.5]);
final List<double> angles = <double>[-0.6, 0.6, -0.6];
final Float32List values = Float32List(angles.length * 4);
for (var i = 0; i < angles.length; i++) {
final Quaternion q = Quaternion.axisAngle(
Vector3(1.0, 0.0, 0.0),
angles[i],
);
values[i * 4] = q.x;
values[i * 4 + 1] = q.y;
values[i * 4 + 2] = q.z;
values[i * 4 + 3] = q.w;
}
_swing = AnimationClip(
name: 'swing',
tracks: <AnimationTrack>[
AnimationTrack(
nodeIndex: 0,
path: AnimationPath.rotation,
interpolation: AnimationInterpolation.linear,
times: times,
values: values,
componentCount: 4,
),
],
);
Step 3: Sample it, then look at the result #
sampleClip resets the pose to rest and writes every track's value onto it.
worldMatrices composes the whole hierarchy from those local transforms.
Only after both of those does this page read a translation out to place a
marker — the pose itself never heard of the markers.
_time = (_time + dt) % _swing.duration;
_pose.sampleClip(_swing, _time);
final List<Matrix4> world = _pose.worldMatrices();
_rootMarker.setPositionFrom(world[0].getTranslation());
_tipMarker.setPositionFrom(world[1].getTranslation());
What to look at #
The orange marker swings under the grey one, which never moves: the pose holds exactly what the clip says and nothing more, whether or not anything is drawing it.