flutter3d
Showcase Changelog 38 packages API reference

Baked visibility: the source

since 0.7.0 Simulation, audio and XR

Regions the guide quotes: level (line 38), bake (line 49), cull (line 66), live (line 83).

1/// Which parts of a level can be seen from where, decided before the game
2/// runs, and applied every frame by hiding the batches a camera cannot see.
3///
4/// Quoted by `baked_visibility.md` and shown whole in the Source tab.
5library;
6
7import 'dart:math' as math;
8
9import 'package:flutter3d/flutter3d.dart' hide Material;
10import 'package:flutter3d/flutter3d.dart' as f3d show Material;
11import 'package:flutter3d_app/flutter3d_app.dart';
12import 'package:flutter3d_showcase/src/demo/demo.dart';
13import 'package:flutter3d_showcase/src/demo/scene_kit.dart';
14import 'package:flutter3d_sim/flutter3d_sim.dart';
15import 'package:vector_math/vector_math.dart';
17final class BakedVisibilityDemo extends ShowcaseDemo {
18 late final String _report;
20 double eyeX = 0.0;
21 bool walking = true;
23 late final VisibilityCuller _live;
24 late final MeshNode _eye;
25 double _clock = 0.0;
27 @override
28 void configureView(DemoContext context) {
29 context.orbit
30 ..distance = 15.0
31 ..pitch = 0.85
32 ..yaw = 0.0;
33 context.orbit.target.setValues(5.0, 0.0, 2.0);
34 }
36 @override
37 Scene build(DemoContext context) {
38 // A floor the whole way along, and one solid wall crossing it a third of
39 // the way down: nothing on the near side of the wall can be seen from
40 // the far side.
41 final level = Level(
42 name: 'corridor',
43 brushes: <Brush>[
44 Brush(centre: Vector3(5, -2, 2), size: Vector3(14, 4, 4)),
45 Brush(centre: Vector3(5, 1, 2), size: Vector3(1.0, 8, 4)),
46 ],
47 );
49 final visibility = LevelVisibility.bake(level, cellSize: 1.0);
51 final material = f3d.Material(
52 name: 'room',
53 baseColor: Vector4(0.6, 0.6, 0.8, 1.0),
54 );
55 final nearMarker = MeshNode(
56 DeviceMesh.upload(context.device, CuboidShape().build()),
57 material,
58 name: 'near',
59 )..setPositionFrom(Vector3(0, 0.5, 2));
60 final farMarker = MeshNode(
61 DeviceMesh.upload(context.device, CuboidShape().build()),
62 material,
63 name: 'far',
64 )..setPositionFrom(Vector3(10, 0.5, 2));
66 final culler = VisibilityCuller(visibility, <VisibilityBatch>[
67 (
68 node: nearMarker,
69 bounds: Aabb3.minMax(Vector3(-1, 0, 1), Vector3(1, 2, 3)),
70 ),
71 (
72 node: farMarker,
73 bounds: Aabb3.minMax(Vector3(9, 0, 1), Vector3(11, 2, 3)),
74 ),
75 ]);
76 final hiddenFromNearSide = culler.apply(Vector3(0, 0.2, 2));
78 _report =
79 'standing on the near side, $hiddenFromNearSide of 2 batches are '
80 'hidden; near marker visible: ${nearMarker.visible}, far marker '
81 'visible: ${farMarker.visible}';
83 // The same culler, over four markers, asked again from wherever the eye
84 // is each frame: a marker is on when the eye's own cell can see it.
85 const List<double> stands = <double>[0.0, 1.5, 8.5, 10.0];
86 final List<MeshNode> markers = <MeshNode>[
87 for (final double x in stands)
88 blockNode(
89 context,
90 'marker $x',
91 Vector3(1.4, 1.4, 1.4),
92 Vector4(0.85, 0.6, 0.35, 1.0),
93 at: Vector3(x, 0.7, 2.0),
94 ),
95 ];
96 _live = VisibilityCuller(visibility, <VisibilityBatch>[
97 for (final MeshNode marker in markers)
98 (
99 node: marker,
100 bounds: Aabb3.minMax(
101 marker.readPosition() - Vector3.all(0.7),
102 marker.readPosition() + Vector3.all(0.7),
103 ),
104 ),
105 ]);
106 _eye = ballNode(
107 context,
108 'eye',
109 0.35,
110 Vector4(0.45, 0.65, 0.95, 1.0),
111 at: Vector3(0.0, 0.35, 2.0),
112 );
113 return sceneOf(<SceneNode>[
114 blockNode(
115 context,
116 'floor',
117 Vector3(14.0, 0.1, 4.0),
118 Vector4(0.34, 0.38, 0.36, 1.0),
119 at: Vector3(5.0, -0.05, 2.0),
120 ),
121 blockNode(
122 context,
123 'wall',
124 Vector3(1.0, 3.0, 4.0),
125 Vector4(0.55, 0.5, 0.5, 1.0),
126 at: Vector3(5.0, 1.5, 2.0),
127 ),
128 _eye,
129 ...markers,
130 ]);
131 }
133 @override
134 void update(DemoContext context, double dt) {
135 _clock += dt;
136 if (walking) eyeX = 5.0 + 5.8 * math.sin(_clock * 0.4);
137 _eye.setPosition(eyeX, 0.35, 2.0);
138 _live.apply(Vector3(eyeX, 0.2, 2.0));
139 }
141 @override
142 List<DemoControl> controls(DemoContext context) => <DemoControl>[
143 SliderControl(
144 'Eye at',
145 min: -1.0,
146 max: 10.9,
147 value: () => eyeX,
148 onChanged: (double v) {
149 walking = false;
150 eyeX = v;
151 },
152 format: (double v) => '${v.toStringAsFixed(1)} m',
153 ),
154 ToggleControl(
155 'Walk about',
156 value: () => walking,
157 onChanged: (bool v) => walking = v,
158 ),
159 ];
161 @override
162 void verify(Scene scene, FrameResult frame) {
163 if (frame.drawCalls < 1) {
164 throw StateError('a marker was not drawn');
165 }
166 if (!_report.contains('near marker visible: true')) {
167 throw StateError('the side the eye stands on should stay visible');
168 }
169 if (!_report.contains('far marker visible: false')) {
170 throw StateError(
171 'the far side of a solid wall with no doorway should be hidden',
172 );
173 }
174 }