Skip to main content

loam_render/raymarch/scene/
scene4.rs

1use std::boxed::Box;
2
3use glam::{Vec3, Vec4};
4
5use super::literal::wgsl_f32;
6use super::primitive4::Primitive4;
7use super::SENTINEL_DISTANCE;
8pub use loam_shape::Shape;
9
10#[derive(Debug, Clone)]
11pub enum SceneNode4 {
12    Leaf(Shape),
13    Union(Box<SceneNode4>, Box<SceneNode4>),
14    Intersection(Box<SceneNode4>, Box<SceneNode4>),
15    Difference(Box<SceneNode4>, Box<SceneNode4>),
16}
17
18impl SceneNode4 {
19    pub fn hypersphere(center: Vec4, radius: f32) -> Self {
20        SceneNode4::Leaf(Shape::HyperSphere4D { center, radius })
21    }
22
23    pub fn halfspace(normal: Vec4, offset: f32) -> Self {
24        SceneNode4::Leaf(Shape::HalfSpace4D { normal, offset })
25    }
26
27    /// The static `Primitive4` emit returns a sentinel; polytope leaves are invisible.
28    pub fn polytope(vertices: Vec<Vec4>) -> Self {
29        SceneNode4::Leaf(Shape::ConvexPolytope4D { vertices })
30    }
31
32    pub fn union(self, other: SceneNode4) -> Self {
33        SceneNode4::Union(Box::new(self), Box::new(other))
34    }
35
36    pub fn intersect(self, other: SceneNode4) -> Self {
37        SceneNode4::Intersection(Box::new(self), Box::new(other))
38    }
39
40    pub fn subtract(self, other: SceneNode4) -> Self {
41        SceneNode4::Difference(Box::new(self), Box::new(other))
42    }
43}
44
45#[derive(Debug, Clone)]
46pub struct Scene4 {
47    pub root: SceneNode4,
48}
49
50impl Scene4 {
51    pub fn new(root: SceneNode4) -> Self {
52        Self { root }
53    }
54
55    /// Emits `fn loam_scene_sdf_4d(p: vec4<f32>) -> f32`.
56    pub fn to_wgsl_4d(&self) -> String {
57        let mut helpers = String::new();
58        let mut body = String::new();
59        let mut counter = 0u32;
60        let (d_root, _k_root) =
61            emit_node_4d(&self.root, &mut counter, &mut helpers, &mut body, None);
62        let kind_consts = scene_kind_constants();
63        format!(
64            "// ---- loam-scene scene4 (native 4D) ----\n\
65             {kind_consts}\
66             {helpers}\
67             fn loam_scene_sdf_4d(p: vec4<f32>) -> f32 {{\n\
68             {body}\
69             \treturn {d_root};\n\
70             }}\n"
71        )
72    }
73
74    /// Emits scene distance, primitive kind and march bounds at the supplied WGSL slice expression.
75    pub fn to_hyperslice_wgsl(&self, w_slice_expr: &str) -> String {
76        emit_hyperslice(self, w_slice_expr, None)
77    }
78
79    /// Halfspaces return [`SENTINEL_DISTANCE`] when the supplied WGSL f32 expression is below 0.5.
80    pub fn to_hyperslice_wgsl_gated(
81        &self,
82        w_slice_expr: &str,
83        halfspace_gate_expr: &str,
84    ) -> String {
85        emit_hyperslice(self, w_slice_expr, Some(halfspace_gate_expr))
86    }
87
88    /// Evaluates a slice; disabling `halfspace_gate` replaces halfspaces with [`SENTINEL_DISTANCE`].
89    pub fn eval_at(&self, p3: Vec3, w_slice: f32, halfspace_gate: bool) -> (f32, u32) {
90        eval_node_4d(&self.root, p3.extend(w_slice), halfspace_gate)
91    }
92
93    pub fn eval(&self, p3: Vec3, w_slice: f32, halfspace_gate: bool) -> f32 {
94        self.eval_at(p3, w_slice, halfspace_gate).0
95    }
96}
97
98pub const PRIM_KIND_HYPERSPHERE4D: u32 = 0;
99pub const PRIM_KIND_HALFSPACE4D: u32 = 1;
100/// A `Difference` node, which has no single owning primitive, or a leaf with no 4D closed form.
101pub const PRIM_KIND_OTHER: u32 = 255;
102
103fn scene_kind_constants() -> String {
104    format!(
105        "const LOAM_PRIM_HYPERSPHERE4D: u32 = {PRIM_KIND_HYPERSPHERE4D}u;\n\
106         const LOAM_PRIM_HALFSPACE4D: u32 = {PRIM_KIND_HALFSPACE4D}u;\n\
107         const LOAM_PRIM_OTHER: u32 = {PRIM_KIND_OTHER}u;\n"
108    )
109}
110
111fn emit_hyperslice(
112    scene: &Scene4,
113    w_slice_expr: &str,
114    halfspace_gate_expr: Option<&str>,
115) -> String {
116    let mut helpers = String::new();
117    let mut body = String::new();
118    let mut counter = 0u32;
119    let (d_root, k_root) = emit_node_4d(
120        &scene.root,
121        &mut counter,
122        &mut helpers,
123        &mut body,
124        halfspace_gate_expr,
125    );
126    let kind_consts = scene_kind_constants();
127    let max_t_body = emit_max_t_body(&scene.root, w_slice_expr, halfspace_gate_expr);
128
129    format!(
130        "// ---- loam-scene scene4 (hyperslice at w = {w_slice_expr}) ----\n\
131         {kind_consts}\
132         struct LoamSceneHit {{ dist: f32, kind: u32 }}\n\
133         {helpers}\
134         fn loam_scene_at(p3: vec3<f32>) -> LoamSceneHit {{\n\
135         \tlet p = vec4<f32>(p3, {w_slice_expr});\n\
136         {body}\
137         \treturn LoamSceneHit({d_root}, {k_root});\n\
138         }}\n\
139         fn loam_scene_sdf(p3: vec3<f32>) -> f32 {{\n\
140         \treturn loam_scene_at(p3).dist;\n\
141         }}\n\
142         fn loam_scene_max_t(ro: vec3<f32>, rd: vec3<f32>) -> f32 {{\n\
143         \tvar t_max: f32 = {SENTINEL_DISTANCE:e};\n\
144         {max_t_body}\
145         \treturn t_max;\n\
146         }}\n"
147    )
148}
149
150fn emit_max_t_body(
151    node: &SceneNode4,
152    w_slice_expr: &str,
153    halfspace_gate_expr: Option<&str>,
154) -> String {
155    let mut body = String::new();
156    walk_max_t(node, &mut body, w_slice_expr, halfspace_gate_expr);
157    body
158}
159
160fn walk_max_t(
161    node: &SceneNode4,
162    body: &mut String,
163    w_slice_expr: &str,
164    halfspace_gate_expr: Option<&str>,
165) {
166    match node {
167        SceneNode4::Leaf(Shape::HalfSpace4D { normal, offset }) => {
168            let inner = format!(
169                "\t\tlet n = vec3<f32>({nx}, {ny}, {nz});\n\
170                 \t\tlet dr = dot(rd, n);\n\
171                 \t\tif (dr < -1.0e-4) {{\n\
172                 \t\t\tlet t = (({offset}) - ({nw}) * ({w_slice_expr}) - dot(ro, n)) / dr;\n\
173                 \t\t\tif (t > 0.0 && t < t_max) {{ t_max = t; }}\n\
174                 \t\t}}\n",
175                nw = wgsl_f32(normal.w),
176                nx = wgsl_f32(normal.x),
177                ny = wgsl_f32(normal.y),
178                nz = wgsl_f32(normal.z),
179                offset = wgsl_f32(*offset),
180            );
181            match halfspace_gate_expr {
182                None => {
183                    body.push_str("\t{\n");
184                    body.push_str(&inner);
185                    body.push_str("\t}\n");
186                }
187                Some(gate) => {
188                    body.push_str(&format!("\tif ({gate} >= 0.5) {{\n"));
189                    body.push_str(&inner);
190                    body.push_str("\t}\n");
191                }
192            }
193        }
194        SceneNode4::Leaf(_) | SceneNode4::Intersection(..) | SceneNode4::Difference(..) => {}
195        SceneNode4::Union(l, r) => {
196            walk_max_t(l, body, w_slice_expr, halfspace_gate_expr);
197            walk_max_t(r, body, w_slice_expr, halfspace_gate_expr);
198        }
199    }
200}
201
202fn primitive_kind(shape: &Shape) -> (&'static str, u32) {
203    match shape {
204        Shape::HyperSphere4D { .. } => ("LOAM_PRIM_HYPERSPHERE4D", PRIM_KIND_HYPERSPHERE4D),
205        Shape::HalfSpace4D { .. } => ("LOAM_PRIM_HALFSPACE4D", PRIM_KIND_HALFSPACE4D),
206        _ => ("LOAM_PRIM_OTHER", PRIM_KIND_OTHER),
207    }
208}
209
210fn emit_node_4d(
211    node: &SceneNode4,
212    counter: &mut u32,
213    helpers: &mut String,
214    body: &mut String,
215    halfspace_gate_expr: Option<&str>,
216) -> (String, String) {
217    let idx = *counter;
218    *counter += 1;
219    match node {
220        SceneNode4::Leaf(prim) => {
221            let fn_name = format!("sdf4_p{idx}");
222            helpers.push_str(&prim.to_wgsl_4d(&fn_name));
223            let d_var = format!("d{idx}");
224            let k_var = format!("k{idx}");
225            let (kind, _kind_value) = primitive_kind(prim);
226            let gated = matches!(prim, Shape::HalfSpace4D { .. }) && halfspace_gate_expr.is_some();
227            if gated {
228                let gate = halfspace_gate_expr.expect("gated branch implies Some");
229                body.push_str(&format!("\tlet {d_var}_raw = {fn_name}(p);\n"));
230                body.push_str(&format!(
231                    "\tlet {d_var} = select({SENTINEL_DISTANCE:e}, {d_var}_raw, {gate} >= 0.5);\n"
232                ));
233            } else {
234                body.push_str(&format!("\tlet {d_var} = {fn_name}(p);\n"));
235            }
236            body.push_str(&format!("\tlet {k_var}: u32 = {kind};\n"));
237            (d_var, k_var)
238        }
239        SceneNode4::Union(left, right) => {
240            let (ld, lk) = emit_node_4d(left, counter, helpers, body, halfspace_gate_expr);
241            let (rd, rk) = emit_node_4d(right, counter, helpers, body, halfspace_gate_expr);
242            let d_var = format!("d{idx}");
243            let k_var = format!("k{idx}");
244            body.push_str(&format!("\tlet {d_var} = min({ld}, {rd});\n"));
245            body.push_str(&format!(
246                "\tlet {k_var}: u32 = select({rk}, {lk}, {ld} <= {rd});\n"
247            ));
248            (d_var, k_var)
249        }
250        SceneNode4::Intersection(left, right) => {
251            let (ld, lk) = emit_node_4d(left, counter, helpers, body, halfspace_gate_expr);
252            let (rd, rk) = emit_node_4d(right, counter, helpers, body, halfspace_gate_expr);
253            let d_var = format!("d{idx}");
254            let k_var = format!("k{idx}");
255            body.push_str(&format!("\tlet {d_var} = max({ld}, {rd});\n"));
256            body.push_str(&format!(
257                "\tlet {k_var}: u32 = select({rk}, {lk}, {ld} >= {rd});\n"
258            ));
259            (d_var, k_var)
260        }
261        SceneNode4::Difference(left, right) => {
262            let (ld, _lk) = emit_node_4d(left, counter, helpers, body, halfspace_gate_expr);
263            let (rd, _rk) = emit_node_4d(right, counter, helpers, body, halfspace_gate_expr);
264            let d_var = format!("d{idx}");
265            let k_var = format!("k{idx}");
266            body.push_str(&format!("\tlet {d_var} = max({ld}, -({rd}));\n"));
267
268            body.push_str(&format!("\tlet {k_var}: u32 = LOAM_PRIM_OTHER;\n"));
269            (d_var, k_var)
270        }
271    }
272}
273
274fn eval_node_4d(node: &SceneNode4, p: Vec4, halfspace_gate: bool) -> (f32, u32) {
275    match node {
276        SceneNode4::Leaf(prim) => {
277            let (_kind_name, kind) = primitive_kind(prim);
278            let gated = matches!(prim, Shape::HalfSpace4D { .. }) && !halfspace_gate;
279            let dist = if gated {
280                SENTINEL_DISTANCE
281            } else {
282                prim.eval_4d(p)
283            };
284            (dist, kind)
285        }
286        SceneNode4::Union(left, right) => {
287            let (ld, lk) = eval_node_4d(left, p, halfspace_gate);
288            let (rd, rk) = eval_node_4d(right, p, halfspace_gate);
289            (ld.min(rd), if ld <= rd { lk } else { rk })
290        }
291        SceneNode4::Intersection(left, right) => {
292            let (ld, lk) = eval_node_4d(left, p, halfspace_gate);
293            let (rd, rk) = eval_node_4d(right, p, halfspace_gate);
294            (ld.max(rd), if ld >= rd { lk } else { rk })
295        }
296        SceneNode4::Difference(left, right) => {
297            let (ld, _lk) = eval_node_4d(left, p, halfspace_gate);
298            let (rd, _rk) = eval_node_4d(right, p, halfspace_gate);
299            (ld.max(-rd), PRIM_KIND_OTHER)
300        }
301    }
302}