1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
//! GPU mesh rendering for the Gizmo engine (wgpu pipelines, materials, instancing).
//!
//! ## Frustum culling (CPU-side, before instancing)
//!
//! The renderer does not iterate entities; **your render loop** builds the instance list. So the
//! cull is yours too, and how you write it decides whether the frame costs `O(visible)` or
//! `O(everything you own)`.
//!
//! **Hold a [`RenderAabbTree`] across frames and query it.** Insert each renderable's
//! world-space box once, update it when it moves, remove it when it dies, and each frame ask
//! for the keys that survive the camera frustum and every shadow cascade. What comes back is a
//! conservative *superset* of the visible set, so your exact test still runs — on a few hundred
//! candidates instead of every mesh you own.
//!
//! Note the shape of the loop below: it still walks **every** renderable and uses the candidate
//! set only to *skip*. That is not an accident, and it is not the same as iterating the
//! candidate list. See the comment on the guard.
//!
//! ```
//! use gizmo_math::{Aabb, Mat4, Vec3};
//! use gizmo_renderer::{classify_visibility_world, Frustum, RenderAabbTree, Visibility};
//! use gizmo_renderer::components::MaterialType;
//!
//! # let view_proj = Mat4::perspective_rh(std::f32::consts::FRAC_PI_4, 1.0, 0.1, 100.0)
//! # * Mat4::look_at_rh(Vec3::new(0.0, 0.0, 5.0), Vec3::ZERO, Vec3::Y);
//! # // Stands in for `mesh.bounds`: a unit cube in local space.
//! # let bounds = Aabb::new(Vec3::splat(-0.5), Vec3::splat(0.5));
//! // ── once, at load ──────────────────────────────────────────────────────────────
//! let mut index = RenderAabbTree::new();
//! let models = [
//! Mat4::from_translation(Vec3::new(0.0, 0.0, -10.0)), // in front of the camera
//! Mat4::from_translation(Vec3::new(0.0, 0.0, 50.0)), // behind it
//! ];
//! for (key, model) in models.iter().enumerate() {
//! // The key is yours to choose — an entity id, an index into your own array. It must be
//! // small and dense. Insert the box of the transform the mesh is actually DRAWN with.
//! index.insert(key as u32, bounds.transform(model));
//! }
//!
//! // ── every frame ────────────────────────────────────────────────────────────────
//! // ...`index.insert(key, new_box)` for whatever moved — an object that stayed inside its
//! // fat box costs one containment test and returns `false`.
//! // ...`index.insert(key, box)` for whatever was SPAWNED, too.
//! // ...`index.remove(key)`, or `index.retain(|k| still_alive(k))`, for whatever died.
//! let camera = Frustum::from_matrix(&view_proj);
//! let cascades: Vec<Frustum> = vec![]; // your shadow cascades, if you have them
//!
//! let mut candidates = Vec::new();
//! let mut frusta = vec![camera];
//! frusta.extend_from_slice(&cascades);
//! index.query_frusta(&frusta, &mut candidates); // sorted ascending, deduplicated
//!
//! # let mut instances = 0;
//! for (key, model) in models.iter().enumerate() {
//! let key = key as u32;
//! // THE GUARD — and note which way it fails. Skip a mesh only when the index KNOWS about
//! // it and did not nominate it. A key the index never received — spawned this frame,
//! // refused by `insert` (an empty `Mesh::bounds` is), deliberately not indexed (a
//! // camera-locked backdrop), or simply forgotten by your maintenance — falls through to
//! // the exact test and is drawn.
//! //
//! // Iterating `candidates` directly instead is the same loop with the `index.contains`
//! // half deleted, and it converts every maintenance gap into geometry that silently
//! // stops being drawn. Fail open: a false positive costs one exact test, a false
//! // negative is an invisible building.
//! if index.contains(key) && candidates.binary_search(&key).is_err() {
//! continue;
//! }
//! // The index is a SKIP FILTER, never the decision. Run the exact test on what survives.
//! let world_aabb = bounds.transform(model);
//! match classify_visibility_world(
//! &camera, &cascades, world_aabb, MaterialType::Pbr, false, 1.0,
//! ) {
//! Visibility::Culled => continue,
//! Visibility::Camera => { /* main passes */ }
//! Visibility::ShadowOnly => { /* shadow maps only */ }
//! }
//! # instances += 1;
//! // ...push an `InstanceRaw` for this mesh.
//! }
//! # assert_eq!(instances, 1, "the box behind the camera is culled, not instanced");
//! ```
//!
//! [`Mesh`](components::Mesh) carries a local-space [`Aabb`](gizmo_math::Aabb) (`bounds`);
//! [`Aabb::transform`](gizmo_math::Aabb::transform) puts it in world space. This pairs with
//! batched `draw(vertex_range, instance_start..instance_end)` so culled instances are never
//! written to the instance buffer.
//!
//! ### Which function to reach for
//!
//! * [`RenderAabbTree`] — the spatial index. Use it whenever you have more than a few hundred
//! renderables. See [`visibility`] for the correctness argument (the candidate set is a
//! superset by construction, so the draw list is unchanged) and for the two things that must
//! **not** be indexed: camera-locked materials, and anything whose drawn box you cannot
//! compute without the camera.
//! * [`classify_visibility_world`] — the exact per-object decision (camera / shadow-only /
//! culled) against a world-space box. One `Aabb::transform` for the camera *and* every
//! cascade.
//! * [`classify_visibility`] — the same, taking a model matrix and a local box. Convenience;
//! it transforms the box for you.
//! * [`visible_in_frustum`] — the single-object primitive, one frustum, no material logic.
//! Fine for a handful of objects, a debug overlay, or a one-off test.
//!
//! A spatial index answers "what might be on screen". If you also have a cell/region system for
//! streaming, LOD tiers or gameplay partitioning, **keep it** — a BVH has no stable region
//! identity and cannot answer those questions. The two are complementary.
//!
//! Implementation: [`frustum_cull`] re-exports [`Frustum`] and helpers from `gizmo-math`;
//! [`visibility`] holds the index.
pub use ;
pub use ;
pub use ;
// `NO_KEY` is deliberately NOT re-exported at the crate root — the name only means anything
// next to the index. Reach it as `gizmo_renderer::visibility::NO_KEY`.
pub use ;
pub use ;
pub use ;
pub use ;
pub use decompose_mat4;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use DecalState;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use AssetWatcher;
pub use SceneState;
pub use PostProcessState;
pub use ;
pub use ;
pub use SsgiState;
pub use TaaState;
pub use FxaaState;