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