1#![allow(clippy::needless_range_loop)]
6use super::types::{
7 ContactPoint, IsoMeshResult, RayMarchResult, SdfCollisionResult, SdfGrid, Triangle,
8};
9
10#[inline]
11pub(super) fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
12 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
13}
14#[inline]
15pub(super) fn len(v: [f64; 3]) -> f64 {
16 dot(v, v).sqrt()
17}
18#[inline]
19pub(super) fn norm(v: [f64; 3]) -> [f64; 3] {
20 let l = len(v).max(1e-300);
21 [v[0] / l, v[1] / l, v[2] / l]
22}
23#[inline]
24pub(super) fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
25 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
26}
27#[inline]
28pub(super) fn add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
29 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
30}
31#[inline]
32pub(super) fn scale(v: [f64; 3], s: f64) -> [f64; 3] {
33 [v[0] * s, v[1] * s, v[2] * s]
34}
35#[inline]
36pub(super) fn clamp01(v: f64) -> f64 {
37 v.clamp(0.0, 1.0)
38}
39#[inline]
40pub(super) fn clamp_f(v: f64, lo: f64, hi: f64) -> f64 {
41 v.clamp(lo, hi)
42}
43#[inline]
44pub(super) fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
45 [
46 a[1] * b[2] - a[2] * b[1],
47 a[2] * b[0] - a[0] * b[2],
48 a[0] * b[1] - a[1] * b[0],
49 ]
50}
51#[inline]
52pub(super) fn len2(v: [f64; 2]) -> f64 {
53 (v[0] * v[0] + v[1] * v[1]).sqrt()
54}
55#[inline]
56pub(super) fn dot2(a: [f64; 2], b: [f64; 2]) -> f64 {
57 a[0] * b[0] + a[1] * b[1]
58}
59pub trait Sdf: Send + Sync {
64 fn dist(&self, p: [f64; 3]) -> f64;
66}
67pub fn sdf_smooth_union(a: f64, b: f64, k: f64) -> f64 {
71 if k < 1e-15 {
72 return a.min(b);
73 }
74 let h = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0);
75 a * h + b * (1.0 - h) - k * h * (1.0 - h)
76}
77pub fn sdf_smooth_intersection(a: f64, b: f64, k: f64) -> f64 {
79 if k < 1e-15 {
80 return a.max(b);
81 }
82 let h = (0.5 - 0.5 * (b - a) / k).clamp(0.0, 1.0);
83 a * (1.0 - h) + b * h + k * h * (1.0 - h)
84}
85pub fn sdf_smooth_difference(a: f64, b: f64, k: f64) -> f64 {
87 sdf_smooth_intersection(a, -b, k)
88}
89pub fn sdf_gradient<S: Sdf>(sdf: &S, p: [f64; 3], eps: f64) -> [f64; 3] {
93 let dx = sdf.dist([p[0] + eps, p[1], p[2]]) - sdf.dist([p[0] - eps, p[1], p[2]]);
94 let dy = sdf.dist([p[0], p[1] + eps, p[2]]) - sdf.dist([p[0], p[1] - eps, p[2]]);
95 let dz = sdf.dist([p[0], p[1], p[2] + eps]) - sdf.dist([p[0], p[1], p[2] - eps]);
96 [dx / (2.0 * eps), dy / (2.0 * eps), dz / (2.0 * eps)]
97}
98pub fn sdf_normal<S: Sdf>(sdf: &S, p: [f64; 3], eps: f64) -> [f64; 3] {
100 norm(sdf_gradient(sdf, p, eps))
101}
102pub fn sdf_mean_curvature<S: Sdf>(sdf: &S, p: [f64; 3], eps: f64) -> f64 {
106 let d = sdf.dist(p);
107 let dxp = sdf.dist([p[0] + eps, p[1], p[2]]);
108 let dxm = sdf.dist([p[0] - eps, p[1], p[2]]);
109 let dyp = sdf.dist([p[0], p[1] + eps, p[2]]);
110 let dym = sdf.dist([p[0], p[1] - eps, p[2]]);
111 let dzp = sdf.dist([p[0], p[1], p[2] + eps]);
112 let dzm = sdf.dist([p[0], p[1], p[2] - eps]);
113 let laplacian = (dxp + dxm + dyp + dym + dzp + dzm - 6.0 * d) / (eps * eps);
114 let g = sdf_gradient(sdf, p, eps);
115 let g_len = len(g).max(1e-30);
116 -0.5 * laplacian / g_len
117}
118pub fn ray_march<S: Sdf>(
126 sdf: &S,
127 origin: [f64; 3],
128 direction: [f64; 3],
129 max_steps: u32,
130 max_t: f64,
131 surf_eps: f64,
132) -> RayMarchResult {
133 let dir = norm(direction);
134 let mut t = 0.0_f64;
135 for step in 0..max_steps {
136 let p = add(origin, scale(dir, t));
137 let d = sdf.dist(p);
138 if d.abs() < surf_eps {
139 return RayMarchResult {
140 hit: true,
141 t,
142 point: p,
143 steps: step + 1,
144 };
145 }
146 t += d.abs().max(surf_eps * 0.1);
147 if t > max_t {
148 break;
149 }
150 }
151 RayMarchResult {
152 hit: false,
153 t,
154 point: add(origin, scale(dir, t)),
155 steps: max_steps,
156 }
157}
158pub fn ray_march_relaxed<S: Sdf>(
163 sdf: &S,
164 origin: [f64; 3],
165 direction: [f64; 3],
166 max_steps: u32,
167 max_t: f64,
168 surf_eps: f64,
169 omega: f64,
170) -> RayMarchResult {
171 let dir = norm(direction);
172 let mut t = 0.0_f64;
173 let mut prev_d = f64::INFINITY;
174 for step in 0..max_steps {
175 let p = add(origin, scale(dir, t));
176 let d = sdf.dist(p);
177 if d.abs() < surf_eps {
178 return RayMarchResult {
179 hit: true,
180 t,
181 point: p,
182 steps: step + 1,
183 };
184 }
185 let step_length = if omega * d.abs() <= d.abs() + prev_d.abs() {
186 omega * d.abs()
187 } else {
188 d.abs()
189 };
190 prev_d = d;
191 t += step_length.max(surf_eps * 0.1);
192 if t > max_t {
193 break;
194 }
195 }
196 RayMarchResult {
197 hit: false,
198 t,
199 point: add(origin, scale(dir, t)),
200 steps: max_steps,
201 }
202}
203pub(super) const MC_EDGE_TABLE: [u16; 256] = [
204 0x000, 0xfff, 0x00f, 0xff0, 0x0f0, 0xf0f, 0x0ff, 0xf00, 0xf00, 0x0ff, 0xf0f, 0x0f0, 0xff0,
205 0x00f, 0xfff, 0x000, 0x00f, 0xff0, 0x000, 0xfff, 0x0ff, 0xf00, 0x0f0, 0xf0f, 0xff0, 0x00f,
206 0xfff, 0x000, 0xf0f, 0x0f0, 0xf00, 0x0ff, 0x0f0, 0xf0f, 0x0ff, 0xf00, 0x000, 0xfff, 0x00f,
207 0xff0, 0xf0f, 0x0f0, 0xf00, 0x0ff, 0xfff, 0x000, 0xff0, 0x00f, 0x0ff, 0xf00, 0x0f0, 0xf0f,
208 0x00f, 0xff0, 0x000, 0xfff, 0xf00, 0x0ff, 0xf0f, 0x0f0, 0xff0, 0x00f, 0xfff, 0x000, 0xf00,
209 0x0ff, 0xf0f, 0x0f0, 0xff0, 0x00f, 0xfff, 0x000, 0x000, 0xfff, 0x00f, 0xff0, 0x0f0, 0xf0f,
210 0x0ff, 0xf00, 0xff0, 0x00f, 0xfff, 0x000, 0xf0f, 0x0f0, 0xf00, 0x0ff, 0x00f, 0xff0, 0x000,
211 0xfff, 0x0ff, 0xf00, 0x0f0, 0xf0f, 0x0ff, 0xf00, 0x0f0, 0xf0f, 0x00f, 0xff0, 0x000, 0xfff,
212 0xf0f, 0x0f0, 0xf00, 0x0ff, 0xfff, 0x000, 0xff0, 0x00f, 0x0f0, 0xf0f, 0x0ff, 0xf00, 0x000,
213 0xfff, 0x00f, 0xff0, 0xf00, 0x0ff, 0xf0f, 0x0f0, 0xff0, 0x00f, 0xfff, 0x000, 0x000, 0xfff,
214 0x00f, 0xff0, 0x0f0, 0xf0f, 0x0ff, 0xf00, 0xf00, 0x0ff, 0xf0f, 0x0f0, 0xff0, 0x00f, 0xfff,
215 0x000, 0x00f, 0xff0, 0x000, 0xfff, 0x0ff, 0xf00, 0x0f0, 0xf0f, 0xff0, 0x00f, 0xfff, 0x000,
216 0xf0f, 0x0f0, 0xf00, 0x0ff, 0x0f0, 0xf0f, 0x0ff, 0xf00, 0x000, 0xfff, 0x00f, 0xff0, 0xf0f,
217 0x0f0, 0xf00, 0x0ff, 0xfff, 0x000, 0xff0, 0x00f, 0x0ff, 0xf00, 0x0f0, 0xf0f, 0x00f, 0xff0,
218 0x000, 0xfff, 0xf00, 0x0ff, 0xf0f, 0x0f0, 0xff0, 0x00f, 0xfff, 0x000, 0xf00, 0x0ff, 0xf0f,
219 0x0f0, 0xff0, 0x00f, 0xfff, 0x000, 0x000, 0xfff, 0x00f, 0xff0, 0x0f0, 0xf0f, 0x0ff, 0xf00,
220 0xff0, 0x00f, 0xfff, 0x000, 0xf0f, 0x0f0, 0xf00, 0x0ff, 0x00f, 0xff0, 0x000, 0xfff, 0x0ff,
221 0xf00, 0x0f0, 0xf0f, 0x0ff, 0xf00, 0x0f0, 0xf0f, 0x00f, 0xff0, 0x000, 0xfff, 0xf0f, 0x0f0,
222 0xf00, 0x0ff, 0xfff, 0x000, 0xff0, 0x00f, 0x0f0, 0xf0f, 0x0ff, 0xf00, 0x000, 0xfff, 0x00f,
223 0xff0, 0xf00, 0x0ff, 0xf0f, 0x0f0, 0xff0, 0x00f, 0xfff, 0x000,
224];
225pub(super) fn interp_edge(p0: [f64; 3], d0: f64, p1: [f64; 3], d1: f64) -> [f64; 3] {
227 if (d1 - d0).abs() < 1e-12 {
228 return p0;
229 }
230 let t = d0 / (d0 - d1);
231 [
232 p0[0] + t * (p1[0] - p0[0]),
233 p0[1] + t * (p1[1] - p0[1]),
234 p0[2] + t * (p1[2] - p0[2]),
235 ]
236}
237pub fn extract_isosurface(grid: &SdfGrid) -> IsoMeshResult {
242 let mut result = IsoMeshResult::default();
243 if grid.nx < 2 || grid.ny < 2 || grid.nz < 2 {
244 return result;
245 }
246 for iz in 0..grid.nz - 1 {
247 for iy in 0..grid.ny - 1 {
248 for ix in 0..grid.nx - 1 {
249 let corners: [[usize; 3]; 8] = [
250 [ix, iy, iz],
251 [ix + 1, iy, iz],
252 [ix + 1, iy + 1, iz],
253 [ix, iy + 1, iz],
254 [ix, iy, iz + 1],
255 [ix + 1, iy, iz + 1],
256 [ix + 1, iy + 1, iz + 1],
257 [ix, iy + 1, iz + 1],
258 ];
259 let vals: [f64; 8] =
260 std::array::from_fn(|i| grid.get(corners[i][0], corners[i][1], corners[i][2]));
261 let pts: [[f64; 3]; 8] = std::array::from_fn(|i| {
262 grid.cell_center(corners[i][0], corners[i][1], corners[i][2])
263 });
264 let mut cube_idx: usize = 0;
265 for i in 0..8 {
266 if vals[i] < 0.0 {
267 cube_idx |= 1 << i;
268 }
269 }
270 if MC_EDGE_TABLE[cube_idx] == 0 {
271 continue;
272 }
273 let edge_mask = MC_EDGE_TABLE[cube_idx];
274 let mut verts: [[f64; 3]; 12] = [[0.0; 3]; 12];
275 let edges = [
276 (0, 1),
277 (1, 2),
278 (2, 3),
279 (3, 0),
280 (4, 5),
281 (5, 6),
282 (6, 7),
283 (7, 4),
284 (0, 4),
285 (1, 5),
286 (2, 6),
287 (3, 7),
288 ];
289 for (e, &(i, j)) in edges.iter().enumerate() {
290 if edge_mask & (1 << e) != 0 {
291 verts[e] = interp_edge(pts[i], vals[i], pts[j], vals[j]);
292 }
293 }
294 let active: Vec<usize> = (0..12).filter(|&e| edge_mask & (1 << e) != 0).collect();
295 if active.len() >= 3 {
296 for i in 1..active.len() - 1 {
297 let tri = Triangle {
298 v0: verts[active[0]],
299 v1: verts[active[i]],
300 v2: verts[active[i + 1]],
301 };
302 result.triangles.push(tri);
303 result.vertex_count += 3;
304 }
305 }
306 }
307 }
308 }
309 result
310}
311pub fn sdf_point_query<S: Sdf>(sdf: &S, p: [f64; 3], eps: f64) -> Option<SdfCollisionResult> {
315 let d = sdf.dist(p);
316 if d >= 0.0 {
317 return None;
318 }
319 let n = sdf_normal(sdf, p, eps);
320 let contact = add(p, scale(n, -d));
321 Some(SdfCollisionResult {
322 depth: -d,
323 normal: n,
324 contact_point: contact,
325 })
326}
327pub fn sdf_sphere_sweep<S: Sdf>(
332 sdf: &S,
333 center: [f64; 3],
334 radius: f64,
335 velocity: [f64; 3],
336 dt: f64,
337 eps: f64,
338) -> Option<(f64, SdfCollisionResult)> {
339 let speed = len(velocity);
340 if speed < 1e-15 {
341 return None;
342 }
343 let dir = scale(velocity, 1.0 / speed);
344 let max_t = speed * dt;
345 let result = ray_march_with_offset(sdf, center, dir, radius, 128, max_t, eps);
346 if result.hit {
347 let toi = result.t / speed;
348 let n = sdf_normal(sdf, result.point, eps);
349 Some((
350 toi,
351 SdfCollisionResult {
352 depth: 0.0,
353 normal: n,
354 contact_point: result.point,
355 },
356 ))
357 } else {
358 None
359 }
360}
361pub(super) fn ray_march_with_offset<S: Sdf>(
362 sdf: &S,
363 origin: [f64; 3],
364 dir: [f64; 3],
365 offset: f64,
366 max_steps: u32,
367 max_t: f64,
368 eps: f64,
369) -> RayMarchResult {
370 let mut t = 0.0_f64;
371 for step in 0..max_steps {
372 let p = add(origin, scale(dir, t));
373 let d = sdf.dist(p) - offset;
374 if d.abs() < eps {
375 return RayMarchResult {
376 hit: true,
377 t,
378 point: p,
379 steps: step + 1,
380 };
381 }
382 t += d.abs().max(eps * 0.1);
383 if t > max_t {
384 break;
385 }
386 }
387 RayMarchResult {
388 hit: false,
389 t,
390 point: add(origin, scale(dir, t)),
391 steps: max_steps,
392 }
393}
394pub(super) fn value_noise_lattice(ix: i32, iy: i32, iz: i32) -> f64 {
398 let n = ix
399 .wrapping_mul(1619)
400 .wrapping_add(iy.wrapping_mul(31337))
401 .wrapping_add(iz.wrapping_mul(6971))
402 .wrapping_add(1376312589i32);
403 let n = n ^ (n << 13);
404 let n = n.wrapping_mul(
405 n.wrapping_mul(n.wrapping_mul(15731).wrapping_add(789221))
406 .wrapping_add(1376312589),
407 );
408 (n & 0x7fffffff) as f64 / 1073741824.0 - 1.0
409}
410#[inline]
412pub(super) fn fade(t: f64) -> f64 {
413 t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
414}
415pub(super) fn value_noise(p: [f64; 3]) -> f64 {
417 let ix = p[0].floor() as i32;
418 let iy = p[1].floor() as i32;
419 let iz = p[2].floor() as i32;
420 let fx = fade(p[0] - ix as f64);
421 let fy = fade(p[1] - iy as f64);
422 let fz = fade(p[2] - iz as f64);
423 let v000 = value_noise_lattice(ix, iy, iz);
424 let v100 = value_noise_lattice(ix + 1, iy, iz);
425 let v010 = value_noise_lattice(ix, iy + 1, iz);
426 let v110 = value_noise_lattice(ix + 1, iy + 1, iz);
427 let v001 = value_noise_lattice(ix, iy, iz + 1);
428 let v101 = value_noise_lattice(ix + 1, iy, iz + 1);
429 let v011 = value_noise_lattice(ix, iy + 1, iz + 1);
430 let v111 = value_noise_lattice(ix + 1, iy + 1, iz + 1);
431 let c00 = v000 * (1.0 - fx) + v100 * fx;
432 let c10 = v010 * (1.0 - fx) + v110 * fx;
433 let c01 = v001 * (1.0 - fx) + v101 * fx;
434 let c11 = v011 * (1.0 - fx) + v111 * fx;
435 let c0 = c00 * (1.0 - fy) + c10 * fy;
436 let c1 = c01 * (1.0 - fy) + c11 * fy;
437 c0 * (1.0 - fz) + c1 * fz
438}
439pub fn fbm_noise(p: [f64; 3], octaves: u32, lacunarity: f64, gain: f64) -> f64 {
441 let mut value = 0.0_f64;
442 let mut amplitude = 0.5_f64;
443 let mut frequency = 1.0_f64;
444 for _ in 0..octaves {
445 value += amplitude * value_noise(scale(p, frequency));
446 amplitude *= gain;
447 frequency *= lacunarity;
448 }
449 value
450}
451pub fn sdf_closest_point<S: Sdf>(sdf: &S, p: [f64; 3], eps: f64, max_iter: u32) -> ([f64; 3], f64) {
456 let mut q = p;
457 for _ in 0..max_iter {
458 let d = sdf.dist(q);
459 if d.abs() < eps {
460 break;
461 }
462 let g = sdf_gradient(sdf, q, eps);
463 let step = d;
464 q = sub(q, scale(g, step));
465 }
466 let d_final = sdf.dist(q);
467 (q, d_final)
468}
469pub fn sdf_ambient_occlusion<S: Sdf>(
474 sdf: &S,
475 p: [f64; 3],
476 n: [f64; 3],
477 num_samples: u32,
478 max_dist: f64,
479) -> f64 {
480 let mut occ = 0.0_f64;
481 let mut scale_factor = 1.0_f64;
482 for i in 1..=num_samples {
483 let h = i as f64 / num_samples as f64 * max_dist;
484 let sample_pt = add(p, scale(n, h));
485 let d = sdf.dist(sample_pt);
486 occ += (h - d) * scale_factor;
487 scale_factor *= 0.5;
488 }
489 (1.0 - 2.0 * occ / max_dist).clamp(0.0, 1.0)
490}
491pub fn sdf_soft_shadow<S: Sdf>(sdf: &S, p: [f64; 3], light_pos: [f64; 3], k: f64, eps: f64) -> f64 {
496 let to_light = sub(light_pos, p);
497 let dist_to_light = len(to_light);
498 let dir = scale(to_light, 1.0 / dist_to_light);
499 let mut t = 2.0 * eps;
500 let mut shadow = 1.0_f64;
501 let max_steps = 64u32;
502 for _ in 0..max_steps {
503 if t >= dist_to_light {
504 break;
505 }
506 let q = add(p, scale(dir, t));
507 let d = sdf.dist(q);
508 if d < eps {
509 return 0.0;
510 }
511 shadow = shadow.min(k * d / t);
512 t += d;
513 }
514 shadow.clamp(0.0, 1.0)
515}
516pub fn sdf_contact_manifold<S: Sdf>(
521 sdf: &S,
522 points: &[[f64; 3]],
523 tolerance: f64,
524 eps: f64,
525) -> Vec<ContactPoint> {
526 let mut contacts = Vec::new();
527 for &p in points {
528 let d = sdf.dist(p);
529 if d < tolerance {
530 let n = sdf_normal(sdf, p, eps);
531 contacts.push(ContactPoint {
532 position: p,
533 normal: n,
534 depth: (-d).max(0.0),
535 });
536 }
537 }
538 contacts
539}
540pub fn count_surface_cells(grid: &SdfGrid) -> usize {
542 let mut count = 0usize;
543 for iz in 0..grid.nz.saturating_sub(1) {
544 for iy in 0..grid.ny.saturating_sub(1) {
545 for ix in 0..grid.nx.saturating_sub(1) {
546 let signs: [bool; 8] = [
547 grid.get(ix, iy, iz) < 0.0,
548 grid.get(ix + 1, iy, iz) < 0.0,
549 grid.get(ix + 1, iy + 1, iz) < 0.0,
550 grid.get(ix, iy + 1, iz) < 0.0,
551 grid.get(ix, iy, iz + 1) < 0.0,
552 grid.get(ix + 1, iy, iz + 1) < 0.0,
553 grid.get(ix + 1, iy + 1, iz + 1) < 0.0,
554 grid.get(ix, iy + 1, iz + 1) < 0.0,
555 ];
556 let all_in = signs.iter().all(|&s| s);
557 let all_out = signs.iter().all(|&s| !s);
558 if !all_in && !all_out {
559 count += 1;
560 }
561 }
562 }
563 }
564 count
565}
566#[cfg(test)]
567mod tests {
568 use super::*;
569 use crate::implicit_geometry::BoolOp;
570 use crate::implicit_geometry::SdfBend;
571 use crate::implicit_geometry::SdfBoundedProxy;
572 use crate::implicit_geometry::SdfEllipsoid;
573 use crate::implicit_geometry::SdfExtrude;
574 use crate::implicit_geometry::SdfGyroid;
575 use crate::implicit_geometry::SdfHexagonalPrism;
576 use crate::implicit_geometry::SdfLineSegment;
577 use crate::implicit_geometry::SdfNode;
578 use crate::implicit_geometry::SdfNoiseDisplace;
579 use crate::implicit_geometry::SdfRepeat;
580 use crate::implicit_geometry::SdfRevolution;
581 use crate::implicit_geometry::SdfScale;
582 use crate::implicit_geometry::SdfShell;
583 use crate::implicit_geometry::SdfTranslate;
584 use crate::implicit_geometry::SdfTwist;
585 use crate::implicit_geometry::types::SdfBox;
586 use crate::implicit_geometry::types::SdfDifference;
587 use crate::implicit_geometry::types::SdfIntersection;
588 use crate::implicit_geometry::types::SdfOffset;
589 use crate::implicit_geometry::types::SdfPlane;
590 use crate::implicit_geometry::types::SdfSphere;
591 use crate::implicit_geometry::types::SdfTorus;
592 use crate::implicit_geometry::types::SdfUnion;
593 fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
594 (a - b).abs() < tol
595 }
596 #[test]
597 fn test_sphere_center_is_minus_radius() {
598 let s = SdfSphere::new(2.0);
599 approx_eq(s.dist([0.0, 0.0, 0.0]), -2.0, 1e-12);
600 }
601 #[test]
602 fn test_sphere_on_surface() {
603 let s = SdfSphere::new(1.0);
604 assert!(approx_eq(s.dist([1.0, 0.0, 0.0]), 0.0, 1e-12));
605 }
606 #[test]
607 fn test_sphere_outside() {
608 let s = SdfSphere::new(1.0);
609 assert!(s.dist([2.0, 0.0, 0.0]) > 0.0);
610 }
611 #[test]
612 fn test_plane_on_surface() {
613 let p = SdfPlane::new([0.0, 1.0, 0.0], 0.0);
614 assert!(approx_eq(p.dist([1.0, 0.0, 5.0]), 0.0, 1e-12));
615 }
616 #[test]
617 fn test_plane_above() {
618 let p = SdfPlane::new([0.0, 1.0, 0.0], 0.0);
619 assert!(p.dist([0.0, 1.0, 0.0]) > 0.0);
620 }
621 #[test]
622 fn test_plane_below() {
623 let p = SdfPlane::new([0.0, 1.0, 0.0], 0.0);
624 assert!(p.dist([0.0, -1.0, 0.0]) < 0.0);
625 }
626 #[test]
627 fn test_ellipsoid_inside() {
628 let e = SdfEllipsoid::new(2.0, 1.0, 1.5);
629 assert!(e.dist([0.0, 0.0, 0.0]) < 0.0);
630 }
631 #[test]
632 fn test_ellipsoid_surface_x() {
633 let e = SdfEllipsoid::new(2.0, 1.0, 1.5);
634 let d = e.dist([2.0, 0.0, 0.0]);
635 assert!(approx_eq(d, 0.0, 1e-6), "d={d}");
636 }
637 #[test]
638 fn test_box_inside() {
639 let b = SdfBox::new(1.0, 1.0, 1.0);
640 assert!(b.dist([0.0, 0.0, 0.0]) < 0.0);
641 }
642 #[test]
643 fn test_box_corner() {
644 let b = SdfBox::new(1.0, 1.0, 1.0);
645 let d = b.dist([1.0, 1.0, 1.0]);
646 assert!(approx_eq(d, 0.0, 1e-12));
647 }
648 #[test]
649 fn test_box_outside_face() {
650 let b = SdfBox::new(1.0, 2.0, 1.0);
651 let d = b.dist([2.0, 0.0, 0.0]);
652 assert!(approx_eq(d, 1.0, 1e-12));
653 }
654 #[test]
655 fn test_torus_tube_surface() {
656 let t = SdfTorus::new(3.0, 1.0);
657 let d = t.dist([4.0, 0.0, 0.0]);
658 assert!(approx_eq(d, 0.0, 1e-12), "d={d}");
659 }
660 #[test]
661 fn test_torus_inside_tube() {
662 let t = SdfTorus::new(3.0, 1.0);
663 assert!(t.dist([3.0, 0.0, 0.0]) < 0.0);
664 }
665 #[test]
666 fn test_line_segment_midpoint() {
667 let s = SdfLineSegment::new([0.0, 0.0, 0.0], [2.0, 0.0, 0.0], 0.5);
668 let d = s.dist([1.0, 0.0, 0.0]);
669 assert!(approx_eq(d, -0.5, 1e-12));
670 }
671 #[test]
672 fn test_line_segment_endpoint() {
673 let s = SdfLineSegment::new([0.0, 0.0, 0.0], [2.0, 0.0, 0.0], 0.0);
674 assert!(approx_eq(s.dist([0.0, 0.0, 0.0]), 0.0, 1e-12));
675 }
676 #[test]
677 fn test_union_inside_either() {
678 let a = SdfSphere::new(1.0);
679 let b = SdfTranslate::new(SdfSphere::new(1.0), [3.0, 0.0, 0.0]);
680 let u = SdfUnion::new(a, b);
681 assert!(u.dist([0.0, 0.0, 0.0]) < 0.0);
682 assert!(u.dist([3.0, 0.0, 0.0]) < 0.0);
683 }
684 #[test]
685 fn test_intersection_inside_both() {
686 let a = SdfSphere::new(2.0);
687 let b = SdfBox::new(1.0, 1.0, 1.0);
688 let i = SdfIntersection::new(a, b);
689 assert!(i.dist([0.0, 0.0, 0.0]) < 0.0);
690 assert!(i.dist([3.0, 0.0, 0.0]) > 0.0);
691 }
692 #[test]
693 fn test_difference_removes_inner() {
694 let outer = SdfSphere::new(2.0);
695 let inner = SdfSphere::new(1.0);
696 let d = SdfDifference::new(outer, inner);
697 assert!(d.dist([0.0, 0.0, 0.0]) > 0.0);
698 assert!(d.dist([1.5, 0.0, 0.0]) < 0.0);
699 }
700 #[test]
701 fn test_smooth_union_le_min() {
702 let a = 0.5_f64;
703 let b = 0.3_f64;
704 let su = sdf_smooth_union(a, b, 0.2);
705 assert!(su <= a.min(b) + 1e-12);
706 }
707 #[test]
708 fn test_smooth_union_zero_k() {
709 let a = 0.5_f64;
710 let b = 0.3_f64;
711 assert!(approx_eq(sdf_smooth_union(a, b, 0.0), a.min(b), 1e-12));
712 }
713 #[test]
714 fn test_smooth_intersection_in_range() {
715 let a = 0.5_f64;
716 let b = 0.3_f64;
717 let k = 0.2_f64;
718 let si = sdf_smooth_intersection(a, b, k);
719 assert!(si >= a.min(b) - k, "si={si} below lower bound");
720 assert!(si <= a.max(b) + k, "si={si} above upper bound");
721 assert!(approx_eq(
722 sdf_smooth_intersection(a, b, 0.0),
723 a.max(b),
724 1e-12
725 ));
726 }
727 #[test]
728 fn test_sphere_gradient_unit_length() {
729 let s = SdfSphere::new(1.0);
730 let g = sdf_gradient(&s, [0.6, 0.5, 0.3], 1e-5);
731 let l = len(g);
732 assert!(approx_eq(l, 1.0, 0.01), "gradient length={l}");
733 }
734 #[test]
735 fn test_sphere_normal_points_outward() {
736 let s = SdfSphere::new(1.0);
737 let p = [1.0, 0.0, 0.0];
738 let n = sdf_normal(&s, p, 1e-5);
739 assert!(n[0] > 0.9);
740 }
741 #[test]
742 fn test_offset_grows_sphere() {
743 let s = SdfSphere::new(1.0);
744 let grown = SdfOffset::new(s, 0.5);
745 assert!(grown.dist([1.0, 0.0, 0.0]) < 0.0);
746 let d = grown.dist([1.5, 0.0, 0.0]);
747 assert!(approx_eq(d, 0.0, 1e-12));
748 }
749 #[test]
750 fn test_shell_makes_thin_surface() {
751 let s = SdfSphere::new(1.0);
752 let shell = SdfShell::new(s, 0.1);
753 let d = shell.dist([1.0, 0.0, 0.0]);
754 assert!(approx_eq(d, -0.05, 1e-12), "d={d}");
755 }
756 #[test]
757 fn test_translate_moves_sphere() {
758 let s = SdfTranslate::new(SdfSphere::new(1.0), [5.0, 0.0, 0.0]);
759 assert!(s.dist([5.0, 0.0, 0.0]) < 0.0);
760 assert!(s.dist([0.0, 0.0, 0.0]) > 0.0);
761 }
762 #[test]
763 fn test_scale_doubles_sphere() {
764 let s = SdfScale::new(SdfSphere::new(1.0), 2.0);
765 let d = s.dist([2.0, 0.0, 0.0]);
766 assert!(approx_eq(d, 0.0, 1e-12));
767 }
768 #[test]
769 fn test_ray_march_hits_sphere() {
770 let s = SdfSphere::new(1.0);
771 let res = ray_march(&s, [-5.0, 0.0, 0.0], [1.0, 0.0, 0.0], 128, 20.0, 1e-4);
772 assert!(res.hit, "ray should hit sphere");
773 assert!(approx_eq(res.t, 4.0, 0.01), "t={}", res.t);
774 }
775 #[test]
776 fn test_ray_march_misses_sphere() {
777 let s = SdfSphere::new(1.0);
778 let res = ray_march(&s, [-5.0, 0.0, 0.0], [-1.0, 0.0, 0.0], 128, 20.0, 1e-4);
779 assert!(!res.hit);
780 }
781 #[test]
782 fn test_ray_march_relaxed_hits() {
783 let s = SdfSphere::new(1.0);
784 let res = ray_march_relaxed(&s, [-5.0, 0.0, 0.0], [1.0, 0.0, 0.0], 512, 20.0, 1e-4, 1.0);
785 assert!(res.hit, "relaxed ray march should hit sphere");
786 }
787 #[test]
788 fn test_grid_from_sphere_sdf() {
789 let s = SdfSphere::new(1.0);
790 let g = SdfGrid::from_sdf(&s, 10, 10, 10, [-2.0, -2.0, -2.0], 0.4);
791 assert!(g.get(5, 5, 5) < 0.0);
792 }
793 #[test]
794 fn test_grid_dilate_erode_inverse() {
795 let s = SdfSphere::new(1.0);
796 let mut g = SdfGrid::from_sdf(&s, 6, 6, 6, [-1.5, -1.5, -1.5], 0.5);
797 let v_orig = g.get(3, 3, 3);
798 g.dilate(0.3);
799 g.erode(0.3);
800 assert!(approx_eq(g.get(3, 3, 3), v_orig, 1e-12));
801 }
802 #[test]
803 fn test_grid_interpolate_center() {
804 let s = SdfSphere::new(1.0);
805 let g = SdfGrid::from_sdf(&s, 20, 20, 20, [-2.0, -2.0, -2.0], 0.2);
806 let interp = g.interpolate([0.0, 0.0, 0.0]);
807 assert!(interp < -0.5, "interp={interp}");
808 }
809 #[test]
810 fn test_extract_isosurface_has_triangles() {
811 let s = SdfSphere::new(1.0);
812 let g = SdfGrid::from_sdf(&s, 10, 10, 10, [-2.0, -2.0, -2.0], 0.4);
813 let mesh = extract_isosurface(&g);
814 assert!(
815 !mesh.triangles.is_empty(),
816 "isosurface should produce triangles"
817 );
818 }
819 #[test]
820 fn test_count_surface_cells() {
821 let s = SdfSphere::new(1.0);
822 let g = SdfGrid::from_sdf(&s, 10, 10, 10, [-2.0, -2.0, -2.0], 0.4);
823 let cnt = count_surface_cells(&g);
824 assert!(cnt > 0, "should have surface cells");
825 }
826 #[test]
827 fn test_point_query_inside() {
828 let s = SdfSphere::new(2.0);
829 let res = sdf_point_query(&s, [0.0, 0.0, 0.0], 1e-5);
830 assert!(res.is_some());
831 assert!(res.unwrap().depth > 0.0);
832 }
833 #[test]
834 fn test_point_query_outside() {
835 let s = SdfSphere::new(1.0);
836 let res = sdf_point_query(&s, [5.0, 0.0, 0.0], 1e-5);
837 assert!(res.is_none());
838 }
839 #[test]
840 fn test_contact_manifold_sphere() {
841 let s = SdfSphere::new(2.0);
842 let pts: Vec<[f64; 3]> = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [3.0, 0.0, 0.0]];
843 let contacts = sdf_contact_manifold(&s, &pts, 0.0, 1e-5);
844 assert_eq!(contacts.len(), 2, "{} contacts", contacts.len());
845 }
846 #[test]
847 fn test_noise_displace_varies() {
848 let s = SdfSphere::new(1.0);
849 let noisy = SdfNoiseDisplace::new(s, 3.0, 0.2, 4);
850 let d1 = noisy.dist([1.0, 0.0, 0.0]);
851 let d2 = noisy.dist([0.0, 1.0, 0.0]);
852 let _ = (d1, d2);
853 }
854 #[test]
855 fn test_fbm_noise_bounded() {
856 for i in 0..20 {
857 let v = fbm_noise(
858 [i as f64 * 0.3, i as f64 * 0.7, i as f64 * 0.5],
859 4,
860 2.0,
861 0.5,
862 );
863 assert!(v.abs() <= 2.0, "fbm out of range: {v}");
864 }
865 }
866 #[test]
867 fn test_sdf_node_union() {
868 let a = SdfNode::leaf(SdfSphere::new(1.0));
869 let b = SdfNode::leaf(SdfTranslate::new(SdfSphere::new(1.0), [4.0, 0.0, 0.0]));
870 let u = SdfNode::combine(BoolOp::Union, a, b, 0.0);
871 assert!(u.eval([0.0, 0.0, 0.0]) < 0.0);
872 assert!(u.eval([4.0, 0.0, 0.0]) < 0.0);
873 }
874 #[test]
875 fn test_sdf_node_smooth_union() {
876 let a = SdfNode::leaf(SdfSphere::new(1.0));
877 let b = SdfNode::leaf(SdfTranslate::new(SdfSphere::new(1.0), [1.5, 0.0, 0.0]));
878 let su = SdfNode::combine(BoolOp::SmoothUnion, a, b, 0.5);
879 let d = su.eval([0.75, 0.0, 0.0]);
880 assert!(d < 0.0, "d={d}");
881 }
882 #[test]
883 fn test_twist_identity_at_zero_strength() {
884 let s = SdfBox::new(1.0, 1.0, 1.0);
885 let t = SdfTwist::new(SdfBox::new(1.0, 1.0, 1.0), 0.0);
886 let p = [0.5, 0.3, 0.2];
887 assert!(approx_eq(s.dist(p), t.dist(p), 1e-12));
888 }
889 #[test]
890 fn test_bend_identity_at_zero_strength() {
891 let s = SdfBox::new(1.0, 1.0, 1.0);
892 let b = SdfBend::new(SdfBox::new(1.0, 1.0, 1.0), 0.0);
893 let p = [0.5, 0.3, 0.2];
894 assert!(approx_eq(s.dist(p), b.dist(p), 1e-12));
895 }
896 #[test]
897 fn test_repeat_periodicity() {
898 let s = SdfRepeat::new(SdfSphere::new(0.4), 2.0, 2.0, 2.0);
899 let d0 = s.dist([0.0, 0.0, 0.0]);
900 let d1 = s.dist([2.0, 0.0, 0.0]);
901 assert!(approx_eq(d0, d1, 1e-10), "d0={d0} d1={d1}");
902 }
903 #[test]
904 fn test_gyroid_surface_exists() {
905 let g = SdfGyroid::new(1.0, 0.05);
906 let d0 = g.dist([0.0, 0.0, 0.0]);
907 let d1 = g.dist([0.25, 0.0, 0.0]);
908 let _ = (d0, d1);
909 assert!(d0.is_finite());
910 assert!(d1.is_finite());
911 }
912 #[test]
913 fn test_bounded_proxy_outside_bounds() {
914 let inner = SdfSphere::new(1.0);
915 let proxy = SdfBoundedProxy::new(inner, [0.0, 0.0, 0.0], 1.5);
916 let d = proxy.dist([10.0, 0.0, 0.0]);
917 assert!(d > 0.0);
918 assert!(approx_eq(d, 10.0 - 1.5, 0.01));
919 }
920 #[test]
921 fn test_sphere_mean_curvature() {
922 let s = SdfSphere::new(2.0);
923 let p = [2.0, 0.0, 0.0];
924 let h = sdf_mean_curvature(&s, p, 1e-4);
925 assert!(h.is_finite(), "curvature should be finite: {h}");
926 }
927 #[test]
928 fn test_revolution_sphere_equivalent() {
929 let sphere_profile = |r: f64, y: f64| (r * r + y * y).sqrt() - 1.0;
930 let rev = SdfRevolution::new(sphere_profile);
931 let s = SdfSphere::new(1.0);
932 let p = [0.6, 0.5, 0.3];
933 assert!(approx_eq(rev.dist(p), s.dist(p), 1e-10));
934 }
935 #[test]
936 fn test_extrude_circle_gives_cylinder() {
937 let circle_profile = |xz: [f64; 2]| (xz[0] * xz[0] + xz[1] * xz[1]).sqrt() - 1.0;
938 let cyl = SdfExtrude::new(circle_profile, 2.0);
939 assert!(cyl.dist([0.0, 0.0, 0.0]) < 0.0);
940 assert!(cyl.dist([2.0, 0.0, 0.0]) > 0.0);
941 assert!(cyl.dist([0.0, 3.0, 0.0]) > 0.0);
942 }
943 #[test]
944 fn test_hex_prism_center_inside() {
945 let h = SdfHexagonalPrism::new(1.0, 1.0);
946 assert!(h.dist([0.0, 0.0, 0.0]) < 0.0);
947 }
948 #[test]
949 fn test_closest_point_sphere() {
950 let s = SdfSphere::new(1.0);
951 let (_q, d) = sdf_closest_point(&s, [0.5, 0.0, 0.0], 1e-5, 50);
952 assert!(d.abs() < 1e-3, "final dist={d}");
953 }
954 #[test]
955 fn test_ao_near_plane_unoccluded() {
956 let s = SdfSphere::new(1.0);
957 let n = [0.0, 1.0, 0.0];
958 let p = [0.0, 1.0, 0.0];
959 let ao = sdf_ambient_occlusion(&s, p, n, 5, 1.0);
960 assert!(ao.is_finite());
961 assert!((0.0..=1.0).contains(&ao));
962 }
963 #[test]
964 fn test_soft_shadow_unobstructed() {
965 let s = SdfSphere::new(0.5);
966 let shadow = sdf_soft_shadow(&s, [-3.0, 0.0, 0.0], [3.0, 0.0, 0.0], 8.0, 1e-4);
967 assert!(shadow < 1.0, "shadow={shadow}");
968 }
969}