1pub fn smooth_min_polynomial(a: f64, b: f64, k: f64) -> f64 {
17 let h = (0.5 + 0.5 * (b - a) / k).clamp(0.0, 1.0);
18 a * h + b * (1.0 - h) - k * h * (1.0 - h)
19}
20
21pub fn smooth_min_exponential(a: f64, b: f64, k: f64) -> f64 {
25 let ea = (-k * a).exp();
26 let eb = (-k * b).exp();
27 -(ea + eb).ln() / k
28}
29
30pub fn sdf_gradient_numerical<F>(f: &F, p: [f64; 3], eps: f64) -> [f64; 3]
32where
33 F: Fn([f64; 3]) -> f64,
34{
35 let dx = f([p[0] + eps, p[1], p[2]]) - f([p[0] - eps, p[1], p[2]]);
36 let dy = f([p[0], p[1] + eps, p[2]]) - f([p[0], p[1] - eps, p[2]]);
37 let dz = f([p[0], p[1], p[2] + eps]) - f([p[0], p[1], p[2] - eps]);
38 let len = (dx * dx + dy * dy + dz * dz).sqrt().max(1e-30);
39 [
40 dx / (2.0 * eps * len / eps * eps),
41 dy / (2.0 * eps * len / eps * eps),
42 dz / (2.0 * eps * len / eps * eps),
43 ]
44}
45
46fn numerical_grad<F: Fn([f64; 3]) -> f64>(f: &F, p: [f64; 3], eps: f64) -> [f64; 3] {
48 [
49 (f([p[0] + eps, p[1], p[2]]) - f([p[0] - eps, p[1], p[2]])) / (2.0 * eps),
50 (f([p[0], p[1] + eps, p[2]]) - f([p[0], p[1] - eps, p[2]])) / (2.0 * eps),
51 (f([p[0], p[1], p[2] + eps]) - f([p[0], p[1], p[2] - eps])) / (2.0 * eps),
52 ]
53}
54
55fn vec3_dot(a: [f64; 3], b: [f64; 3]) -> f64 {
56 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
57}
58fn vec3_len(a: [f64; 3]) -> f64 {
59 vec3_dot(a, a).sqrt()
60}
61fn vec3_norm(a: [f64; 3]) -> [f64; 3] {
62 let l = vec3_len(a).max(1e-30);
63 [a[0] / l, a[1] / l, a[2] / l]
64}
65fn vec3_sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
66 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
67}
68fn vec3_add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
69 [a[0] + b[0], a[1] + b[1], a[2] + b[2]]
70}
71fn vec3_scale(a: [f64; 3], s: f64) -> [f64; 3] {
72 [a[0] * s, a[1] * s, a[2] * s]
73}
74
75fn clamp(v: f64, lo: f64, hi: f64) -> f64 {
76 v.max(lo).min(hi)
77}
78
79#[derive(Debug, Clone, Copy)]
87pub struct ImplicitSphere {
88 pub center: [f64; 3],
90 pub radius: f64,
92}
93
94impl ImplicitSphere {
95 pub fn new(center: [f64; 3], radius: f64) -> Self {
97 Self { center, radius }
98 }
99
100 pub fn sdf(&self, p: [f64; 3]) -> f64 {
102 let d = vec3_sub(p, self.center);
103 vec3_len(d) - self.radius
104 }
105
106 pub fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
108 vec3_norm(vec3_sub(p, self.center))
109 }
110
111 pub fn hessian(&self, p: [f64; 3]) -> [f64; 9] {
113 let d = vec3_sub(p, self.center);
114 let r = vec3_len(d).max(1e-30);
115 let mut h = [0.0f64; 9];
116 for i in 0..3 {
117 for j in 0..3 {
118 let delta = if i == j { 1.0 } else { 0.0 };
119 h[i * 3 + j] = (delta * r * r - d[i] * d[j]) / (r * r * r);
120 }
121 }
122 h
123 }
124}
125
126#[derive(Debug, Clone, Copy)]
132pub struct ImplicitBox {
133 pub center: [f64; 3],
135 pub half_extents: [f64; 3],
137 pub rounding: f64,
139}
140
141impl ImplicitBox {
142 pub fn new(center: [f64; 3], half_extents: [f64; 3]) -> Self {
144 Self {
145 center,
146 half_extents,
147 rounding: 0.0,
148 }
149 }
150
151 pub fn rounded(center: [f64; 3], half_extents: [f64; 3], rounding: f64) -> Self {
153 Self {
154 center,
155 half_extents,
156 rounding,
157 }
158 }
159
160 pub fn sdf(&self, p: [f64; 3]) -> f64 {
162 let q = [
163 (p[0] - self.center[0]).abs() - self.half_extents[0] + self.rounding,
164 (p[1] - self.center[1]).abs() - self.half_extents[1] + self.rounding,
165 (p[2] - self.center[2]).abs() - self.half_extents[2] + self.rounding,
166 ];
167 let outside = [q[0].max(0.0), q[1].max(0.0), q[2].max(0.0)];
168 let outside_dist = vec3_len(outside);
169 let inside_dist = q[0].max(q[1]).max(q[2]).min(0.0);
170 outside_dist + inside_dist - self.rounding
171 }
172
173 pub fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
175 numerical_grad(&|x| self.sdf(x), p, 1e-5)
176 }
177}
178
179#[derive(Debug, Clone, Copy)]
185pub struct ImplicitCapsule {
186 pub a: [f64; 3],
188 pub b: [f64; 3],
190 pub radius: f64,
192}
193
194impl ImplicitCapsule {
195 pub fn new(a: [f64; 3], b: [f64; 3], radius: f64) -> Self {
197 Self { a, b, radius }
198 }
199
200 pub fn sdf(&self, p: [f64; 3]) -> f64 {
202 let ab = vec3_sub(self.b, self.a);
203 let ap = vec3_sub(p, self.a);
204 let t = clamp(vec3_dot(ap, ab) / vec3_dot(ab, ab).max(1e-30), 0.0, 1.0);
205 let closest = vec3_add(self.a, vec3_scale(ab, t));
206 vec3_len(vec3_sub(p, closest)) - self.radius
207 }
208
209 pub fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
211 let ab = vec3_sub(self.b, self.a);
212 let ap = vec3_sub(p, self.a);
213 let t = clamp(vec3_dot(ap, ab) / vec3_dot(ab, ab).max(1e-30), 0.0, 1.0);
214 let closest = vec3_add(self.a, vec3_scale(ab, t));
215 vec3_norm(vec3_sub(p, closest))
216 }
217}
218
219#[derive(Debug, Clone, Copy)]
225pub struct ImplicitTorus {
226 pub major_radius: f64,
228 pub minor_radius: f64,
230}
231
232impl ImplicitTorus {
233 pub fn new(major_radius: f64, minor_radius: f64) -> Self {
235 Self {
236 major_radius,
237 minor_radius,
238 }
239 }
240
241 pub fn sdf(&self, p: [f64; 3]) -> f64 {
243 let q = [(p[0] * p[0] + p[2] * p[2]).sqrt() - self.major_radius, p[1]];
244 (q[0] * q[0] + q[1] * q[1]).sqrt() - self.minor_radius
245 }
246
247 pub fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
249 numerical_grad(&|x| self.sdf(x), p, 1e-5)
250 }
251}
252
253#[derive(Debug, Clone)]
261pub struct ImplicitBlend {
262 pub k: f64,
264}
265
266impl ImplicitBlend {
267 pub fn new(k: f64) -> Self {
269 Self { k }
270 }
271
272 pub fn smooth_union(&self, a: f64, b: f64) -> f64 {
274 smooth_min_polynomial(a, b, self.k)
275 }
276
277 pub fn smooth_intersection(&self, a: f64, b: f64) -> f64 {
279 -smooth_min_polynomial(-a, -b, self.k)
280 }
281
282 pub fn smooth_subtraction(&self, a: f64, b: f64) -> f64 {
284 self.smooth_intersection(a, -b)
285 }
286
287 pub fn union(a: f64, b: f64) -> f64 {
289 a.min(b)
290 }
291
292 pub fn intersection(a: f64, b: f64) -> f64 {
294 a.max(b)
295 }
296
297 pub fn subtraction(a: f64, b: f64) -> f64 {
299 a.max(-b)
300 }
301
302 pub fn exp_union(&self, a: f64, b: f64) -> f64 {
304 smooth_min_exponential(a, b, self.k)
305 }
306}
307
308#[derive(Debug, Clone)]
317pub struct SdfGrid3D {
318 pub nx: usize,
320 pub ny: usize,
322 pub nz: usize,
324 pub min_corner: [f64; 3],
326 pub voxel_size: f64,
328 pub data: Vec<f64>,
330}
331
332impl SdfGrid3D {
333 pub fn new(nx: usize, ny: usize, nz: usize, min_corner: [f64; 3], voxel_size: f64) -> Self {
335 let data = vec![f64::INFINITY; nx * ny * nz];
336 Self {
337 nx,
338 ny,
339 nz,
340 min_corner,
341 voxel_size,
342 data,
343 }
344 }
345
346 pub fn from_sdf<F: Fn([f64; 3]) -> f64>(
348 nx: usize,
349 ny: usize,
350 nz: usize,
351 min_corner: [f64; 3],
352 voxel_size: f64,
353 f: F,
354 ) -> Self {
355 let mut grid = Self::new(nx, ny, nz, min_corner, voxel_size);
356 for iz in 0..nz {
357 for iy in 0..ny {
358 for ix in 0..nx {
359 let p = grid.voxel_center(ix, iy, iz);
360 let idx = grid.index(ix, iy, iz);
361 grid.data[idx] = f(p);
362 }
363 }
364 }
365 grid
366 }
367
368 pub fn index(&self, ix: usize, iy: usize, iz: usize) -> usize {
370 ix + self.nx * (iy + self.ny * iz)
371 }
372
373 pub fn voxel_center(&self, ix: usize, iy: usize, iz: usize) -> [f64; 3] {
375 [
376 self.min_corner[0] + (ix as f64 + 0.5) * self.voxel_size,
377 self.min_corner[1] + (iy as f64 + 0.5) * self.voxel_size,
378 self.min_corner[2] + (iz as f64 + 0.5) * self.voxel_size,
379 ]
380 }
381
382 pub fn interpolate(&self, p: [f64; 3]) -> f64 {
384 let fx = (p[0] - self.min_corner[0]) / self.voxel_size - 0.5;
385 let fy = (p[1] - self.min_corner[1]) / self.voxel_size - 0.5;
386 let fz = (p[2] - self.min_corner[2]) / self.voxel_size - 0.5;
387
388 let ix = fx.floor() as i64;
389 let iy = fy.floor() as i64;
390 let iz = fz.floor() as i64;
391
392 let tx = fx - ix as f64;
393 let ty = fy - iy as f64;
394 let tz = fz - iz as f64;
395
396 let sample = |dx: i64, dy: i64, dz: i64| -> f64 {
397 let xi = (ix + dx).clamp(0, self.nx as i64 - 1) as usize;
398 let yi = (iy + dy).clamp(0, self.ny as i64 - 1) as usize;
399 let zi = (iz + dz).clamp(0, self.nz as i64 - 1) as usize;
400 self.data[self.index(xi, yi, zi)]
401 };
402
403 let c000 = sample(0, 0, 0);
405 let c100 = sample(1, 0, 0);
406 let c010 = sample(0, 1, 0);
407 let c110 = sample(1, 1, 0);
408 let c001 = sample(0, 0, 1);
409 let c101 = sample(1, 0, 1);
410 let c011 = sample(0, 1, 1);
411 let c111 = sample(1, 1, 1);
412
413 let c00 = c000 * (1.0 - tx) + c100 * tx;
414 let c10 = c010 * (1.0 - tx) + c110 * tx;
415 let c01 = c001 * (1.0 - tx) + c101 * tx;
416 let c11 = c011 * (1.0 - tx) + c111 * tx;
417 let c0 = c00 * (1.0 - ty) + c10 * ty;
418 let c1 = c01 * (1.0 - ty) + c11 * ty;
419 c0 * (1.0 - tz) + c1 * tz
420 }
421
422 pub fn gradient(&self, p: [f64; 3]) -> [f64; 3] {
424 let h = self.voxel_size * 0.5;
425 let gx = (self.interpolate([p[0] + h, p[1], p[2]])
426 - self.interpolate([p[0] - h, p[1], p[2]]))
427 / (2.0 * h);
428 let gy = (self.interpolate([p[0], p[1] + h, p[2]])
429 - self.interpolate([p[0], p[1] - h, p[2]]))
430 / (2.0 * h);
431 let gz = (self.interpolate([p[0], p[1], p[2] + h])
432 - self.interpolate([p[0], p[1], p[2] - h]))
433 / (2.0 * h);
434 [gx, gy, gz]
435 }
436
437 pub fn get(&self, ix: usize, iy: usize, iz: usize) -> f64 {
439 self.data[self.index(ix, iy, iz)]
440 }
441
442 pub fn set(&mut self, ix: usize, iy: usize, iz: usize, v: f64) {
444 let idx = self.index(ix, iy, iz);
445 self.data[idx] = v;
446 }
447}
448
449pub struct SdfReinitialize;
457
458impl SdfReinitialize {
459 pub fn reinitialize(grid: &mut SdfGrid3D) {
464 let nx = grid.nx;
465 let ny = grid.ny;
466 let nz = grid.nz;
467 let h = grid.voxel_size;
468
469 let mut new_data = grid.data.clone();
471 let sign = |v: f64| -> f64 { if v >= 0.0 { 1.0 } else { -1.0 } };
472
473 for _ in 0..5 {
475 for iz in 0..nz {
476 for iy in 0..ny {
477 for ix in 0..nx {
478 let idx = grid.index(ix, iy, iz);
479 let v = grid.data[idx];
480 let s = sign(v);
481
482 let get = |x: i64, y: i64, z: i64| -> f64 {
484 let xi = x.clamp(0, nx as i64 - 1) as usize;
485 let yi = y.clamp(0, ny as i64 - 1) as usize;
486 let zi = z.clamp(0, nz as i64 - 1) as usize;
487 grid.data[grid.index(xi, yi, zi)]
488 };
489
490 let dx = get(ix as i64 - 1, iy as i64, iz as i64)
491 .abs()
492 .min(get(ix as i64 + 1, iy as i64, iz as i64).abs());
493 let dy = get(ix as i64, iy as i64 - 1, iz as i64)
494 .abs()
495 .min(get(ix as i64, iy as i64 + 1, iz as i64).abs());
496 let dz = get(ix as i64, iy as i64, iz as i64 - 1)
497 .abs()
498 .min(get(ix as i64, iy as i64, iz as i64 + 1).abs());
499
500 let proposed = s * (dx.min(dy).min(dz) + h);
503 if (proposed).abs() < v.abs() {
504 new_data[idx] = proposed;
505 }
506 }
507 }
508 }
509 }
510 grid.data = new_data;
511 }
512}
513
514#[derive(Debug, Clone, Default)]
520pub struct IsoMesh {
521 pub vertices: Vec<[f64; 3]>,
523 pub triangles: Vec<[usize; 3]>,
525 pub normals: Vec<[f64; 3]>,
527}
528
529impl IsoMesh {
530 pub fn new() -> Self {
532 Self::default()
533 }
534
535 pub fn num_triangles(&self) -> usize {
537 self.triangles.len()
538 }
539
540 pub fn num_vertices(&self) -> usize {
542 self.vertices.len()
543 }
544}
545
546pub struct MarchingCubes;
548
549const MC_EDGE_TABLE: [u16; 256] = [
551 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a,
552 0xd03, 0xe09, 0xf00, 0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895,
553 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 0x230, 0x339, 0x033, 0x13a, 0x636, 0x73f, 0x435,
554 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 0x3a0, 0x2a9, 0x1a3, 0x0aa,
555 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 0x460,
556 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963,
557 0xa69, 0xb60, 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0x0ff, 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff,
558 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c,
559 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6,
560 0x2cf, 0x1c5, 0x0cc, 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 0x8c0, 0x9c9,
561 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9,
562 0x7c0, 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x055, 0x35f, 0x256,
563 0x55a, 0x453, 0x759, 0x650, 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc,
564 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f,
565 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460, 0xca0, 0xda9, 0xea3,
566 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0x0aa, 0x1a3, 0x2a9, 0x3a0,
567 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a,
568 0x033, 0x339, 0x230, 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795,
569 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190, 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905,
570 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000,
571];
572
573const MC_TRI_TABLE: [[i8; 16]; 256] = include_mc_tri_table();
576
577const fn include_mc_tri_table() -> [[i8; 16]; 256] {
578 [
581 [
582 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
583 ],
584 [0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
585 [0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
586 [1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
587 [1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
588 [0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
589 [9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
590 [2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1],
591 [3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
592 [0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
593 [1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
594 [1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1],
595 [3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
596 [0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1],
597 [3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1],
598 [9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
599 [4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
600 [4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
601 [0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
602 [4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1],
603 [1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
604 [3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1],
605 [9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1],
606 [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1],
607 [8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
608 [11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1],
609 [9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1],
610 [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1],
611 [3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1],
612 [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1],
613 [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1],
614 [4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1],
615 [9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
616 [9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
617 [0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
618 [8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1],
619 [1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
620 [3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1],
621 [5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1],
622 [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1],
623 [9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
624 [0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1],
625 [0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1],
626 [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1],
627 [10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1],
628 [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1],
629 [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1],
630 [5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1],
631 [9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
632 [9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1],
633 [0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1],
634 [1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
635 [9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1],
636 [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1],
637 [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1],
638 [2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1],
639 [7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1],
640 [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1],
641 [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1],
642 [11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1],
643 [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1],
644 [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1],
645 [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1],
646 [11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
647 [10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
648 [0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
649 [9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
650 [1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1],
651 [1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
652 [1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1],
653 [9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1],
654 [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1],
655 [2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
656 [11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1],
657 [0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1],
658 [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1],
659 [6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1],
660 [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1],
661 [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1],
662 [6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1],
663 [5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
664 [4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1],
665 [1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1],
666 [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1],
667 [6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1],
668 [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1],
669 [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1],
670 [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1],
671 [3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1],
672 [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1],
673 [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1],
674 [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1],
675 [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1],
676 [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1],
677 [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1],
678 [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1],
679 [10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
680 [4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1],
681 [10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1],
682 [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1],
683 [1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1],
684 [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1],
685 [0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
686 [8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1],
687 [10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1],
688 [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1],
689 [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1],
690 [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1],
691 [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1],
692 [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1],
693 [3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1],
694 [6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
695 [7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1],
696 [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1],
697 [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1],
698 [10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1],
699 [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1],
700 [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1],
701 [7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1],
702 [7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
703 [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1],
704 [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1],
705 [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1],
706 [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1],
707 [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1],
708 [0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
709 [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1],
710 [7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
711 [7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
712 [3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
713 [0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
714 [8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1],
715 [10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
716 [1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1],
717 [2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1],
718 [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1],
719 [7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
720 [7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1],
721 [2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1],
722 [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1],
723 [10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1],
724 [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1],
725 [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1],
726 [7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1],
727 [6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
728 [3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1],
729 [8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1],
730 [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1],
731 [6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1],
732 [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1],
733 [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1],
734 [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1],
735 [8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1],
736 [0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
737 [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1],
738 [1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1],
739 [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1],
740 [10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1],
741 [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1],
742 [10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
743 [4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
744 [0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1],
745 [5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1],
746 [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1],
747 [9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1],
748 [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1],
749 [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1],
750 [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1],
751 [7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1],
752 [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1],
753 [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1],
754 [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1],
755 [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1],
756 [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1],
757 [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1],
758 [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1],
759 [6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1],
760 [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1],
761 [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1],
762 [6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1],
763 [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1],
764 [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1],
765 [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1],
766 [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1],
767 [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1],
768 [9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1],
769 [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1],
770 [1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
771 [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1],
772 [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1],
773 [0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
774 [10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
775 [11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
776 [11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1],
777 [5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1],
778 [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1],
779 [11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1],
780 [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1],
781 [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1],
782 [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1],
783 [2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1],
784 [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1],
785 [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1],
786 [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1],
787 [1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
788 [0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1],
789 [9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1],
790 [9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
791 [5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1],
792 [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1],
793 [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1],
794 [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1],
795 [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1],
796 [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1],
797 [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1],
798 [9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
799 [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1],
800 [5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1],
801 [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1],
802 [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1],
803 [8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1],
804 [0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
805 [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1],
806 [9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
807 [4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1],
808 [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1],
809 [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1],
810 [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1],
811 [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1],
812 [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1],
813 [11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1],
814 [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1],
815 [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1],
816 [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1],
817 [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1],
818 [1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
819 [4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1],
820 [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1],
821 [4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
822 [4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
823 [9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
824 [3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1],
825 [0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1],
826 [3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
827 [1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1],
828 [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1],
829 [0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
830 [3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
831 [2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1],
832 [9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
833 [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1],
834 [1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
835 [1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
836 [0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
837 [0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
838 [
839 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
840 ],
841 ]
842}
843
844impl MarchingCubes {
845 pub fn extract(grid: &SdfGrid3D, isovalue: f64) -> IsoMesh {
847 let mut mesh = IsoMesh::new();
848 let corner_offsets: [[usize; 3]; 8] = [
850 [0, 0, 0],
851 [1, 0, 0],
852 [1, 1, 0],
853 [0, 1, 0],
854 [0, 0, 1],
855 [1, 0, 1],
856 [1, 1, 1],
857 [0, 1, 1],
858 ];
859 let edge_pairs: [(usize, usize); 12] = [
861 (0, 1),
862 (1, 2),
863 (2, 3),
864 (3, 0),
865 (4, 5),
866 (5, 6),
867 (6, 7),
868 (7, 4),
869 (0, 4),
870 (1, 5),
871 (2, 6),
872 (3, 7),
873 ];
874
875 let mut vertex_cache: std::collections::HashMap<(usize, usize), usize> =
876 std::collections::HashMap::new();
877
878 let nx = grid.nx.saturating_sub(1);
879 let ny = grid.ny.saturating_sub(1);
880 let nz = grid.nz.saturating_sub(1);
881
882 for iz in 0..nz {
883 for iy in 0..ny {
884 for ix in 0..nx {
885 let mut corner_vals = [0.0f64; 8];
887 let mut corner_pos = [[0.0f64; 3]; 8];
888 for (ci, &[dx, dy, dz]) in corner_offsets.iter().enumerate() {
889 corner_pos[ci] = grid.voxel_center(ix + dx, iy + dy, iz + dz);
890 corner_vals[ci] = grid.get(ix + dx, iy + dy, iz + dz);
891 }
892
893 let cube_idx: u8 = corner_vals.iter().enumerate().fold(0u8, |acc, (ci, &v)| {
895 if v < isovalue { acc | (1 << ci) } else { acc }
896 });
897 if MC_EDGE_TABLE[cube_idx as usize] == 0 {
898 continue;
899 }
900
901 let mut edge_verts = [usize::MAX; 12];
903 let edge_bits = MC_EDGE_TABLE[cube_idx as usize];
904 for (ei, &(v0, v1)) in edge_pairs.iter().enumerate() {
905 if edge_bits & (1 << ei) == 0 {
906 continue;
907 }
908 let base = ix + grid.nx * (iy + grid.ny * iz);
910 let key = (base, ei);
911 let vi = *vertex_cache.entry(key).or_insert_with(|| {
912 let t = (isovalue - corner_vals[v0])
913 / (corner_vals[v1] - corner_vals[v0] + 1e-30).clamp(-1e10, 1e10);
914 let t = t.clamp(0.0, 1.0);
915 let p = [
916 corner_pos[v0][0] + t * (corner_pos[v1][0] - corner_pos[v0][0]),
917 corner_pos[v0][1] + t * (corner_pos[v1][1] - corner_pos[v0][1]),
918 corner_pos[v0][2] + t * (corner_pos[v1][2] - corner_pos[v0][2]),
919 ];
920 let idx = mesh.vertices.len();
921 mesh.vertices.push(p);
922 mesh.normals.push([0.0, 1.0, 0.0]); idx
924 });
925 edge_verts[ei] = vi;
926 }
927
928 let tri_row = &MC_TRI_TABLE[cube_idx as usize];
930 let mut ti = 0;
931 while ti < 16 && tri_row[ti] >= 0 {
932 let a = edge_verts[tri_row[ti] as usize];
933 let b = edge_verts[tri_row[ti + 1] as usize];
934 let c = edge_verts[tri_row[ti + 2] as usize];
935 if a != usize::MAX && b != usize::MAX && c != usize::MAX {
936 mesh.triangles.push([a, b, c]);
937 }
938 ti += 3;
939 }
940 }
941 }
942 }
943
944 for (i, &p) in mesh.vertices.iter().enumerate() {
946 mesh.normals[i] = vec3_norm(grid.gradient(p));
947 }
948
949 mesh
950 }
951}
952
953pub struct DualContouring;
959
960impl DualContouring {
961 pub fn extract(grid: &SdfGrid3D, isovalue: f64) -> IsoMesh {
966 let mut mesh = IsoMesh::new();
967 let nx = grid.nx;
968 let ny = grid.ny;
969 let nz = grid.nz;
970
971 let mut voxel_vertex: std::collections::HashMap<usize, usize> =
973 std::collections::HashMap::new();
974
975 for iz in 0..nz.saturating_sub(1) {
976 for iy in 0..ny.saturating_sub(1) {
977 for ix in 0..nx.saturating_sub(1) {
978 let v000 = grid.get(ix, iy, iz);
979 let v100 = grid.get(ix + 1, iy, iz);
980 let v010 = grid.get(ix, iy + 1, iz);
981 let v001 = grid.get(ix, iy, iz + 1);
982 let active = (v000 < isovalue) != (v100 < isovalue)
984 || (v000 < isovalue) != (v010 < isovalue)
985 || (v000 < isovalue) != (v001 < isovalue);
986 if !active {
987 continue;
988 }
989
990 let center = grid.voxel_center(ix, iy, iz);
992 let vi = mesh.vertices.len();
993 mesh.vertices.push(center);
994 mesh.normals.push(vec3_norm(grid.gradient(center)));
995 let key = ix + nx * (iy + ny * iz);
996 voxel_vertex.insert(key, vi);
997 }
998 }
999 }
1000
1001 let directions = [
1003 (1, 0, 0, 1usize, 0usize, 0usize),
1004 (0, 1, 0, 0, 1, 0),
1005 (0, 0, 1, 0, 0, 1),
1006 ];
1007 for iz in 0..nz.saturating_sub(1) {
1008 for iy in 0..ny.saturating_sub(1) {
1009 for ix in 0..nx.saturating_sub(1) {
1010 let v0 = grid.get(ix, iy, iz);
1011 for &(dx, dy, dz, ox, oy, oz) in &directions {
1012 let nx2 = ix + dx;
1013 let ny2 = iy + dy;
1014 let nz2 = iz + dz;
1015 if nx2 >= nx || ny2 >= ny || nz2 >= nz {
1016 continue;
1017 }
1018 let v1 = grid.get(nx2, ny2, nz2);
1019 if (v0 < isovalue) == (v1 < isovalue) {
1020 continue;
1021 }
1022 let k0 = ix + nx * (iy + ny * iz);
1024 let k1 = (ix + ox) + nx * ((iy + oy) + ny * (iz + oz));
1025 let k2 = (ix + ox + dx) + nx * ((iy + oy + dy) + ny * (iz + oz + dz));
1026 let k3 = (ix + dx) + nx * ((iy + dy) + ny * (iz + dz));
1027 if let (Some(&a), Some(&b), Some(&c), Some(&d)) = (
1028 voxel_vertex.get(&k0),
1029 voxel_vertex.get(&k1),
1030 voxel_vertex.get(&k2),
1031 voxel_vertex.get(&k3),
1032 ) {
1033 mesh.triangles.push([a, b, c]);
1034 mesh.triangles.push([a, c, d]);
1035 }
1036 }
1037 }
1038 }
1039 }
1040
1041 mesh
1042 }
1043}
1044
1045pub struct MarchingTetrahedra;
1053
1054impl MarchingTetrahedra {
1055 pub fn extract(grid: &SdfGrid3D, isovalue: f64) -> IsoMesh {
1057 let mut mesh = IsoMesh::new();
1058 let nx = grid.nx.saturating_sub(1);
1059 let ny = grid.ny.saturating_sub(1);
1060 let nz = grid.nz.saturating_sub(1);
1061
1062 let tet_indices: [[usize; 4]; 5] = [
1064 [0, 1, 3, 5],
1065 [1, 3, 5, 6],
1066 [0, 3, 4, 5],
1067 [3, 5, 6, 7],
1068 [0, 3, 4, 7],
1069 ];
1070 let corner_offsets: [[usize; 3]; 8] = [
1071 [0, 0, 0],
1072 [1, 0, 0],
1073 [1, 1, 0],
1074 [0, 1, 0],
1075 [0, 0, 1],
1076 [1, 0, 1],
1077 [1, 1, 1],
1078 [0, 1, 1],
1079 ];
1080
1081 for iz in 0..nz {
1082 for iy in 0..ny {
1083 for ix in 0..nx {
1084 let mut cvals = [0.0f64; 8];
1085 let mut cpos = [[0.0f64; 3]; 8];
1086 for (ci, &[dx, dy, dz]) in corner_offsets.iter().enumerate() {
1087 cpos[ci] = grid.voxel_center(ix + dx, iy + dy, iz + dz);
1088 cvals[ci] = grid.get(ix + dx, iy + dy, iz + dz);
1089 }
1090
1091 for &tet in &tet_indices {
1092 let tv: [usize; 4] = tet;
1093 let tp: [[f64; 3]; 4] =
1094 [cpos[tv[0]], cpos[tv[1]], cpos[tv[2]], cpos[tv[3]]];
1095 let td: [f64; 4] = [cvals[tv[0]], cvals[tv[1]], cvals[tv[2]], cvals[tv[3]]];
1096 Self::process_tet(&tp, &td, isovalue, &mut mesh);
1097 }
1098 }
1099 }
1100 }
1101 mesh
1102 }
1103
1104 fn interp(p0: [f64; 3], p1: [f64; 3], d0: f64, d1: f64, iso: f64) -> [f64; 3] {
1105 let t = ((iso - d0) / (d1 - d0 + 1e-30)).clamp(0.0, 1.0);
1106 [
1107 p0[0] + t * (p1[0] - p0[0]),
1108 p0[1] + t * (p1[1] - p0[1]),
1109 p0[2] + t * (p1[2] - p0[2]),
1110 ]
1111 }
1112
1113 fn process_tet(pos: &[[f64; 3]; 4], vals: &[f64; 4], iso: f64, mesh: &mut IsoMesh) {
1114 let idx: u8 = vals.iter().enumerate().fold(
1115 0u8,
1116 |acc, (i, &v)| {
1117 if v < iso { acc | (1 << i) } else { acc }
1118 },
1119 );
1120 let edges: [(usize, usize); 6] = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)];
1122 let tri_cases: [[i8; 7]; 16] = [
1123 [-1, -1, -1, -1, -1, -1, -1],
1124 [0, 3, 2, -1, -1, -1, -1],
1125 [0, 1, 4, -1, -1, -1, -1],
1126 [1, 3, 2, 1, 4, 3, -1],
1127 [1, 2, 5, -1, -1, -1, -1],
1128 [0, 3, 5, 0, 5, 1, -1],
1129 [0, 2, 5, 0, 5, 4, -1],
1130 [3, 5, 4, -1, -1, -1, -1],
1131 [3, 5, 4, -1, -1, -1, -1],
1132 [0, 5, 2, 0, 4, 5, -1],
1133 [0, 5, 3, 0, 1, 5, -1],
1134 [1, 2, 5, -1, -1, -1, -1],
1135 [1, 3, 2, 1, 4, 3, -1],
1136 [0, 1, 4, -1, -1, -1, -1],
1137 [0, 3, 2, -1, -1, -1, -1],
1138 [-1, -1, -1, -1, -1, -1, -1],
1139 ];
1140 let tc = tri_cases[idx as usize];
1141 let mut ti = 0;
1142 while ti < 6 && tc[ti] >= 0 {
1143 let ei0 = tc[ti] as usize;
1144 let ei1 = tc[ti + 1] as usize;
1145 let ei2 = tc[ti + 2] as usize;
1146 let (a0, a1) = edges[ei0];
1147 let (b0, b1) = edges[ei1];
1148 let (c0, c1) = edges[ei2];
1149 let pa = Self::interp(pos[a0], pos[a1], vals[a0], vals[a1], iso);
1150 let pb = Self::interp(pos[b0], pos[b1], vals[b0], vals[b1], iso);
1151 let pc = Self::interp(pos[c0], pos[c1], vals[c0], vals[c1], iso);
1152 let vi = mesh.vertices.len();
1153 mesh.vertices.extend_from_slice(&[pa, pb, pc]);
1154 mesh.normals.extend_from_slice(&[[0.0, 1.0, 0.0]; 3]);
1155 mesh.triangles.push([vi, vi + 1, vi + 2]);
1156 ti += 3;
1157 }
1158 }
1159}
1160
1161pub struct ImplicitConvolution;
1167
1168impl ImplicitConvolution {
1169 pub fn offset_surface<F: Fn([f64; 3]) -> f64>(f: F, offset: f64) -> impl Fn([f64; 3]) -> f64 {
1171 move |p| f(p) - offset
1172 }
1173
1174 pub fn gaussian_blur<F: Fn([f64; 3]) -> f64>(f: F, sigma: f64) -> impl Fn([f64; 3]) -> f64 {
1178 move |p: [f64; 3]| {
1179 let s = sigma;
1180 let mut sum = 0.0;
1181 for dx in [-s, s] {
1182 for dy in [-s, s] {
1183 for dz in [-s, s] {
1184 sum += f([p[0] + dx, p[1] + dy, p[2] + dz]);
1185 }
1186 }
1187 }
1188 sum / 8.0
1189 }
1190 }
1191
1192 pub fn minkowski_sum<F: Fn([f64; 3]) -> f64>(f: F, radius: f64) -> impl Fn([f64; 3]) -> f64 {
1196 move |p| f(p) - radius
1197 }
1198}
1199
1200#[derive(Debug, Clone, Copy)]
1206pub struct RayMarchHit {
1207 pub position: [f64; 3],
1209 pub t: f64,
1211 pub steps: usize,
1213}
1214
1215pub struct RayMarchSdf;
1217
1218impl RayMarchSdf {
1219 pub fn march<F: Fn([f64; 3]) -> f64>(
1224 f: &F,
1225 origin: [f64; 3],
1226 dir: [f64; 3],
1227 max_dist: f64,
1228 max_steps: usize,
1229 epsilon: f64,
1230 ) -> Option<RayMarchHit> {
1231 let dir = vec3_norm(dir);
1232 let mut t = 0.0;
1233 for step in 0..max_steps {
1234 let p = [
1235 origin[0] + t * dir[0],
1236 origin[1] + t * dir[1],
1237 origin[2] + t * dir[2],
1238 ];
1239 let d = f(p);
1240 if d.abs() < epsilon {
1241 return Some(RayMarchHit {
1242 position: p,
1243 t,
1244 steps: step + 1,
1245 });
1246 }
1247 t += d.abs().max(epsilon * 0.1);
1248 if t > max_dist {
1249 break;
1250 }
1251 }
1252 None
1253 }
1254
1255 pub fn ambient_occlusion<F: Fn([f64; 3]) -> f64>(
1257 f: &F,
1258 pos: [f64; 3],
1259 normal: [f64; 3],
1260 samples: usize,
1261 step_size: f64,
1262 ) -> f64 {
1263 let mut occ = 0.0;
1264 for i in 1..=samples {
1265 let t = i as f64 * step_size;
1266 let p = vec3_add(pos, vec3_scale(normal, t));
1267 let d = f(p);
1268 occ += (t - d) / (2.0_f64.powi(i as i32));
1269 }
1270 (1.0 - occ).clamp(0.0, 1.0)
1271 }
1272}
1273
1274#[cfg(test)]
1279mod tests {
1280 use super::*;
1281 use std::f64::consts::PI;
1282
1283 #[test]
1284 fn test_sphere_sdf_center() {
1285 let s = ImplicitSphere::new([0.0, 0.0, 0.0], 1.0);
1286 assert!((s.sdf([0.0, 0.0, 0.0]) - (-1.0)).abs() < 1e-10);
1287 }
1288
1289 #[test]
1290 fn test_sphere_sdf_surface() {
1291 let s = ImplicitSphere::new([0.0, 0.0, 0.0], 1.0);
1292 assert!(s.sdf([1.0, 0.0, 0.0]).abs() < 1e-10);
1293 }
1294
1295 #[test]
1296 fn test_sphere_sdf_outside() {
1297 let s = ImplicitSphere::new([0.0, 0.0, 0.0], 1.0);
1298 assert!(s.sdf([2.0, 0.0, 0.0]) > 0.0);
1299 }
1300
1301 #[test]
1302 fn test_sphere_gradient_unit_length() {
1303 let s = ImplicitSphere::new([0.0, 0.0, 0.0], 1.0);
1304 let g = s.gradient([2.0, 0.0, 0.0]);
1305 let len = (g[0] * g[0] + g[1] * g[1] + g[2] * g[2]).sqrt();
1306 assert!((len - 1.0).abs() < 1e-10);
1307 }
1308
1309 #[test]
1310 fn test_box_sdf_inside() {
1311 let b = ImplicitBox::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1312 assert!(b.sdf([0.0, 0.0, 0.0]) < 0.0);
1313 }
1314
1315 #[test]
1316 fn test_box_sdf_surface() {
1317 let b = ImplicitBox::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1318 assert!(b.sdf([1.0, 0.0, 0.0]).abs() < 1e-8);
1319 }
1320
1321 #[test]
1322 fn test_box_sdf_outside() {
1323 let b = ImplicitBox::new([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
1324 assert!(b.sdf([2.0, 0.0, 0.0]) > 0.0);
1325 }
1326
1327 #[test]
1328 fn test_rounded_box_sdf() {
1329 let b = ImplicitBox::rounded([0.0, 0.0, 0.0], [1.0, 1.0, 1.0], 0.1);
1330 assert!(b.sdf([0.0, 0.0, 0.0]) < 0.0);
1331 }
1332
1333 #[test]
1334 fn test_capsule_sdf_on_axis() {
1335 let c = ImplicitCapsule::new([0.0, 0.0, 0.0], [0.0, 2.0, 0.0], 0.5);
1336 assert!(c.sdf([0.5, 1.0, 0.0]).abs() < 1e-10);
1338 }
1339
1340 #[test]
1341 fn test_capsule_sdf_inside() {
1342 let c = ImplicitCapsule::new([0.0, 0.0, 0.0], [0.0, 2.0, 0.0], 0.5);
1343 assert!(c.sdf([0.0, 1.0, 0.0]) < 0.0);
1344 }
1345
1346 #[test]
1347 fn test_torus_sdf_on_surface() {
1348 let t = ImplicitTorus::new(2.0, 0.5);
1349 let p = [2.5, 0.0, 0.0]; assert!(t.sdf(p).abs() < 1e-10);
1352 }
1353
1354 #[test]
1355 fn test_torus_sdf_inside() {
1356 let t = ImplicitTorus::new(2.0, 0.5);
1357 let p = [2.0, 0.0, 0.0]; assert!(t.sdf(p) < 0.0);
1359 }
1360
1361 #[test]
1362 fn test_smooth_min_polynomial_limit() {
1363 let a = 1.0;
1365 let b = 3.0;
1366 let sm = smooth_min_polynomial(a, b, 0.001);
1367 assert!((sm - a.min(b)).abs() < 0.01);
1368 }
1369
1370 #[test]
1371 fn test_smooth_min_exponential_symmetry() {
1372 let a = 1.0;
1373 let b = 2.0;
1374 let k = 1.0;
1375 let sm1 = smooth_min_exponential(a, b, k);
1376 let sm2 = smooth_min_exponential(b, a, k);
1377 assert!((sm1 - sm2).abs() < 1e-12);
1378 }
1379
1380 #[test]
1381 fn test_implicit_blend_smooth_union() {
1382 let blend = ImplicitBlend::new(0.3);
1383 let u = blend.smooth_union(0.5, -0.1);
1384 assert!(u <= 0.5 + 0.01);
1386 }
1387
1388 #[test]
1389 fn test_implicit_blend_hard_union() {
1390 assert_eq!(ImplicitBlend::union(1.0, 2.0), 1.0);
1391 assert_eq!(ImplicitBlend::union(-1.0, 2.0), -1.0);
1392 }
1393
1394 #[test]
1395 fn test_implicit_blend_subtraction() {
1396 let r = ImplicitBlend::subtraction(2.0, -1.0);
1398 assert!(r > 0.0);
1399 }
1400
1401 #[test]
1402 fn test_sdf_grid_from_sphere() {
1403 let sphere = ImplicitSphere::new([0.5, 0.5, 0.5], 0.3);
1404 let grid = SdfGrid3D::from_sdf(10, 10, 10, [0.0, 0.0, 0.0], 0.1, |p| sphere.sdf(p));
1405 assert_eq!(grid.data.len(), 1000);
1406 let v = grid.get(5, 5, 5);
1408 assert!(v < 0.0);
1409 }
1410
1411 #[test]
1412 fn test_sdf_grid_interpolate() {
1413 let sphere = ImplicitSphere::new([0.5, 0.5, 0.5], 0.3);
1414 let grid = SdfGrid3D::from_sdf(20, 20, 20, [0.0, 0.0, 0.0], 0.05, |p| sphere.sdf(p));
1415 let v = grid.interpolate([0.5, 0.5, 0.5]);
1416 assert!(v < 0.0, "center should be inside: {}", v);
1417 }
1418
1419 #[test]
1420 fn test_marching_cubes_sphere() {
1421 let sphere = ImplicitSphere::new([2.0, 2.0, 2.0], 1.0);
1422 let grid = SdfGrid3D::from_sdf(20, 20, 20, [0.0, 0.0, 0.0], 0.25, |p| sphere.sdf(p));
1423 let mesh = MarchingCubes::extract(&grid, 0.0);
1424 assert!(
1425 !mesh.vertices.is_empty(),
1426 "marching cubes should produce vertices"
1427 );
1428 assert!(
1429 !mesh.triangles.is_empty(),
1430 "marching cubes should produce triangles"
1431 );
1432 }
1433
1434 #[test]
1435 fn test_marching_tetrahedra_sphere() {
1436 let sphere = ImplicitSphere::new([2.0, 2.0, 2.0], 1.0);
1437 let grid = SdfGrid3D::from_sdf(16, 16, 16, [0.0, 0.0, 0.0], 0.3, |p| sphere.sdf(p));
1438 let mesh = MarchingTetrahedra::extract(&grid, 0.0);
1439 assert!(
1440 !mesh.triangles.is_empty(),
1441 "marching tetrahedra should produce triangles"
1442 );
1443 }
1444
1445 #[test]
1446 fn test_dual_contouring_sphere() {
1447 let sphere = ImplicitSphere::new([2.0, 2.0, 2.0], 1.0);
1448 let grid = SdfGrid3D::from_sdf(16, 16, 16, [0.0, 0.0, 0.0], 0.3, |p| sphere.sdf(p));
1449 let mesh = DualContouring::extract(&grid, 0.0);
1450 assert!(
1451 !mesh.vertices.is_empty(),
1452 "dual contouring should produce vertices"
1453 );
1454 }
1455
1456 #[test]
1457 fn test_ray_march_sphere_hit() {
1458 let sphere = ImplicitSphere::new([0.0, 0.0, 5.0], 1.0);
1459 let f = |p: [f64; 3]| sphere.sdf(p);
1460 let hit = RayMarchSdf::march(&f, [0.0, 0.0, 0.0], [0.0, 0.0, 1.0], 20.0, 200, 1e-4);
1461 assert!(hit.is_some(), "ray should hit sphere");
1462 let h = hit.unwrap();
1463 assert!((h.t - 4.0).abs() < 0.01, "hit at t≈4, got {}", h.t);
1464 }
1465
1466 #[test]
1467 fn test_ray_march_sphere_miss() {
1468 let sphere = ImplicitSphere::new([0.0, 0.0, 5.0], 1.0);
1469 let f = |p: [f64; 3]| sphere.sdf(p);
1470 let hit = RayMarchSdf::march(&f, [0.0, 0.0, 0.0], [0.0, 0.0, -1.0], 20.0, 200, 1e-4);
1472 assert!(hit.is_none(), "ray in wrong direction should miss");
1473 }
1474
1475 #[test]
1476 fn test_ambient_occlusion_on_surface() {
1477 let sphere = ImplicitSphere::new([0.0, 0.0, 0.0], 1.0);
1478 let f = |p: [f64; 3]| sphere.sdf(p);
1479 let ao = RayMarchSdf::ambient_occlusion(&f, [1.0, 0.0, 0.0], [1.0, 0.0, 0.0], 5, 0.1);
1480 assert!(
1481 (0.0..=1.0).contains(&ao),
1482 "AO should be in [0,1], got {}",
1483 ao
1484 );
1485 }
1486
1487 #[test]
1488 fn test_sdf_reinitialize() {
1489 let sphere = ImplicitSphere::new([2.0, 2.0, 2.0], 1.0);
1490 let mut grid = SdfGrid3D::from_sdf(16, 16, 16, [0.0, 0.0, 0.0], 0.3, |p| sphere.sdf(p));
1491 grid.set(5, 5, 5, 100.0);
1493 SdfReinitialize::reinitialize(&mut grid);
1494 assert!(grid.get(5, 5, 5).abs() < 50.0);
1496 }
1497
1498 #[test]
1499 fn test_iso_mesh_empty() {
1500 let mesh = IsoMesh::new();
1501 assert_eq!(mesh.num_vertices(), 0);
1502 assert_eq!(mesh.num_triangles(), 0);
1503 }
1504
1505 #[test]
1506 fn test_offset_surface() {
1507 let sphere = ImplicitSphere::new([0.0, 0.0, 0.0], 1.0);
1508 let offset = ImplicitConvolution::offset_surface(|p| sphere.sdf(p), 0.5);
1509 assert!(offset([1.5, 0.0, 0.0]).abs() < 1e-10);
1511 }
1512
1513 #[test]
1514 fn test_smooth_min_polynomial_symmetric() {
1515 let a = 0.5;
1516 let b = 1.5;
1517 let k = 0.3;
1518 let s1 = smooth_min_polynomial(a, b, k);
1519 let s2 = smooth_min_polynomial(b, a, k);
1520 assert!((s1 - s2).abs() < 1e-12);
1521 }
1522
1523 #[test]
1524 fn test_sphere_hessian_shape() {
1525 let s = ImplicitSphere::new([0.0, 0.0, 0.0], 1.0);
1526 let h = s.hessian([2.0, 0.0, 0.0]);
1527 assert_eq!(h.len(), 9);
1528 }
1529
1530 #[test]
1531 fn test_capsule_gradient_unit() {
1532 let c = ImplicitCapsule::new([0.0, 0.0, 0.0], [0.0, 2.0, 0.0], 0.5);
1533 let g = c.gradient([1.0, 1.0, 0.0]);
1534 let len = (g[0] * g[0] + g[1] * g[1] + g[2] * g[2]).sqrt();
1535 assert!((len - 1.0).abs() < 1e-10);
1536 }
1537
1538 #[test]
1539 fn test_sdf_grid_gradient_direction() {
1540 let sphere = ImplicitSphere::new([2.0, 2.0, 2.0], 1.0);
1541 let grid = SdfGrid3D::from_sdf(20, 20, 20, [0.0, 0.0, 0.0], 0.2, |p| sphere.sdf(p));
1542 let g = grid.gradient([3.5, 2.0, 2.0]);
1544 assert!(g[0] > 0.0, "gradient x component should be positive");
1545 }
1546
1547 #[test]
1548 fn test_implicit_torus_outside() {
1549 let t = ImplicitTorus::new(2.0, 0.5);
1550 assert!(t.sdf([10.0, 0.0, 0.0]) > 0.0);
1552 }
1553
1554 #[test]
1555 fn test_sdf_numerical_gradient() {
1556 let sphere = ImplicitSphere::new([0.0, 0.0, 0.0], 1.0);
1557 let f = |p: [f64; 3]| sphere.sdf(p);
1558 let g = sdf_gradient_numerical(&f, [2.0, 0.0, 0.0], 1e-4);
1559 assert!(g[0] > 0.0);
1561 }
1562
1563 #[test]
1564 fn test_marching_cubes_box() {
1565 let b = ImplicitBox::new([2.0, 2.0, 2.0], [0.8, 0.8, 0.8]);
1566 let grid = SdfGrid3D::from_sdf(20, 20, 20, [0.0, 0.0, 0.0], 0.25, |p| b.sdf(p));
1567 let mesh = MarchingCubes::extract(&grid, 0.0);
1568 assert!(
1569 !mesh.triangles.is_empty(),
1570 "box isosurface should have triangles"
1571 );
1572 }
1573
1574 #[test]
1575 fn test_pi_used() {
1576 let _ = PI;
1578 }
1579}