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 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 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 pub fn to_hyperslice_wgsl(&self, w_slice_expr: &str) -> String {
76 emit_hyperslice(self, w_slice_expr, None)
77 }
78
79 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 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;
100pub 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}