flutter3d
Showcase Changelog 38 packages API reference

The shared camera rig: the source

since 0.4.0 or earlier Simulation, audio and XR

Regions the guide quotes: tuning (line 13), rig (line 36), place (line 59), settle (line 96).

1/// The part of a chasing view every chasing view has: easing towards where
2/// it should be, carrying a knock or a shake that fades, and staying out of
3/// the walls.
4///
5/// Quoted by `camera_shake.md` and shown whole in the Source tab.
6library;
7
8import 'package:flutter3d/flutter3d.dart';
9import 'package:flutter3d_showcase/src/demo/demo.dart';
10import 'package:flutter3d_sim/flutter3d_sim.dart';
11import 'package:vector_math/vector_math.dart';
13/// The six numbers `CameraRig` needs to place itself, gathered under a name
14/// this page's own view uses.
15final class _ChaseTuning extends RigTuning {
16 const _ChaseTuning()
17 : super(
18 distance: 4.0,
19 height: 1.5,
20 aimHeight: 1.0,
21 lag: 8.0,
22 nearClearance: 0.3,
23 minDistance: 1.0,
24 );
27final class CameraShakeDemo extends ShowcaseDemo {
28 bool shaken = false;
30 late final CameraRig _rig;
31 late final MeshNode _ball;
32 final Vector3 _target = Vector3.zero();
34 @override
35 Scene build(DemoContext context) {
36 _rig = CameraRig(world: CollisionWorld());
38 final material = Material(
39 name: 'runner',
40 baseColor: Vector4(0.6, 0.6, 0.9, 1.0),
41 );
42 _ball = MeshNode(
43 DeviceMesh.upload(context.device, SphereShape(segments: 16).build()),
44 material,
45 );
46 return Scene()
47 ..add(_ball)
48 ..add(
49 LightNode(name: 'sun', intensity: 3.0)
50 ..setLocalForward(Vector3(-0.4, -1.0, -0.3)),
51 );
52 }
54 @override
55 void configureView(DemoContext context) => context.orbit.frameBounds(
56 Aabb3.minMax(Vector3(-2, -2, -2), Vector3(2, 2, 2)),
57 );
59 static const _tuning = _ChaseTuning();
61 void _place(double dt) {
62 if (shaken) {
63 _rig.kick(Vector3(0, -0.4, 0));
64 _rig.shake(0.05, seconds: 0.3);
65 shaken = false;
66 }
67 final desiredEye = Vector3(0, _tuning.height, _tuning.distance);
68 _rig.place(
69 desiredEye: desiredEye,
70 desiredTarget: _target,
71 lag: _tuning.lag,
72 dt: dt,
73 );
74 }
76 @override
77 void update(DemoContext context, double dt) {
78 _place(dt);
79 _ball.setPositionFrom(_rig.eye);
80 }
82 @override
83 List<DemoControl> controls(DemoContext context) => <DemoControl>[
84 ToggleControl(
85 'Kick the rig',
86 value: () => shaken,
87 onChanged: (bool v) => shaken = v,
88 ),
89 ];
91 @override
92 void verify(Scene scene, FrameResult frame) {
93 if (frame.drawCalls < 1) {
94 throw StateError('the marker was not drawn');
95 }
96 // Placed enough times with nothing disturbing it, the rig settles to
97 // within a few millimetres of the free eye it is chasing.
98 for (var i = 0; i < 240; i++) {
99 _place(1 / 60);
100 }
101 final settledGap = (_rig.eye - _rig.freeEye).length;
102 if (settledGap > 0.01) {
103 throw StateError(
104 'an undisturbed rig should settle onto its free eye, '
105 'gap was $settledGap',
106 );
107 }
108 }