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