Layers and masks: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: clips (line 52), layer (line 69), live (line 116).
1/// A walk on the base clip, a wave laid over just the arm: `AnimationLayer`
2/// and `AnimationMask` say which joints a second clip may touch while the
3/// base keeps playing everywhere else.
4///
5/// Quoted by `animation_layers.md` and shown whole in the Source tab.
6library;
8import 'dart:typed_data';
10import 'package:flutter3d/flutter3d.dart';
11import 'package:flutter3d_showcase/src/demo/demo.dart';
12import 'package:vector_math/vector_math.dart';
14final class AnimationLayersDemo extends ShowcaseDemo {
15 late final AnimationPlayer _player;
16 late final AnimationLayer _wave;
17 late final AnimationTrack _waveTrack;
18 late final SceneNode _body;
19 late final SceneNode _arm;
22 void configureView(DemoContext context) {
23 context.orbit
24 ..distance = 4.5
25 ..pitch = 0.2;
26 }
29 Scene build(DemoContext context) {
30 final DeviceMesh torso = DeviceMesh.upload(
31 context.device,
32 CuboidShape(size: Vector3(0.8, 1.4, 0.5)).build(),
33 );
34 final DeviceMesh limb = DeviceMesh.upload(
35 context.device,
36 CuboidShape(size: Vector3(0.2, 0.9, 0.2)).build(),
37 );
39 final MeshNode bodyMesh = MeshNode(
40 torso,
41 Material(name: 'body', baseColor: Vector4(0.5, 0.55, 0.65, 1.0)),
42 name: 'body',
43 );
44 final MeshNode armMesh = MeshNode(
45 limb,
46 Material(name: 'arm', baseColor: Vector4(0.85, 0.5, 0.2, 1.0)),
47 name: 'arm',
48 )..setPosition(0.6, 0.3, 0.0);
49 _body = bodyMesh;
50 _arm = armMesh;
52 final AnimationClip walk = AnimationClip(
53 name: 'walk',
54 tracks: <AnimationTrack>[
55 _rotationTrack(nodeIndex: 0, axis: Vector3(0, 0, 1), peak: 0.12),
56 _rotationTrack(nodeIndex: 1, axis: Vector3(1, 0, 0), peak: 0.5),
57 ],
58 );
59 _waveTrack = _rotationTrack(
60 nodeIndex: 1,
61 axis: Vector3(0, 0, 1),
62 peak: 1.1,
63 );
64 final AnimationClip wave = AnimationClip(
65 name: 'wave',
66 tracks: <AnimationTrack>[_waveTrack],
67 );
69 _player = AnimationPlayer(
70 clips: <AnimationClip>[walk, wave],
71 targets: <AnimationTarget?>[bodyMesh, armMesh],
72 )..play(0);
73 _wave = _player.playLayer(
74 1,
75 mask: AnimationMask(<int>[1]),
76 wrap: AnimationWrap.loop,
77 fadeIn: 0.0,
78 );
80 return Scene()
81 ..add(bodyMesh)
82 ..add(armMesh)
83 ..add(
84 LightNode(name: 'sun', intensity: 3.0)
85 ..setLocalForward(Vector3(-0.4, -0.7, -0.5)),
86 );
87 }
89 static AnimationTrack _rotationTrack({
90 required int nodeIndex,
91 required Vector3 axis,
92 required double peak,
93 }) {
94 final Float32List times = Float32List.fromList(<double>[0.0, 0.5, 1.0]);
95 final List<double> angles = <double>[0.0, peak, 0.0];
96 final Float32List values = Float32List(angles.length * 4);
97 for (var i = 0; i < angles.length; i++) {
98 final Quaternion q = Quaternion.axisAngle(axis, angles[i]);
99 values[i * 4] = q.x;
100 values[i * 4 + 1] = q.y;
101 values[i * 4 + 2] = q.z;
102 values[i * 4 + 3] = q.w;
103 }
104 return AnimationTrack(
105 nodeIndex: nodeIndex,
106 path: AnimationPath.rotation,
107 interpolation: AnimationInterpolation.linear,
108 times: times,
109 values: values,
110 componentCount: 4,
111 );
112 }
115 void update(DemoContext context, double dt) {
116 _player.update(dt);
117 }
120 void verify(Scene scene, FrameResult frame) {
121 if (!_wave.mask.covers(1) || _wave.mask.covers(0)) {
122 throw StateError('the mask should cover the arm and nothing else');
123 }
124 final Quaternion bodyRotation = Quaternion.fromRotation(
125 _body.worldMatrix.getRotation(),
126 );
127 if ((bodyRotation.w - 1.0).abs() < 1e-6) {
128 throw StateError('the base clip did not move the body');
129 }
131 final Float32List sample = Float32List(4);
132 _waveTrack.sample(_wave.time, sample);
133 final Quaternion armRotation = Quaternion.fromRotation(
134 _arm.worldMatrix.getRotation(),
135 );
136 final double dot =
137 (armRotation.x * sample[0] +
138 armRotation.y * sample[1] +
139 armRotation.z * sample[2] +
140 armRotation.w * sample[3])
141 .abs();
142 if (dot < 0.999) {
143 throw StateError("the arm's pose came from the base clip, not the layer");
144 }
145 }
146}