concinnity_asset/reflection_probe.rs
1// Reflection-probe schema.
2
3/// A localized reflection probe. The renderer captures the surrounding scene
4/// into a cubemap from `position` and uses it for the specular reflection on
5/// glossy surfaces within the influence box (`position` plus or minus
6/// `half_extents`). The box is also the parallax-correction volume, so a
7/// reflection stays anchored to the surrounding geometry as the camera moves.
8///
9/// Place several across a level so reflections stay accurate as a first-person
10/// camera moves between areas (a room, a courtyard, a corridor): each surface
11/// uses the probe whose box it sits deepest inside, and cross-fades into the
12/// neighbouring box near a shared boundary so reflections don't pop as the camera
13/// crosses between them. When a world declares no `ReflectionProbe`, the renderer
14/// auto-seeds a small grid of probes from the scene bounds, so existing scenes
15/// still get local reflections without authoring.
16///
17/// Reflections are most accurate near `position`; a tighter box around a
18/// distinct space (a room) parallax-corrects better than one large box. Boxes may
19/// overlap freely: a surface inside several boxes blends all of them, so reflections
20/// cross-fade smoothly as the camera moves between probes.
21///
22/// ```rust
23/// # use concinnity_asset::ReflectionProbe;
24/// ReflectionProbe {
25/// position: [0.0, 1.7, 0.0],
26/// half_extents: [8.0, 4.0, 8.0],
27/// ..Default::default()
28/// };
29/// ```
30#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
31#[serde(default)]
32pub struct ReflectionProbe {
33 /// World-space capture point the cubemap is rendered from. Put it at roughly
34 /// eye height in open space (not inside geometry) for the area it serves.
35 pub position: [f32; 3],
36 /// Half-size of the influence box around `position`, per axis. A surface
37 /// inside `position` plus or minus `half_extents` may select this probe, and
38 /// the box is the parallax-correction volume. Make it span the local space
39 /// the probe represents (e.g. a room's walls).
40 pub half_extents: [f32; 3],
41}
42
43impl Default for ReflectionProbe {
44 fn default() -> Self {
45 Self {
46 position: [0.0, 1.7, 0.0],
47 half_extents: [10.0, 5.0, 10.0],
48 }
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn the_default_probe_captures_from_eye_height_over_a_room_sized_box() {
58 let p = ReflectionProbe::default();
59 assert_eq!(p.position, [0.0, 1.7, 0.0]);
60 // The parallax box is wider than it is tall, matching a room rather than
61 // a cube, so floor reflections land where the geometry actually is.
62 assert_eq!(p.half_extents, [10.0, 5.0, 10.0]);
63 }
64
65 #[test]
66 fn an_authored_probe_parses_and_round_trips_through_postcard() {
67 let p: ReflectionProbe =
68 serde_json::from_str(r#"{"position":[4,2,-6],"half_extents":[6,3,8]}"#).unwrap();
69 assert_eq!(p.position, [4.0, 2.0, -6.0]);
70
71 let bytes = postcard::to_allocvec(&p).unwrap();
72 let back: ReflectionProbe = postcard::from_bytes(&bytes).unwrap();
73 assert_eq!(back.half_extents, [6.0, 3.0, 8.0]);
74 }
75}