flutter3d
Showcase Changelog 38 packages API reference

Step, linear and cubic tracks: the source

since 0.7.0 Animation

Regions the guide quotes: tracks (line 48), player (line 71), live (line 127).

1/// Three cubes bounce on the same keyframes through three different
2/// `AnimationInterpolation`s: step, linear and cubic spline.
3///
4/// Quoted by `interpolation.md` and shown whole in the Source tab.
5library;
6
7import 'dart:typed_data';
8
9import 'package:flutter3d/flutter3d.dart';
10import 'package:flutter3d_showcase/src/demo/demo.dart';
11import 'package:vector_math/vector_math.dart';
13final class InterpolationDemo extends ShowcaseDemo {
14 late final AnimationPlayer _player;
15 late final MeshNode _stepCube;
16 late final MeshNode _linearCube;
17 late final MeshNode _cubicCube;
19 @override
20 void configureView(DemoContext context) {
21 context.orbit
22 ..distance = 5.5
23 ..pitch = 0.2;
24 }
26 @override
27 Scene build(DemoContext context) {
28 final DeviceMesh box = DeviceMesh.upload(
29 context.device,
30 CuboidShape(size: Vector3.all(0.6)).build(),
31 );
32 _stepCube = MeshNode(
33 box,
34 Material(name: 'step', baseColor: Vector4(0.85, 0.25, 0.2, 1.0)),
35 name: 'step',
36 );
37 _linearCube = MeshNode(
38 box,
39 Material(name: 'linear', baseColor: Vector4(0.25, 0.75, 0.3, 1.0)),
40 name: 'linear',
41 );
42 _cubicCube = MeshNode(
43 box,
44 Material(name: 'cubic', baseColor: Vector4(0.25, 0.4, 0.9, 1.0)),
45 name: 'cubic',
46 );
48 // The player writes a translation track's sampled (x, y, z) straight
49 // through `setPosition`, replacing whatever was there — so the column
50 // each cube stands in has to be baked into the track itself. A
51 // `setPosition` on the node instead would have held for exactly one
52 // frame, until `AnimationPlayer.play` below sampled time zero and
53 // snapped every cube back to x = 0, stacking all three on top of each
54 // other.
55 final AnimationTrack stepTrack = _bounceTrack(
56 nodeIndex: 0,
57 x: -1.4,
58 interpolation: AnimationInterpolation.step,
59 );
60 final AnimationTrack linearTrack = _bounceTrack(
61 nodeIndex: 1,
62 x: 0.0,
63 interpolation: AnimationInterpolation.linear,
64 );
65 final AnimationTrack cubicTrack = _bounceTrack(
66 nodeIndex: 2,
67 x: 1.4,
68 interpolation: AnimationInterpolation.cubicSpline,
69 );
71 final AnimationClip bounce = AnimationClip(
72 name: 'bounce',
73 tracks: <AnimationTrack>[stepTrack, linearTrack, cubicTrack],
74 );
75 _player = AnimationPlayer(
76 clips: <AnimationClip>[bounce],
77 targets: <AnimationTarget?>[_stepCube, _linearCube, _cubicCube],
78 )..play(0);
80 return Scene()
81 ..add(_stepCube)
82 ..add(_linearCube)
83 ..add(_cubicCube)
84 ..add(
85 LightNode(name: 'sun', intensity: 3.0)
86 ..setLocalForward(Vector3(-0.3, -0.8, -0.4)),
87 );
88 }
90 /// The same three keys, up and back down, on every track: only how the
91 /// player reads *between* them changes.
92 ///
93 /// A cubic key carries an in tangent, the value and an out tangent; flat
94 /// tangents (zero) are enough to show the ease the other two do not have.
95 static AnimationTrack _bounceTrack({
96 required int nodeIndex,
97 required double x,
98 required AnimationInterpolation interpolation,
99 }) {
100 final Float32List times = Float32List.fromList(<double>[0.0, 0.5, 1.0]);
101 final List<double> heights = <double>[0.0, 1.4, 0.0];
102 final int perKey = interpolation.valuesPerKey;
103 final Float32List values = Float32List(heights.length * 3 * perKey);
105 for (var i = 0; i < heights.length; i++) {
106 final int base = i * 3 * perKey;
107 // For a cubic key the value sits in the middle third; the in and out
108 // tangent thirds stay zero, which is a flat tangent — including x's,
109 // since it never moves and a flat tangent is what "never moves" means.
110 final int valueAt = base + (perKey == 3 ? 3 : 0);
111 values[valueAt] = x;
112 values[valueAt + 1] = heights[i];
113 }
115 return AnimationTrack(
116 nodeIndex: nodeIndex,
117 path: AnimationPath.translation,
118 interpolation: interpolation,
119 times: times,
120 values: values,
121 componentCount: 3,
122 );
123 }
125 @override
126 void update(DemoContext context, double dt) {
127 _player.update(dt);
128 }
130 @override
131 List<DemoControl> controls(DemoContext context) => <DemoControl>[
132 SliderControl(
133 'Speed',
134 min: 0.1,
135 max: 2,
136 value: () => _player.speed,
137 onChanged: (double v) => _player.speed = v,
138 ),
139 ];
141 @override
142 void verify(Scene scene, FrameResult frame) {
143 final Vector3 step = _stepCube.worldMatrix.getTranslation();
144 final Vector3 linear = _linearCube.worldMatrix.getTranslation();
145 final Vector3 cubic = _cubicCube.worldMatrix.getTranslation();
147 // Each track's baked x must survive the player writing translation
148 // through — a track that forgot it would snap every cube to x = 0 and
149 // stack the three on top of each other.
150 if ((step.x - -1.4).abs() > 1e-6 ||
151 linear.x.abs() > 1e-6 ||
152 (cubic.x - 1.4).abs() > 1e-6) {
153 throw StateError(
154 'the three cubes should stand apart at x = -1.4, 0, 1.4; got '
155 '${step.x}, ${linear.x}, ${cubic.x}',
156 );
157 }
158 if (step.y.abs() > 1e-6) {
159 throw StateError('step should hold the first key until the next one');
160 }
161 if (linear.y <= 1e-4) {
162 throw StateError('linear should already have left the first key');
163 }
164 if (cubic.y <= 0.0 || cubic.y >= linear.y) {
165 throw StateError(
166 'cubic should ease away from the first key slower than linear does',
167 );
168 }
169 }