1#[derive(Debug, Clone, PartialEq)]
20pub enum SdfPrimitive {
21 Sphere {
23 center: [f64; 3],
25 radius: f64,
27 },
28 Box3d {
30 center: [f64; 3],
32 half_extents: [f64; 3],
34 },
35 Cylinder {
37 center: [f64; 3],
39 radius: f64,
41 height: f64,
43 },
44 Torus {
46 center: [f64; 3],
48 r_major: f64,
50 r_minor: f64,
52 },
53}
54
55impl SdfPrimitive {
56 pub fn evaluate(&self, p: [f64; 3]) -> f64 {
58 match self {
59 SdfPrimitive::Sphere { center, radius } => sdf_sphere(p, *center, *radius),
60 SdfPrimitive::Box3d {
61 center,
62 half_extents,
63 } => sdf_box(p, *center, *half_extents),
64 SdfPrimitive::Cylinder {
65 center,
66 radius,
67 height,
68 } => sdf_cylinder(p, *center, *radius, *height),
69 SdfPrimitive::Torus {
70 center,
71 r_major,
72 r_minor,
73 } => sdf_torus(p, *center, *r_major, *r_minor),
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq)]
84pub enum SdfOp {
85 Union,
87 Intersection,
89 Subtraction,
91 SmoothUnion {
93 k: f64,
95 },
96 Offset {
98 d: f64,
100 },
101}
102
103impl SdfOp {
104 pub fn apply(&self, d1: f64, d2: f64) -> f64 {
107 match self {
108 SdfOp::Union => d1.min(d2),
109 SdfOp::Intersection => d1.max(d2),
110 SdfOp::Subtraction => d1.max(-d2),
111 SdfOp::SmoothUnion { k } => sdf_smooth_union(d1, d2, *k),
112 SdfOp::Offset { d } => d1 + d,
113 }
114 }
115}
116
117#[derive(Debug, Clone)]
123pub enum SdfNode {
124 Leaf(SdfPrimitive),
126 Op {
128 op: SdfOp,
130 left: Box<SdfNode>,
132 right: Box<SdfNode>,
134 },
135}
136
137impl SdfNode {
138 pub fn leaf(prim: SdfPrimitive) -> Self {
140 SdfNode::Leaf(prim)
141 }
142
143 pub fn op(op: SdfOp, left: SdfNode, right: SdfNode) -> Self {
145 SdfNode::Op {
146 op,
147 left: Box::new(left),
148 right: Box::new(right),
149 }
150 }
151
152 pub fn evaluate(&self, p: [f64; 3]) -> f64 {
154 match self {
155 SdfNode::Leaf(prim) => prim.evaluate(p),
156 SdfNode::Op { op, left, right } => {
157 let d1 = left.evaluate(p);
158 let d2 = right.evaluate(p);
159 op.apply(d1, d2)
160 }
161 }
162 }
163}
164
165#[derive(Debug, Clone)]
174pub struct SdfGrid {
175 pub dimensions: [usize; 3],
177 pub spacing: f64,
179 pub origin: [f64; 3],
181 pub data: Vec<f32>,
185}
186
187impl SdfGrid {
188 pub fn new(dimensions: [usize; 3], spacing: f64, origin: [f64; 3]) -> Self {
190 let n = dimensions[0] * dimensions[1] * dimensions[2];
191 Self {
192 dimensions,
193 spacing,
194 origin,
195 data: vec![f32::MAX; n],
196 }
197 }
198
199 pub fn voxel_center(&self, i: usize, j: usize, k: usize) -> [f64; 3] {
201 [
202 self.origin[0] + self.spacing * (i as f64 + 0.5),
203 self.origin[1] + self.spacing * (j as f64 + 0.5),
204 self.origin[2] + self.spacing * (k as f64 + 0.5),
205 ]
206 }
207
208 pub fn index(&self, i: usize, j: usize, k: usize) -> usize {
210 i + self.dimensions[0] * (j + self.dimensions[1] * k)
211 }
212
213 pub fn compute_from_sdf(&mut self, sdf: &dyn Fn([f64; 3]) -> f64) {
218 let [nx, ny, nz] = self.dimensions;
219 for k in 0..nz {
220 for j in 0..ny {
221 for i in 0..nx {
222 let p = self.voxel_center(i, j, k);
223 let idx = self.index(i, j, k);
224 self.data[idx] = sdf(p) as f32;
225 }
226 }
227 }
228 }
229
230 pub fn len(&self) -> usize {
232 self.dimensions[0] * self.dimensions[1] * self.dimensions[2]
233 }
234
235 pub fn is_empty(&self) -> bool {
237 self.len() == 0
238 }
239}
240
241#[derive(Debug, Clone, Default)]
250pub struct GpuSdfCompute;
251
252impl GpuSdfCompute {
253 pub fn new() -> Self {
255 Self
256 }
257
258 pub fn dispatch_compute(&self, grid: &mut SdfGrid, primitives: &[SdfPrimitive]) {
263 if primitives.is_empty() {
264 return;
265 }
266 grid.compute_from_sdf(&|p| {
267 primitives
268 .iter()
269 .map(|prim| prim.evaluate(p))
270 .fold(f64::INFINITY, f64::min)
271 });
272 }
273}
274
275pub fn sdf_sphere(p: [f64; 3], center: [f64; 3], r: f64) -> f64 {
283 let dx = p[0] - center[0];
284 let dy = p[1] - center[1];
285 let dz = p[2] - center[2];
286 (dx * dx + dy * dy + dz * dz).sqrt() - r
287}
288
289pub fn sdf_box(p: [f64; 3], center: [f64; 3], b: [f64; 3]) -> f64 {
292 let qx = (p[0] - center[0]).abs() - b[0];
293 let qy = (p[1] - center[1]).abs() - b[1];
294 let qz = (p[2] - center[2]).abs() - b[2];
295 let outer =
296 (qx.max(0.0) * qx.max(0.0) + qy.max(0.0) * qy.max(0.0) + qz.max(0.0) * qz.max(0.0)).sqrt();
297 let inner = qx.max(qy).max(qz).min(0.0);
298 outer + inner
299}
300
301pub fn sdf_cylinder(p: [f64; 3], center: [f64; 3], radius: f64, height: f64) -> f64 {
304 let dx = p[0] - center[0];
305 let dz = p[2] - center[2];
306 let radial = (dx * dx + dz * dz).sqrt() - radius;
307 let axial = (p[1] - center[1]).abs() - height * 0.5;
308 let outer = (radial.max(0.0) * radial.max(0.0) + axial.max(0.0) * axial.max(0.0)).sqrt();
309 let inner = radial.max(axial).min(0.0);
310 outer + inner
311}
312
313pub fn sdf_torus(p: [f64; 3], center: [f64; 3], r_major: f64, r_minor: f64) -> f64 {
316 let dx = p[0] - center[0];
317 let dy = p[1] - center[1];
318 let dz = p[2] - center[2];
319 let xz_dist = (dx * dx + dz * dz).sqrt();
321 let qx = xz_dist - r_major;
322 let qy = dy;
323 (qx * qx + qy * qy).sqrt() - r_minor
324}
325
326pub fn sdf_smooth_union(d1: f64, d2: f64, k: f64) -> f64 {
333 if k <= 0.0 {
334 return d1.min(d2);
335 }
336 let h = (0.5 + 0.5 * (d2 - d1) / k).clamp(0.0, 1.0);
337 d1 * (1.0 - h) + d2 * h - k * h * (1.0 - h)
338}
339
340pub fn sdf_gradient(sdf: &dyn Fn([f64; 3]) -> f64, p: [f64; 3]) -> [f64; 3] {
345 const EPS: f64 = 1e-4;
346 let gx = sdf([p[0] + EPS, p[1], p[2]]) - sdf([p[0] - EPS, p[1], p[2]]);
347 let gy = sdf([p[0], p[1] + EPS, p[2]]) - sdf([p[0], p[1] - EPS, p[2]]);
348 let gz = sdf([p[0], p[1], p[2] + EPS]) - sdf([p[0], p[1], p[2] - EPS]);
349 let len = (gx * gx + gy * gy + gz * gz).sqrt();
350 if len < 1e-15 {
351 [0.0, 1.0, 0.0]
352 } else {
353 [gx / len, gy / len, gz / len]
354 }
355}
356
357#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
368 fn test_sphere_surface() {
369 let d = sdf_sphere([1.0, 0.0, 0.0], [0.0; 3], 1.0);
370 assert!(d.abs() < 1e-10, "expected ~0, got {d}");
371 }
372
373 #[test]
374 fn test_sphere_outside() {
375 let d = sdf_sphere([2.0, 0.0, 0.0], [0.0; 3], 1.0);
376 assert!((d - 1.0).abs() < 1e-10);
377 }
378
379 #[test]
380 fn test_sphere_inside() {
381 let d = sdf_sphere([0.0; 3], [0.0; 3], 1.0);
382 assert!((d - (-1.0)).abs() < 1e-10);
383 }
384
385 #[test]
386 fn test_sphere_offset_center() {
387 let d = sdf_sphere([3.0, 0.0, 0.0], [2.0, 0.0, 0.0], 1.0);
388 assert!(d.abs() < 1e-10);
389 }
390
391 #[test]
392 fn test_sphere_diagonal() {
393 let expected = 3_f64.sqrt() - 1.0;
395 let d = sdf_sphere([1.0, 1.0, 1.0], [0.0; 3], 1.0);
396 assert!((d - expected).abs() < 1e-10);
397 }
398
399 #[test]
402 fn test_box_outside_x() {
403 let d = sdf_box([1.5, 0.0, 0.0], [0.0; 3], [0.5; 3]);
405 assert!((d - 1.0).abs() < 1e-10, "got {d}");
406 }
407
408 #[test]
409 fn test_box_inside() {
410 let d = sdf_box([0.0; 3], [0.0; 3], [1.0; 3]);
412 assert!(d < 0.0, "expected negative, got {d}");
413 }
414
415 #[test]
416 fn test_box_on_face() {
417 let d = sdf_box([1.0, 0.0, 0.0], [0.0; 3], [1.0; 3]);
419 assert!(d.abs() < 1e-10, "got {d}");
420 }
421
422 #[test]
423 fn test_box_corner() {
424 let d = sdf_box([1.0, 1.0, 1.0], [0.0; 3], [1.0; 3]);
426 assert!(d.abs() < 1e-10, "got {d}");
427 }
428
429 #[test]
430 fn test_box_asymmetric_extents() {
431 let d = sdf_box([2.0, 0.0, 0.0], [0.0; 3], [1.0, 2.0, 3.0]);
433 assert!((d - 1.0).abs() < 1e-10, "got {d}");
434 }
435
436 #[test]
439 fn test_smooth_union_zero_k_equals_min() {
440 let d1 = 1.0_f64;
441 let d2 = 2.0_f64;
442 assert!((sdf_smooth_union(d1, d2, 0.0) - d1.min(d2)).abs() < 1e-12);
443 }
444
445 #[test]
446 fn test_smooth_union_equal_inputs() {
447 let d = 1.0_f64;
449 let k = 0.5_f64;
450 let result = sdf_smooth_union(d, d, k);
451 let expected = d - k / 4.0;
452 assert!((result - expected).abs() < 1e-10, "got {result}");
453 }
454
455 #[test]
456 fn test_smooth_union_approaches_min_for_large_k() {
457 let d1 = 0.0_f64;
459 let d2 = 100.0_f64;
460 let result = sdf_smooth_union(d1, d2, 1.0);
461 assert!(result <= d2 + 1e-9);
466 }
467
468 #[test]
469 fn test_smooth_union_negative_k_is_min() {
470 let result = sdf_smooth_union(1.0, 2.0, -1.0);
471 assert!((result - 1.0_f64.min(2.0)).abs() < 1e-12);
472 }
473
474 #[test]
475 fn test_smooth_union_symmetric() {
476 let a = 0.3_f64;
478 let b = 0.7_f64;
479 let k = 0.4_f64;
480 let ab = sdf_smooth_union(a, b, k);
481 let ba = sdf_smooth_union(b, a, k);
482 assert!((ab - ba).abs() < 1e-12, "not symmetric: {ab} vs {ba}");
483 }
484
485 #[test]
488 fn test_gradient_sphere_outward() {
489 let sdf = |p: [f64; 3]| sdf_sphere(p, [0.0; 3], 1.0);
491 let g = sdf_gradient(&sdf, [1.0, 0.0, 0.0]);
492 assert!((g[0] - 1.0).abs() < 1e-3, "gx={}", g[0]);
493 assert!(g[1].abs() < 1e-3);
494 assert!(g[2].abs() < 1e-3);
495 }
496
497 #[test]
498 fn test_gradient_is_unit_length() {
499 let sdf = |p: [f64; 3]| sdf_sphere(p, [0.0; 3], 1.0);
500 let g = sdf_gradient(&sdf, [2.0, 0.0, 0.0]);
501 let len = (g[0] * g[0] + g[1] * g[1] + g[2] * g[2]).sqrt();
502 assert!((len - 1.0).abs() < 1e-6, "len={len}");
503 }
504
505 #[test]
506 fn test_gradient_box_face_normal() {
507 let sdf = |p: [f64; 3]| sdf_box(p, [0.0; 3], [1.0; 3]);
509 let g = sdf_gradient(&sdf, [2.0, 0.0, 0.0]);
510 assert!((g[0] - 1.0).abs() < 1e-3, "gx={}", g[0]);
511 }
512
513 #[test]
514 fn test_gradient_smooth_union() {
515 let sdf = |p: [f64; 3]| {
516 let d1 = sdf_sphere(p, [-1.0, 0.0, 0.0], 0.5);
517 let d2 = sdf_sphere(p, [1.0, 0.0, 0.0], 0.5);
518 sdf_smooth_union(d1, d2, 0.3)
519 };
520 let g = sdf_gradient(&sdf, [0.0, 0.0, 3.0]);
521 let len = (g[0] * g[0] + g[1] * g[1] + g[2] * g[2]).sqrt();
522 assert!((len - 1.0).abs() < 1e-4, "len={len}");
523 }
524
525 #[test]
528 fn test_primitive_sphere_evaluate() {
529 let prim = SdfPrimitive::Sphere {
530 center: [0.0; 3],
531 radius: 2.0,
532 };
533 assert!((prim.evaluate([2.0, 0.0, 0.0])).abs() < 1e-10);
534 }
535
536 #[test]
537 fn test_primitive_box_evaluate() {
538 let prim = SdfPrimitive::Box3d {
539 center: [0.0; 3],
540 half_extents: [1.0; 3],
541 };
542 let d = prim.evaluate([0.0; 3]);
543 assert!(d < 0.0);
544 }
545
546 #[test]
547 fn test_primitive_cylinder_evaluate() {
548 let prim = SdfPrimitive::Cylinder {
549 center: [0.0; 3],
550 radius: 1.0,
551 height: 2.0,
552 };
553 let d = prim.evaluate([1.0, 0.0, 0.0]);
555 assert!(d.abs() < 1e-10, "d={d}");
556 }
557
558 #[test]
559 fn test_primitive_torus_evaluate() {
560 let prim = SdfPrimitive::Torus {
561 center: [0.0; 3],
562 r_major: 2.0,
563 r_minor: 0.5,
564 };
565 let d = prim.evaluate([2.5, 0.0, 0.0]);
567 assert!(d.abs() < 1e-10, "d={d}");
568 }
569
570 #[test]
573 fn test_op_union() {
574 let op = SdfOp::Union;
575 assert!((op.apply(1.0, 2.0) - 1.0).abs() < 1e-12);
576 }
577
578 #[test]
579 fn test_op_intersection() {
580 let op = SdfOp::Intersection;
581 assert!((op.apply(1.0, 2.0) - 2.0).abs() < 1e-12);
582 }
583
584 #[test]
585 fn test_op_subtraction() {
586 let op = SdfOp::Subtraction;
588 assert!((op.apply(2.0, 3.0) - 2.0).abs() < 1e-12);
589 }
590
591 #[test]
592 fn test_op_smooth_union() {
593 let op = SdfOp::SmoothUnion { k: 0.3 };
594 let result = op.apply(1.0, 1.0);
595 assert!((result - (1.0 - 0.3 / 4.0)).abs() < 1e-10);
597 }
598
599 #[test]
600 fn test_op_offset_positive() {
601 let op = SdfOp::Offset { d: 0.5 };
602 assert!((op.apply(1.0, 0.0) - 1.5).abs() < 1e-12);
603 }
604
605 #[test]
606 fn test_op_offset_negative() {
607 let op = SdfOp::Offset { d: -0.5 };
608 assert!((op.apply(1.0, 0.0) - 0.5).abs() < 1e-12);
609 }
610
611 #[test]
614 fn test_node_leaf_evaluate() {
615 let node = SdfNode::leaf(SdfPrimitive::Sphere {
616 center: [0.0; 3],
617 radius: 1.0,
618 });
619 assert!((node.evaluate([1.0, 0.0, 0.0])).abs() < 1e-10);
620 }
621
622 #[test]
623 fn test_node_op_union() {
624 let a = SdfNode::leaf(SdfPrimitive::Sphere {
625 center: [-2.0, 0.0, 0.0],
626 radius: 1.0,
627 });
628 let b = SdfNode::leaf(SdfPrimitive::Sphere {
629 center: [2.0, 0.0, 0.0],
630 radius: 1.0,
631 });
632 let tree = SdfNode::op(SdfOp::Union, a, b);
633 let d = tree.evaluate([0.0; 3]);
635 assert!((d - 1.0).abs() < 1e-10, "d={d}");
636 }
637
638 #[test]
639 fn test_node_op_intersection() {
640 let a = SdfNode::leaf(SdfPrimitive::Sphere {
641 center: [0.0; 3],
642 radius: 2.0,
643 });
644 let b = SdfNode::leaf(SdfPrimitive::Sphere {
645 center: [0.0; 3],
646 radius: 3.0,
647 });
648 let tree = SdfNode::op(SdfOp::Intersection, a, b);
649 let d = tree.evaluate([0.0; 3]);
651 assert!((d - (-2.0)).abs() < 1e-10);
652 }
653
654 #[test]
655 fn test_node_op_subtraction() {
656 let a = SdfNode::leaf(SdfPrimitive::Sphere {
657 center: [0.0; 3],
658 radius: 2.0,
659 });
660 let b = SdfNode::leaf(SdfPrimitive::Sphere {
661 center: [0.0; 3],
662 radius: 1.0,
663 });
664 let tree = SdfNode::op(SdfOp::Subtraction, a, b);
665 let d = tree.evaluate([0.0; 3]);
667 assert!((d - 1.0).abs() < 1e-10, "d={d}");
668 }
669
670 #[test]
673 fn test_grid_new_size() {
674 let grid = SdfGrid::new([4, 4, 4], 0.1, [0.0; 3]);
675 assert_eq!(grid.len(), 64);
676 assert!(!grid.is_empty());
677 }
678
679 #[test]
680 fn test_grid_empty_dimensions() {
681 let grid = SdfGrid::new([0, 4, 4], 0.1, [0.0; 3]);
682 assert!(grid.is_empty());
683 }
684
685 #[test]
686 fn test_grid_compute_from_sdf_sphere() {
687 let mut grid = SdfGrid::new([4, 4, 4], 1.0, [-2.0, -2.0, -2.0]);
688 let sphere_sdf = |p: [f64; 3]| sdf_sphere(p, [0.0; 3], 1.0);
689 grid.compute_from_sdf(&sphere_sdf);
690 assert!(
692 grid.data.iter().all(|&v| v.is_finite()),
693 "some voxels unfilled"
694 );
695 }
696
697 #[test]
698 fn test_grid_index_roundtrip() {
699 let grid = SdfGrid::new([5, 6, 7], 0.5, [0.0; 3]);
700 for k in 0..7 {
701 for j in 0..6 {
702 for i in 0..5 {
703 let idx = grid.index(i, j, k);
704 let expected = i + 5 * (j + 6 * k);
706 assert_eq!(idx, expected);
707 }
708 }
709 }
710 }
711
712 #[test]
713 fn test_grid_voxel_center() {
714 let grid = SdfGrid::new([2, 2, 2], 1.0, [0.0; 3]);
715 let c = grid.voxel_center(0, 0, 0);
716 assert!((c[0] - 0.5).abs() < 1e-10);
717 assert!((c[1] - 0.5).abs() < 1e-10);
718 assert!((c[2] - 0.5).abs() < 1e-10);
719 }
720
721 #[test]
722 fn test_grid_center_of_last_voxel() {
723 let grid = SdfGrid::new([4, 4, 4], 1.0, [0.0; 3]);
724 let c = grid.voxel_center(3, 3, 3);
725 assert!((c[0] - 3.5).abs() < 1e-10);
726 }
727
728 #[test]
729 fn test_grid_inside_outside_counts() {
730 let mut grid = SdfGrid::new([10, 10, 10], 0.2, [-1.0, -1.0, -1.0]);
731 let sphere_sdf = |p: [f64; 3]| sdf_sphere(p, [0.0; 3], 0.5);
732 grid.compute_from_sdf(&sphere_sdf);
733 let inside = grid.data.iter().filter(|&&v| v < 0.0).count();
734 assert!(inside > 0, "no voxels inside the sphere");
735 let outside = grid.data.iter().filter(|&&v| v > 0.0).count();
736 assert!(outside > 0, "no voxels outside the sphere");
737 }
738
739 #[test]
742 fn test_gpu_sdf_compute_no_primitives() {
743 let compute = GpuSdfCompute::new();
744 let mut grid = SdfGrid::new([4, 4, 4], 1.0, [0.0; 3]);
745 grid.data.iter_mut().for_each(|v| *v = -999.0);
747 compute.dispatch_compute(&mut grid, &[]);
748 assert!(grid.data.iter().all(|&v| (v - (-999.0)).abs() < 1e-3));
750 }
751
752 #[test]
753 fn test_gpu_sdf_compute_sphere_union() {
754 let compute = GpuSdfCompute::new();
755 let mut grid = SdfGrid::new([8, 8, 8], 0.25, [-1.0, -1.0, -1.0]);
756 let primitives = vec![
757 SdfPrimitive::Sphere {
758 center: [-0.5, 0.0, 0.0],
759 radius: 0.3,
760 },
761 SdfPrimitive::Sphere {
762 center: [0.5, 0.0, 0.0],
763 radius: 0.3,
764 },
765 ];
766 compute.dispatch_compute(&mut grid, &primitives);
767 assert!(grid.data.iter().all(|&v| v.is_finite()));
769 let inside = grid.data.iter().filter(|&&v| v < 0.0).count();
771 assert!(inside > 0, "no voxels inside");
772 }
773
774 #[test]
775 fn test_gpu_sdf_compute_single_box() {
776 let compute = GpuSdfCompute::new();
777 let mut grid = SdfGrid::new([4, 4, 4], 1.0, [-2.0, -2.0, -2.0]);
778 let primitives = vec![SdfPrimitive::Box3d {
779 center: [0.0; 3],
780 half_extents: [0.5; 3],
781 }];
782 compute.dispatch_compute(&mut grid, &primitives);
783 assert!(grid.data.iter().all(|&v| v.is_finite()));
784 }
785
786 #[test]
787 fn test_gpu_sdf_compute_torus() {
788 let compute = GpuSdfCompute::new();
789 let mut grid = SdfGrid::new([6, 6, 6], 0.5, [-1.5, -1.5, -1.5]);
790 let primitives = vec![SdfPrimitive::Torus {
791 center: [0.0; 3],
792 r_major: 0.8,
793 r_minor: 0.2,
794 }];
795 compute.dispatch_compute(&mut grid, &primitives);
796 assert!(grid.data.iter().all(|&v| v.is_finite()));
797 }
798
799 #[test]
802 fn test_cylinder_inside() {
803 let d = sdf_cylinder([0.0, 0.0, 0.0], [0.0; 3], 1.0, 2.0);
804 assert!(d < 0.0, "expected inside, d={d}");
805 }
806
807 #[test]
808 fn test_cylinder_outside_radially() {
809 let d = sdf_cylinder([2.0, 0.0, 0.0], [0.0; 3], 1.0, 4.0);
810 assert!((d - 1.0).abs() < 1e-10, "d={d}");
811 }
812
813 #[test]
814 fn test_cylinder_on_curved_surface() {
815 let d = sdf_cylinder([1.0, 0.0, 0.0], [0.0; 3], 1.0, 4.0);
816 assert!(d.abs() < 1e-10, "d={d}");
817 }
818
819 #[test]
822 fn test_torus_centre_is_positive() {
823 let d = sdf_torus([0.0; 3], [0.0; 3], 2.0, 0.5);
825 assert!(d > 0.0, "d={d}");
826 }
827
828 #[test]
829 fn test_torus_on_tube_surface() {
830 let d = sdf_torus([1.5, 0.0, 0.0], [0.0; 3], 2.0, 0.5);
832 assert!(d.abs() < 1e-10, "d={d}");
833 }
834}