alice_sdf/types/mod.rs
1//! Core types for ALICE-SDF
2//!
3//! Defines the SdfNode tree structure and related types.
4//!
5//! Author: Moroya Sakamoto
6
7use glam::{Quat, Vec2, Vec3};
8use serde::{Deserialize, Serialize};
9use std::sync::Arc;
10
11mod constructors;
12mod containers;
13mod drop;
14mod modifiers;
15mod operations;
16mod transforms;
17
18pub use containers::{Aabb, Hit, Ray, SdfMetadata, SdfTree};
19
20/// Category of an SDF node variant
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum SdfCategory {
23 /// Leaf geometry nodes (spheres, boxes, cylinders, etc.)
24 Primitive,
25 /// Boolean and blending operations (union, intersection, smooth union, etc.)
26 Operation,
27 /// Spatial transform nodes (translate, rotate, scale, etc.)
28 Transform,
29 /// Surface and domain modifiers (twist, bend, shell, etc.)
30 Modifier,
31}
32
33impl SdfCategory {
34 /// Number of SdfNode variants in this category
35 ///
36 /// Checked against one node per variant by
37 /// `tests/test_node_backend_matrix.rs::category_counts_match_one_node_per_variant`.
38 pub const fn count(self) -> u32 {
39 match self {
40 Self::Primitive => 74,
41 Self::Operation => 25,
42 Self::Transform => 7,
43 Self::Modifier => 24,
44 }
45 }
46
47 /// Total number of all SdfNode variants
48 pub const fn total() -> u32 {
49 Self::Primitive.count()
50 + Self::Operation.count()
51 + Self::Transform.count()
52 + Self::Modifier.count()
53 }
54}
55
56/// serde default for `SdfNode::Taper::reach` (files written before 2.0).
57const fn taper_reach_unknown() -> [f32; 2] {
58 [f32::INFINITY; 2]
59}
60
61/// Signed Distance Function Node
62///
63/// Represents a node in the SDF tree. Each node can be:
64/// - A primitive shape (sphere, box, etc.)
65/// - An operation combining two shapes (union, intersection, etc.)
66/// - A transform applied to a child node
67/// - A modifier deforming a child node
68#[derive(Debug, Clone, Serialize, Deserialize)]
69// 4.0.0: variant の追加は破壊的変更なので、これ以降の primitive 追加を minor
70// で出せるように宣言する。downstream の `match` には wildcard arm が必要。
71#[non_exhaustive]
72pub enum SdfNode {
73 // === Primitives ===
74 /// Sphere with radius
75 Sphere {
76 /// Sphere radius
77 radius: f32,
78 },
79
80 /// Axis-aligned box with half-extents
81 Box3d {
82 /// Half-extents along each axis
83 half_extents: Vec3,
84 },
85
86 /// Cylinder along Y-axis with radius and half-height
87 Cylinder {
88 /// Cylinder radius
89 radius: f32,
90 /// Half the cylinder height
91 half_height: f32,
92 },
93
94 /// Torus in XZ plane with major and minor radius
95 Torus {
96 /// Distance from center to tube center
97 major_radius: f32,
98 /// Tube radius
99 minor_radius: f32,
100 },
101
102 /// Infinite plane with normal and distance from origin
103 Plane {
104 /// Plane normal direction
105 normal: Vec3,
106 /// Signed distance from origin along normal
107 distance: f32,
108 },
109
110 /// Capsule between two points with radius
111 Capsule {
112 /// First endpoint
113 point_a: Vec3,
114 /// Second endpoint
115 point_b: Vec3,
116 /// Capsule radius
117 radius: f32,
118 },
119
120 /// Cone along Y-axis with base radius and half-height
121 Cone {
122 /// Base radius
123 radius: f32,
124 /// Half the cone height
125 half_height: f32,
126 },
127
128 /// Ellipsoid with semi-axis radii
129 Ellipsoid {
130 /// Semi-axis radii (x, y, z)
131 radii: Vec3,
132 },
133
134 /// Rounded cone along Y-axis with bottom radius r1, top radius r2
135 RoundedCone {
136 /// Bottom sphere radius
137 r1: f32,
138 /// Top sphere radius
139 r2: f32,
140 /// Half the cone height
141 half_height: f32,
142 },
143
144 /// 4-sided pyramid with square base (side=1) centered at origin
145 Pyramid {
146 /// Half the pyramid height
147 half_height: f32,
148 },
149
150 /// Regular octahedron centered at origin
151 Octahedron {
152 /// Distance from center to vertex
153 size: f32,
154 },
155
156 /// Hexagonal prism centered at origin
157 HexPrism {
158 /// Hexagon circumradius
159 hex_radius: f32,
160 /// Half the prism height
161 half_height: f32,
162 },
163
164 /// Chain link shape centered at origin
165 Link {
166 /// Half the straight section length
167 half_length: f32,
168 /// Major radius (center to tube center)
169 r1: f32,
170 /// Minor radius (tube thickness)
171 r2: f32,
172 },
173
174 /// Triangle defined by three vertices (unsigned distance)
175 Triangle {
176 /// First vertex
177 point_a: Vec3,
178 /// Second vertex
179 point_b: Vec3,
180 /// Third vertex
181 point_c: Vec3,
182 },
183
184 /// Quadratic Bezier curve with radius (tube around curve)
185 Bezier {
186 /// Start point
187 point_a: Vec3,
188 /// Control point
189 point_b: Vec3,
190 /// End point
191 point_c: Vec3,
192 /// Tube radius
193 radius: f32,
194 },
195
196 /// Box with rounded edges
197 RoundedBox {
198 /// Half-extents (before rounding)
199 half_extents: Vec3,
200 /// Edge rounding radius
201 round_radius: f32,
202 },
203
204 /// Capped cone (frustum) along Y-axis
205 CappedCone {
206 /// Half the cone height
207 half_height: f32,
208 /// Bottom radius
209 r1: f32,
210 /// Top radius
211 r2: f32,
212 },
213
214 /// Partial torus (arc) in XZ plane
215 CappedTorus {
216 /// Distance from center to tube center
217 major_radius: f32,
218 /// Tube radius
219 minor_radius: f32,
220 /// Half opening angle (radians)
221 cap_angle: f32,
222 },
223
224 /// Cylinder with rounded edges along Y-axis
225 RoundedCylinder {
226 /// Cylinder radius
227 radius: f32,
228 /// Edge rounding radius
229 round_radius: f32,
230 /// Half the cylinder height
231 half_height: f32,
232 },
233
234 /// Equilateral triangular prism along Z-axis
235 TriangularPrism {
236 /// Half-width of the triangle cross-section
237 width: f32,
238 /// Half the prism depth along Z
239 half_depth: f32,
240 },
241
242 /// Sphere with a planar cut
243 CutSphere {
244 /// Sphere radius
245 radius: f32,
246 /// Y height of the cut plane
247 cut_height: f32,
248 },
249
250 /// Hollow sphere shell with a planar cut
251 CutHollowSphere {
252 /// Sphere radius
253 radius: f32,
254 /// Y height of the cut plane
255 cut_height: f32,
256 /// Shell thickness
257 thickness: f32,
258 },
259
260 /// Death Star: sphere with spherical indentation
261 DeathStar {
262 /// Main sphere radius
263 ra: f32,
264 /// Carving sphere radius
265 rb: f32,
266 /// Distance between sphere centers
267 d: f32,
268 },
269
270 /// Solid angle (cone sector)
271 SolidAngle {
272 /// Half-angle in radians
273 angle: f32,
274 /// Bounding radius
275 radius: f32,
276 },
277
278 /// 3D rhombus (diamond shape)
279 Rhombus {
280 /// Half-diagonal along X
281 la: f32,
282 /// Half-diagonal along Z
283 lb: f32,
284 /// Half-height along Y
285 half_height: f32,
286 /// Edge rounding
287 round_radius: f32,
288 },
289
290 /// Horseshoe (U-shape)
291 Horseshoe {
292 /// Opening half-angle (radians)
293 angle: f32,
294 /// Ring radius
295 radius: f32,
296 /// Straight extension half-length
297 half_length: f32,
298 /// Cross-section half-width
299 width: f32,
300 /// Cross-section half-thickness
301 thickness: f32,
302 },
303
304 /// 3D vesica (lens shape, revolved)
305 Vesica {
306 /// Arc radius
307 radius: f32,
308 /// Half distance between arc centers
309 half_dist: f32,
310 },
311
312 /// Infinite cylinder along Y-axis
313 InfiniteCylinder {
314 /// Cylinder radius
315 radius: f32,
316 },
317
318 /// Infinite cone along Y-axis
319 InfiniteCone {
320 /// Half-angle in radians
321 angle: f32,
322 },
323
324 /// Gyroid triply-periodic minimal surface
325 Gyroid {
326 /// Spatial frequency
327 scale: f32,
328 /// Shell half-thickness
329 thickness: f32,
330 },
331
332 /// Ball of a weighted norm — `w₁‖p‖₁ + w₂‖p‖₂ + w∞‖p‖∞ − radius`.
333 ///
334 /// The three weights choose the shape: `(0, 1, 0)` is a sphere,
335 /// `(0, 0, 1)` a cube, `(1, 0, 0)` an octahedron, and anything in
336 /// between a convex blend of those unit balls. They are non-negative and
337 /// not all zero — [`SdfNode::metric_ball`] takes a validated
338 /// `MetricWeights`, which is the only way to build one.
339 MetricBall {
340 /// Radius in the ball's own metric.
341 radius: f32,
342 /// Weight of `‖·‖₁` (octahedral).
343 w_l1: f32,
344 /// Weight of `‖·‖₂` (Euclidean).
345 w_l2: f32,
346 /// Weight of `‖·‖∞` (cubic).
347 w_linf: f32,
348 },
349
350 /// Two fields blended over a spherical skin — the inner field inside a
351 /// bubble, the outer one beyond it, smoothly joined across the skin.
352 ///
353 /// Inside `radius` the result is the inner field *exactly*, beyond
354 /// `radius + skin` the outer one *exactly* (the blend weight is exactly
355 /// 0 and 1 there, and the interpolation is endpoint-exact), so placing a
356 /// bubble never perturbs the world outside it.
357 ///
358 /// The blend of two unrelated fields is not a distance function: across
359 /// the skin the gradient picks up `|f_in − f_out| · |∇w|`, which no
360 /// static bound can know, so
361 /// [`eval_lipschitz`](crate::interval::eval_lipschitz) reports infinity
362 /// for it. Use [`measure_tension`](crate::measure::measure_tension) over
363 /// the skin to find out what it actually does.
364 MetricBlend {
365 /// Field inside the bubble.
366 inner: Arc<Self>,
367 /// Field outside it.
368 outer: Arc<Self>,
369 /// Bubble centre.
370 center: Vec3,
371 /// Radius at which the skin starts.
372 radius: f32,
373 /// Skin width; the blend spans `[radius, radius + skin]`.
374 skin: f32,
375 },
376
377 /// 3D heart shape (revolved contour)
378 Heart {
379 /// Overall size
380 size: f32,
381 },
382
383 /// Hollow cylinder (tube/pipe) along Y-axis
384 Tube {
385 /// Radius of the tube center-line
386 outer_radius: f32,
387 /// Half-wall thickness
388 thickness: f32,
389 /// Half the tube height
390 half_height: f32,
391 },
392
393 /// Cylinder with parabolic radial bulge along Y-axis
394 Barrel {
395 /// Base radius at caps
396 radius: f32,
397 /// Half the barrel height
398 half_height: f32,
399 /// Additional radius at the middle
400 bulge: f32,
401 },
402
403 /// Bipyramid (double-cone) with revolution symmetry
404 Diamond {
405 /// Equator radius
406 radius: f32,
407 /// Half the total height
408 half_height: f32,
409 },
410
411 /// Box with chamfered (beveled) edges
412 ChamferedCube {
413 /// Half-extents along each axis
414 half_extents: Vec3,
415 /// Chamfer amount
416 chamfer: f32,
417 },
418
419 /// Schwarz P triply-periodic minimal surface
420 SchwarzP {
421 /// Spatial frequency
422 scale: f32,
423 /// Shell half-thickness
424 thickness: f32,
425 },
426
427 /// Generalized ellipsoid (sphere-box morph)
428 Superellipsoid {
429 /// Semi-axis radii
430 half_extents: Vec3,
431 /// North-south roundness
432 e1: f32,
433 /// East-west roundness
434 e2: f32,
435 },
436
437 /// Rounded X shape extruded along Y-axis
438 RoundedX {
439 /// Arm length
440 width: f32,
441 /// Rounding radius
442 round_radius: f32,
443 /// Half the extrusion height
444 half_height: f32,
445 },
446
447 /// Pie (sector) shape extruded along Y-axis
448 Pie {
449 /// Half opening angle in radians
450 angle: f32,
451 /// Pie radius
452 radius: f32,
453 /// Half the extrusion height
454 half_height: f32,
455 },
456
457 /// Trapezoid prism in XY plane, extruded along Z
458 Trapezoid {
459 /// Half-width at bottom
460 r1: f32,
461 /// Half-width at top
462 r2: f32,
463 /// Half-height of the 2D trapezoid
464 trap_height: f32,
465 /// Half the extrusion depth along Z
466 half_depth: f32,
467 },
468
469 /// Parallelogram prism in XY plane, extruded along Z
470 Parallelogram {
471 /// Half-width
472 width: f32,
473 /// Half-height
474 para_height: f32,
475 /// Horizontal skew
476 skew: f32,
477 /// Half the extrusion depth along Z
478 half_depth: f32,
479 },
480
481 /// Tunnel (arch opening) in XY plane, extruded along Z
482 Tunnel {
483 /// Half-width of the tunnel
484 width: f32,
485 /// Height of the 2D tunnel opening
486 height_2d: f32,
487 /// Half the extrusion depth along Z
488 half_depth: f32,
489 },
490
491 /// Uneven capsule prism in XY plane, extruded along Z
492 UnevenCapsule {
493 /// Bottom circle radius
494 r1: f32,
495 /// Top circle radius
496 r2: f32,
497 /// Half-height between circle centers
498 cap_height: f32,
499 /// Half the extrusion depth along Z
500 half_depth: f32,
501 },
502
503 /// Egg shape (revolution body) around Y-axis
504 Egg {
505 /// Overall size / base radius
506 ra: f32,
507 /// Top deformation (controls pointiness)
508 rb: f32,
509 },
510
511 /// Arc shape (thick ring sector) in XZ plane, extruded along Y
512 ArcShape {
513 /// Half opening angle in radians
514 aperture: f32,
515 /// Arc center radius
516 radius: f32,
517 /// Ring half-thickness
518 thickness: f32,
519 /// Half the extrusion height along Y
520 half_height: f32,
521 },
522
523 /// Moon (crescent) shape in XZ plane, extruded along Y
524 Moon {
525 /// Distance between circle centers
526 d: f32,
527 /// Outer circle radius
528 ra: f32,
529 /// Inner (subtracted) circle radius
530 rb: f32,
531 /// Half the extrusion height along Y
532 half_height: f32,
533 },
534
535 /// Cross (plus) shape in XZ plane, extruded along Y
536 CrossShape {
537 /// Half-length of cross arms
538 length: f32,
539 /// Half-thickness of cross arms
540 thickness: f32,
541 /// Rounding radius
542 round_radius: f32,
543 /// Half the extrusion height along Y
544 half_height: f32,
545 },
546
547 /// Blobby cross (organic) in XZ plane, extruded along Y
548 BlobbyCross {
549 /// Overall size
550 size: f32,
551 /// Half the extrusion height along Y
552 half_height: f32,
553 },
554
555 /// Parabola segment in XY plane, extruded along Z
556 ParabolaSegment {
557 /// Half-width of the parabola base
558 width: f32,
559 /// Height of the parabola
560 para_height: f32,
561 /// Half the extrusion depth along Z
562 half_depth: f32,
563 },
564
565 /// Regular N-sided polygon prism in XZ plane, extruded along Y
566 RegularPolygon {
567 /// Circumscribed circle radius (center to vertex)
568 radius: f32,
569 /// Number of sides (as f32)
570 n_sides: f32,
571 /// Half the extrusion height along Y
572 half_height: f32,
573 },
574
575 /// Star polygon prism in XZ plane, extruded along Y
576 StarPolygon {
577 /// Outer vertex radius
578 radius: f32,
579 /// Number of star points (as f32)
580 n_points: f32,
581 /// Inner vertex radius (spike depth)
582 m: f32,
583 /// Half the extrusion height along Y
584 half_height: f32,
585 },
586
587 /// Staircase shape in XY plane, extruded along Z
588 Stairs {
589 /// Width of each step
590 step_width: f32,
591 /// Height of each step
592 step_height: f32,
593 /// Number of steps (as f32)
594 n_steps: f32,
595 /// Half the extrusion depth along Z
596 half_depth: f32,
597 },
598
599 /// Helix (spiral tube) along Y-axis
600 Helix {
601 /// Major radius (distance from Y-axis to helix center)
602 major_r: f32,
603 /// Minor radius (tube thickness)
604 minor_r: f32,
605 /// Vertical distance per full revolution
606 pitch: f32,
607 /// Half the height along Y
608 half_height: f32,
609 },
610
611 /// Regular tetrahedron centered at origin
612 Tetrahedron {
613 /// Distance from center to face
614 size: f32,
615 },
616
617 /// Regular dodecahedron centered at origin (GDF)
618 Dodecahedron {
619 /// Distance from center to face
620 radius: f32,
621 },
622
623 /// Regular icosahedron centered at origin (GDF)
624 Icosahedron {
625 /// Distance from center to face
626 radius: f32,
627 },
628
629 /// Truncated octahedron centered at origin (GDF)
630 TruncatedOctahedron {
631 /// Distance from center to face
632 radius: f32,
633 },
634
635 /// Truncated icosahedron (soccer ball) centered at origin (GDF)
636 TruncatedIcosahedron {
637 /// Distance from center to face
638 radius: f32,
639 },
640
641 /// Box frame (wireframe box, edges only)
642 BoxFrame {
643 /// Half-extents along each axis
644 half_extents: Vec3,
645 /// Edge thickness
646 edge: f32,
647 },
648
649 /// Diamond surface (Schwarz D TPMS)
650 DiamondSurface {
651 /// Spatial frequency
652 scale: f32,
653 /// Shell half-thickness
654 thickness: f32,
655 },
656
657 /// Neovius surface (TPMS)
658 Neovius {
659 /// Spatial frequency
660 scale: f32,
661 /// Shell half-thickness
662 thickness: f32,
663 },
664
665 /// Lidinoid surface (TPMS)
666 Lidinoid {
667 /// Spatial frequency
668 scale: f32,
669 /// Shell half-thickness
670 thickness: f32,
671 },
672
673 /// IWP surface (TPMS)
674 IWP {
675 /// Spatial frequency
676 scale: f32,
677 /// Shell half-thickness
678 thickness: f32,
679 },
680
681 /// FRD surface (TPMS)
682 FRD {
683 /// Spatial frequency
684 scale: f32,
685 /// Shell half-thickness
686 thickness: f32,
687 },
688
689 /// Fischer-Koch S surface (TPMS)
690 FischerKochS {
691 /// Spatial frequency
692 scale: f32,
693 /// Shell half-thickness
694 thickness: f32,
695 },
696
697 /// PMY surface (TPMS)
698 PMY {
699 /// Spatial frequency
700 scale: f32,
701 /// Shell half-thickness
702 thickness: f32,
703 },
704
705 // === 2D Primitives (extruded to 3D) ===
706 /// 2D Circle extruded along Z
707 Circle2D {
708 /// Circle radius
709 radius: f32,
710 /// Half-height of extrusion along Z
711 half_height: f32,
712 },
713 /// 2D Rectangle extruded along Z
714 Rect2D {
715 /// Half-width and half-height of the rectangle
716 half_extents: Vec2,
717 /// Half-depth of extrusion along Z
718 half_height: f32,
719 },
720 /// 2D Line Segment extruded along Z
721 Segment2D {
722 /// Start point
723 a: Vec2,
724 /// End point
725 b: Vec2,
726 /// Segment thickness (radius)
727 thickness: f32,
728 /// Half-depth of extrusion along Z
729 half_height: f32,
730 },
731 /// 2D Polygon (convex, up to 8 vertices) extruded along Z
732 Polygon2D {
733 /// Vertices (up to 8)
734 vertices: Vec<Vec2>,
735 /// Half-depth of extrusion along Z
736 half_height: f32,
737 },
738 /// 2D Rounded Rectangle extruded along Z
739 RoundedRect2D {
740 /// Half-extents of the rectangle
741 half_extents: Vec2,
742 /// Corner rounding radius
743 round_radius: f32,
744 /// Half-depth of extrusion along Z
745 half_height: f32,
746 },
747 /// 2D Annular (ring) shape extruded along Z
748 Annular2D {
749 /// Outer radius
750 outer_radius: f32,
751 /// Ring thickness
752 thickness: f32,
753 /// Half-depth of extrusion along Z
754 half_height: f32,
755 },
756
757 // === Operations ===
758 /// Union of two shapes (min distance)
759 Union {
760 /// First operand
761 a: Arc<Self>,
762 /// Second operand
763 b: Arc<Self>,
764 },
765
766 /// Intersection of two shapes (max distance)
767 Intersection {
768 /// First operand
769 a: Arc<Self>,
770 /// Second operand
771 b: Arc<Self>,
772 },
773
774 /// Subtraction: a minus b (max of a and -b)
775 Subtraction {
776 /// Shape to subtract from
777 a: Arc<Self>,
778 /// Shape to subtract
779 b: Arc<Self>,
780 },
781
782 /// Smooth union with blending factor k
783 SmoothUnion {
784 /// First operand
785 a: Arc<Self>,
786 /// Second operand
787 b: Arc<Self>,
788 /// Blending radius
789 k: f32,
790 },
791
792 /// Smooth intersection with blending factor k
793 SmoothIntersection {
794 /// First operand
795 a: Arc<Self>,
796 /// Second operand
797 b: Arc<Self>,
798 /// Blending radius
799 k: f32,
800 },
801
802 /// Smooth subtraction with blending factor k
803 SmoothSubtraction {
804 /// Shape to subtract from
805 a: Arc<Self>,
806 /// Shape to subtract
807 b: Arc<Self>,
808 /// Blending radius
809 k: f32,
810 },
811
812 /// Chamfer union: 45-degree beveled blend
813 ChamferUnion {
814 /// First operand
815 a: Arc<Self>,
816 /// Second operand
817 b: Arc<Self>,
818 /// Chamfer radius
819 r: f32,
820 },
821
822 /// Chamfer intersection: 45-degree beveled blend
823 ChamferIntersection {
824 /// First operand
825 a: Arc<Self>,
826 /// Second operand
827 b: Arc<Self>,
828 /// Chamfer radius
829 r: f32,
830 },
831
832 /// Chamfer subtraction: 45-degree beveled blend
833 ChamferSubtraction {
834 /// Shape to subtract from
835 a: Arc<Self>,
836 /// Shape to subtract
837 b: Arc<Self>,
838 /// Chamfer radius
839 r: f32,
840 },
841
842 /// Stairs union: stepped/terraced blend (Mercury hg_sdf)
843 StairsUnion {
844 /// First operand
845 a: Arc<Self>,
846 /// Second operand
847 b: Arc<Self>,
848 /// Blend radius
849 r: f32,
850 /// Number of steps (n-1 visible steps)
851 n: f32,
852 },
853
854 /// Stairs intersection: stepped/terraced blend
855 StairsIntersection {
856 /// First operand
857 a: Arc<Self>,
858 /// Second operand
859 b: Arc<Self>,
860 /// Blend radius
861 r: f32,
862 /// Number of steps
863 n: f32,
864 },
865
866 /// Stairs subtraction: stepped/terraced blend
867 StairsSubtraction {
868 /// Shape to subtract from
869 a: Arc<Self>,
870 /// Shape to subtract
871 b: Arc<Self>,
872 /// Blend radius
873 r: f32,
874 /// Number of steps
875 n: f32,
876 },
877
878 /// XOR (symmetric difference)
879 XOR {
880 /// Left operand
881 a: Arc<Self>,
882 /// Right operand
883 b: Arc<Self>,
884 },
885
886 /// Morph (linear interpolation between two shapes)
887 Morph {
888 /// Source shape
889 a: Arc<Self>,
890 /// Target shape
891 b: Arc<Self>,
892 /// Blend factor (0=a, 1=b)
893 t: f32,
894 },
895
896 /// Columns union: column-shaped blend
897 ColumnsUnion {
898 /// Left operand
899 a: Arc<Self>,
900 /// Right operand
901 b: Arc<Self>,
902 /// Column radius
903 r: f32,
904 /// Number of columns
905 n: f32,
906 },
907
908 /// Columns intersection: column-shaped blend
909 ColumnsIntersection {
910 /// Left operand
911 a: Arc<Self>,
912 /// Right operand
913 b: Arc<Self>,
914 /// Column radius
915 r: f32,
916 /// Number of columns
917 n: f32,
918 },
919
920 /// Columns subtraction: column-shaped blend
921 ColumnsSubtraction {
922 /// Left operand
923 a: Arc<Self>,
924 /// Right operand
925 b: Arc<Self>,
926 /// Column radius
927 r: f32,
928 /// Number of columns
929 n: f32,
930 },
931
932 /// Pipe: cylindrical surface at intersection
933 Pipe {
934 /// Left operand
935 a: Arc<Self>,
936 /// Right operand
937 b: Arc<Self>,
938 /// Pipe radius
939 r: f32,
940 },
941
942 /// Engrave: engrave shape b into shape a
943 Engrave {
944 /// Base shape (receives the engraving)
945 a: Arc<Self>,
946 /// Engraving shape
947 b: Arc<Self>,
948 /// Engrave depth
949 r: f32,
950 },
951
952 /// Groove: cut a groove of shape b into shape a
953 Groove {
954 /// Base shape (receives the groove)
955 a: Arc<Self>,
956 /// Groove profile shape
957 b: Arc<Self>,
958 /// Groove width
959 ra: f32,
960 /// Groove depth
961 rb: f32,
962 },
963
964 /// Tongue: add a tongue protrusion
965 Tongue {
966 /// Base shape
967 a: Arc<Self>,
968 /// Tongue profile shape
969 b: Arc<Self>,
970 /// Tongue width
971 ra: f32,
972 /// Tongue height
973 rb: f32,
974 },
975
976 /// Exponential smooth union (IQ): exp-weighted smooth min
977 ExpSmoothUnion {
978 /// Left operand
979 a: Arc<Self>,
980 /// Right operand
981 b: Arc<Self>,
982 /// Smoothness parameter
983 k: f32,
984 },
985 /// Exponential smooth intersection
986 ExpSmoothIntersection {
987 /// Left operand
988 a: Arc<Self>,
989 /// Right operand
990 b: Arc<Self>,
991 /// Smoothness parameter
992 k: f32,
993 },
994 /// Exponential smooth subtraction
995 ExpSmoothSubtraction {
996 /// Left operand
997 a: Arc<Self>,
998 /// Right operand
999 b: Arc<Self>,
1000 /// Smoothness parameter
1001 k: f32,
1002 },
1003
1004 // === Transforms ===
1005 /// Translation
1006 Translate {
1007 /// Child node
1008 child: Arc<Self>,
1009 /// Translation offset
1010 offset: Vec3,
1011 },
1012
1013 /// Rotation (quaternion)
1014 Rotate {
1015 /// Child node
1016 child: Arc<Self>,
1017 /// Rotation quaternion
1018 rotation: Quat,
1019 },
1020
1021 /// Uniform scale
1022 Scale {
1023 /// Child node
1024 child: Arc<Self>,
1025 /// Scale factor
1026 factor: f32,
1027 },
1028
1029 /// Non-uniform scale (stretches the shape)
1030 ScaleNonUniform {
1031 /// Child node
1032 child: Arc<Self>,
1033 /// Scale factors per axis
1034 factors: Vec3,
1035 },
1036
1037 /// Projective (perspective) transform with Lipschitz correction
1038 ProjectiveTransform {
1039 /// Child node
1040 child: Arc<Self>,
1041 /// Inverse projection matrix (column-major)
1042 inv_matrix: [f32; 16],
1043 /// Lipschitz bound for distance correction
1044 lipschitz_bound: f32,
1045 },
1046
1047 /// Free-Form Deformation via control point lattice
1048 LatticeDeform {
1049 /// Child node
1050 child: Arc<Self>,
1051 /// Control points array
1052 control_points: Vec<Vec3>,
1053 /// Number of control points along X
1054 nx: u32,
1055 /// Number of control points along Y
1056 ny: u32,
1057 /// Number of control points along Z
1058 nz: u32,
1059 /// Lattice bounding box minimum
1060 bbox_min: Vec3,
1061 /// Lattice bounding box maximum
1062 bbox_max: Vec3,
1063 },
1064
1065 /// SDF Skinning — bone-weight based spatial blending
1066 SdfSkinning {
1067 /// Child node
1068 child: Arc<Self>,
1069 /// Bone transforms with weights
1070 bones: Vec<crate::transforms::skinning::BoneTransform>,
1071 },
1072
1073 // === Modifiers ===
1074 /// Twist around Y-axis (radians per unit height)
1075 Twist {
1076 /// Child node
1077 child: Arc<Self>,
1078 /// Twist strength (radians per unit)
1079 strength: f32,
1080 },
1081
1082 /// Bend around Y-axis
1083 Bend {
1084 /// Child node
1085 child: Arc<Self>,
1086 /// Bend curvature
1087 curvature: f32,
1088 },
1089
1090 /// Infinite repetition with spacing
1091 RepeatInfinite {
1092 /// Child node
1093 child: Arc<Self>,
1094 /// Spacing between repetitions
1095 spacing: Vec3,
1096 },
1097
1098 /// Finite repetition with count and spacing
1099 RepeatFinite {
1100 /// Child node
1101 child: Arc<Self>,
1102 /// Repeat count per axis
1103 count: [u32; 3],
1104 /// Spacing between repetitions
1105 spacing: Vec3,
1106 },
1107
1108 /// Perlin noise displacement
1109 Noise {
1110 /// Child node
1111 child: Arc<Self>,
1112 /// Noise amplitude
1113 amplitude: f32,
1114 /// Noise frequency
1115 frequency: f32,
1116 /// Random seed
1117 seed: u32,
1118 },
1119
1120 /// Round edges by subtracting radius
1121 Round {
1122 /// Child node
1123 child: Arc<Self>,
1124 /// Rounding radius
1125 radius: f32,
1126 },
1127
1128 /// Onion: creates a shell with thickness
1129 Onion {
1130 /// Child node
1131 child: Arc<Self>,
1132 /// Shell thickness
1133 thickness: f32,
1134 },
1135
1136 /// Elongate along an axis
1137 Elongate {
1138 /// Child node
1139 child: Arc<Self>,
1140 /// Elongation amount per axis
1141 amount: Vec3,
1142 },
1143
1144 /// Mirror along specified axes (takes absolute value of coordinates)
1145 Mirror {
1146 /// Child node
1147 child: Arc<Self>,
1148 /// Mirror axes (nonzero = mirrored)
1149 axes: Vec3,
1150 },
1151
1152 /// Revolution around Y-axis (creates rotational symmetry)
1153 Revolution {
1154 /// Child node
1155 child: Arc<Self>,
1156 /// Radial offset
1157 offset: f32,
1158 },
1159
1160 /// Extrude along Z-axis (creates 3D from XY cross-section)
1161 Extrude {
1162 /// Child node
1163 child: Arc<Self>,
1164 /// Half the extrusion height
1165 half_height: f32,
1166 },
1167
1168 /// Sweep along a quadratic Bezier curve in the XZ plane.
1169 /// The child SDF is evaluated at (perpendicular_distance, y, 0).
1170 /// Creates tubes, channels, or any cross-section shape along a curved path.
1171 SweepBezier {
1172 /// Child node (2D cross-section, evaluated in XY)
1173 child: Arc<Self>,
1174 /// Bezier start point (XZ plane)
1175 p0: Vec2,
1176 /// Bezier control point (XZ plane)
1177 p1: Vec2,
1178 /// Bezier end point (XZ plane)
1179 p2: Vec2,
1180 },
1181
1182 /// Taper: the child is evaluated at `(x, y, z) / (1 − y·factor)` (a
1183 /// perspective map with centre `(0, 1/factor, 0)`), and its distance is
1184 /// turned into a parent-space bound by `real::taper_bound`.
1185 Taper {
1186 /// Child node
1187 child: Arc<Self>,
1188 /// Taper factor
1189 factor: f32,
1190 /// Reach of the child in its own space, `[r_xz, r_y]` (`|q_xz| ≤ r_xz`,
1191 /// `|q_y| ≤ r_y`), computed by [`SdfNode::taper`] from the child's
1192 /// AABB. It bounds the parent-space shape by a cone ∩ slab so that the
1193 /// singular plane `y = 1/factor` is not reported as a surface.
1194 /// `[INFINITY; 2]` (unbounded child, or a file written before 2.0)
1195 /// keeps only the Jacobian bound. Must be recomputed
1196 /// (`SdfNode::taper`) if the child changes.
1197 #[serde(default = "taper_reach_unknown")]
1198 reach: [f32; 2],
1199 },
1200
1201 /// Sin-based displacement (post-processing modifier)
1202 Displacement {
1203 /// Child node
1204 child: Arc<Self>,
1205 /// Displacement strength
1206 strength: f32,
1207 },
1208
1209 /// Sin-based displacement with custom amplitude and per-axis frequency
1210 /// (= Vec3 frequency for anisotropic patterns、 e.g. elongated diamond scales)
1211 SineDisplacement {
1212 /// Child node
1213 child: Arc<Self>,
1214 /// Displacement amplitude (= strength magnitude)
1215 amplitude: f32,
1216 /// Sin wave frequency per axis (= use `Vec3::splat(f)` for isotropic)
1217 frequency: glam::Vec3,
1218 },
1219
1220 /// Polar repetition around Y-axis
1221 PolarRepeat {
1222 /// Child node
1223 child: Arc<Self>,
1224 /// Number of copies around Y-axis
1225 count: u32,
1226 },
1227
1228 /// Octant mirror: maps point to first octant with x >= y >= z (48-fold symmetry)
1229 OctantMirror {
1230 /// Child node
1231 child: Arc<Self>,
1232 },
1233
1234 /// Shear deformation (modifies evaluation point)
1235 Shear {
1236 /// Child node
1237 child: Arc<Self>,
1238 /// Shear factors: (xy, xz, yz)
1239 shear: Vec3,
1240 },
1241
1242 /// Animated modifier: applies time-based transformation
1243 Animated {
1244 /// Child node
1245 child: Arc<Self>,
1246 /// Animation speed multiplier
1247 speed: f32,
1248 /// Animation amplitude
1249 amplitude: f32,
1250 },
1251
1252 /// Assign a material ID to a subtree (transparent for distance evaluation)
1253 WithMaterial {
1254 /// Child node
1255 child: Arc<Self>,
1256 /// Material ID (indexes into MaterialLibrary)
1257 material_id: u32,
1258 },
1259
1260 /// Icosahedral symmetry (120-fold) — maps to fundamental domain
1261 IcosahedralSymmetry {
1262 /// Child node
1263 child: Arc<Self>,
1264 },
1265
1266 /// Iterated Function System — fractal self-similar folding
1267 IFS {
1268 /// Child node
1269 child: Arc<Self>,
1270 /// Affine transforms (column-major Mat4)
1271 transforms: Vec<[f32; 16]>,
1272 /// Number of iterations
1273 iterations: u32,
1274 },
1275
1276 /// Heightmap displacement — image-based surface perturbation
1277 HeightmapDisplacement {
1278 /// Child node
1279 child: Arc<Self>,
1280 /// Heightmap data (row-major, grayscale)
1281 heightmap: Vec<f32>,
1282 /// Heightmap width
1283 width: u32,
1284 /// Heightmap height
1285 height: u32,
1286 /// Displacement amplitude
1287 amplitude: f32,
1288 /// UV scale
1289 scale: f32,
1290 },
1291
1292 /// Surface roughness — FBM micro-detail noise
1293 SurfaceRoughness {
1294 /// Child node
1295 child: Arc<Self>,
1296 /// Noise frequency
1297 frequency: f32,
1298 /// Noise amplitude
1299 amplitude: f32,
1300 /// Number of octaves
1301 octaves: u32,
1302 },
1303
1304 /// Biome terrain — procedural terrain with FBM + Voronoi erosion
1305 /// Uses radial biome blending (snow, desert, rock, grassland)
1306 Terrain {
1307 /// Overall terrain scale (higher = larger terrain features)
1308 scale: f32,
1309 /// Vertical amplitude multiplier
1310 amplitude: f32,
1311 },
1312}
1313
1314impl SdfNode {
1315 /// Returns the category of this node variant
1316 pub const fn category(&self) -> SdfCategory {
1317 match self {
1318 // === Primitives ===
1319 Self::Sphere { .. }
1320 | Self::Box3d { .. }
1321 | Self::Cylinder { .. }
1322 | Self::Torus { .. }
1323 | Self::Plane { .. }
1324 | Self::Capsule { .. }
1325 | Self::Cone { .. }
1326 | Self::Ellipsoid { .. }
1327 | Self::RoundedCone { .. }
1328 | Self::Pyramid { .. }
1329 | Self::Octahedron { .. }
1330 | Self::HexPrism { .. }
1331 | Self::Link { .. }
1332 | Self::Triangle { .. }
1333 | Self::Bezier { .. }
1334 | Self::RoundedBox { .. }
1335 | Self::CappedCone { .. }
1336 | Self::CappedTorus { .. }
1337 | Self::RoundedCylinder { .. }
1338 | Self::TriangularPrism { .. }
1339 | Self::CutSphere { .. }
1340 | Self::CutHollowSphere { .. }
1341 | Self::DeathStar { .. }
1342 | Self::SolidAngle { .. }
1343 | Self::Rhombus { .. }
1344 | Self::Horseshoe { .. }
1345 | Self::Vesica { .. }
1346 | Self::InfiniteCylinder { .. }
1347 | Self::InfiniteCone { .. }
1348 | Self::Gyroid { .. }
1349 | Self::MetricBall { .. }
1350 | Self::Heart { .. }
1351 | Self::Tube { .. }
1352 | Self::Barrel { .. }
1353 | Self::Diamond { .. }
1354 | Self::ChamferedCube { .. }
1355 | Self::SchwarzP { .. }
1356 | Self::Superellipsoid { .. }
1357 | Self::RoundedX { .. }
1358 | Self::Pie { .. }
1359 | Self::Trapezoid { .. }
1360 | Self::Parallelogram { .. }
1361 | Self::Tunnel { .. }
1362 | Self::UnevenCapsule { .. }
1363 | Self::Egg { .. }
1364 | Self::ArcShape { .. }
1365 | Self::Moon { .. }
1366 | Self::CrossShape { .. }
1367 | Self::BlobbyCross { .. }
1368 | Self::ParabolaSegment { .. }
1369 | Self::RegularPolygon { .. }
1370 | Self::StarPolygon { .. }
1371 | Self::Stairs { .. }
1372 | Self::Helix { .. }
1373 | Self::Tetrahedron { .. }
1374 | Self::Dodecahedron { .. }
1375 | Self::Icosahedron { .. }
1376 | Self::TruncatedOctahedron { .. }
1377 | Self::TruncatedIcosahedron { .. }
1378 | Self::BoxFrame { .. }
1379 | Self::DiamondSurface { .. }
1380 | Self::Neovius { .. }
1381 | Self::Lidinoid { .. }
1382 | Self::IWP { .. }
1383 | Self::FRD { .. }
1384 | Self::FischerKochS { .. }
1385 | Self::PMY { .. }
1386 | Self::Circle2D { .. }
1387 | Self::Rect2D { .. }
1388 | Self::Segment2D { .. }
1389 | Self::Polygon2D { .. }
1390 | Self::RoundedRect2D { .. }
1391 | Self::Annular2D { .. }
1392 | Self::Terrain { .. } => SdfCategory::Primitive,
1393
1394 // === Operations ===
1395 Self::Union { .. }
1396 | Self::MetricBlend { .. }
1397 | Self::Intersection { .. }
1398 | Self::Subtraction { .. }
1399 | Self::SmoothUnion { .. }
1400 | Self::SmoothIntersection { .. }
1401 | Self::SmoothSubtraction { .. }
1402 | Self::ChamferUnion { .. }
1403 | Self::ChamferIntersection { .. }
1404 | Self::ChamferSubtraction { .. }
1405 | Self::StairsUnion { .. }
1406 | Self::StairsIntersection { .. }
1407 | Self::StairsSubtraction { .. }
1408 | Self::XOR { .. }
1409 | Self::Morph { .. }
1410 | Self::ColumnsUnion { .. }
1411 | Self::ColumnsIntersection { .. }
1412 | Self::ColumnsSubtraction { .. }
1413 | Self::Pipe { .. }
1414 | Self::Engrave { .. }
1415 | Self::Groove { .. }
1416 | Self::Tongue { .. }
1417 | Self::ExpSmoothUnion { .. }
1418 | Self::ExpSmoothIntersection { .. }
1419 | Self::ExpSmoothSubtraction { .. } => SdfCategory::Operation,
1420
1421 // === Transforms ===
1422 Self::Translate { .. }
1423 | Self::Rotate { .. }
1424 | Self::Scale { .. }
1425 | Self::ScaleNonUniform { .. }
1426 | Self::ProjectiveTransform { .. }
1427 | Self::LatticeDeform { .. }
1428 | Self::SdfSkinning { .. } => SdfCategory::Transform,
1429
1430 // === Modifiers ===
1431 Self::Twist { .. }
1432 | Self::Bend { .. }
1433 | Self::RepeatInfinite { .. }
1434 | Self::RepeatFinite { .. }
1435 | Self::Noise { .. }
1436 | Self::Round { .. }
1437 | Self::Onion { .. }
1438 | Self::Elongate { .. }
1439 | Self::Mirror { .. }
1440 | Self::Revolution { .. }
1441 | Self::Extrude { .. }
1442 | Self::SweepBezier { .. }
1443 | Self::Taper { .. }
1444 | Self::Displacement { .. }
1445 | Self::SineDisplacement { .. }
1446 | Self::PolarRepeat { .. }
1447 | Self::OctantMirror { .. }
1448 | Self::Shear { .. }
1449 | Self::Animated { .. }
1450 | Self::WithMaterial { .. }
1451 | Self::IcosahedralSymmetry { .. }
1452 | Self::IFS { .. }
1453 | Self::HeightmapDisplacement { .. }
1454 | Self::SurfaceRoughness { .. } => SdfCategory::Modifier,
1455 }
1456 }
1457
1458 /// Count total nodes in the tree
1459 pub fn node_count(&self) -> u32 {
1460 match self {
1461 // Primitives: 1 node
1462 Self::Sphere { .. }
1463 | Self::Box3d { .. }
1464 | Self::Cylinder { .. }
1465 | Self::Torus { .. }
1466 | Self::Plane { .. }
1467 | Self::Capsule { .. }
1468 | Self::Cone { .. }
1469 | Self::Ellipsoid { .. }
1470 | Self::RoundedCone { .. }
1471 | Self::Pyramid { .. }
1472 | Self::Octahedron { .. }
1473 | Self::HexPrism { .. }
1474 | Self::Link { .. }
1475 | Self::Triangle { .. }
1476 | Self::Bezier { .. }
1477 | Self::RoundedBox { .. }
1478 | Self::CappedCone { .. }
1479 | Self::CappedTorus { .. }
1480 | Self::RoundedCylinder { .. }
1481 | Self::TriangularPrism { .. }
1482 | Self::CutSphere { .. }
1483 | Self::CutHollowSphere { .. }
1484 | Self::DeathStar { .. }
1485 | Self::SolidAngle { .. }
1486 | Self::Rhombus { .. }
1487 | Self::Horseshoe { .. }
1488 | Self::Vesica { .. }
1489 | Self::InfiniteCylinder { .. }
1490 | Self::InfiniteCone { .. }
1491 | Self::Gyroid { .. }
1492 | Self::Heart { .. }
1493 | Self::Tube { .. }
1494 | Self::Barrel { .. }
1495 | Self::Diamond { .. }
1496 | Self::ChamferedCube { .. }
1497 | Self::SchwarzP { .. }
1498 | Self::Superellipsoid { .. }
1499 | Self::RoundedX { .. }
1500 | Self::Pie { .. }
1501 | Self::Trapezoid { .. }
1502 | Self::Parallelogram { .. }
1503 | Self::Tunnel { .. }
1504 | Self::UnevenCapsule { .. }
1505 | Self::Egg { .. }
1506 | Self::ArcShape { .. }
1507 | Self::Moon { .. }
1508 | Self::CrossShape { .. }
1509 | Self::BlobbyCross { .. }
1510 | Self::ParabolaSegment { .. }
1511 | Self::RegularPolygon { .. }
1512 | Self::StarPolygon { .. }
1513 | Self::Stairs { .. }
1514 | Self::Helix { .. }
1515 | Self::Tetrahedron { .. }
1516 | Self::Dodecahedron { .. }
1517 | Self::Icosahedron { .. }
1518 | Self::TruncatedOctahedron { .. }
1519 | Self::TruncatedIcosahedron { .. }
1520 | Self::BoxFrame { .. }
1521 | Self::DiamondSurface { .. }
1522 | Self::Neovius { .. }
1523 | Self::Lidinoid { .. }
1524 | Self::IWP { .. }
1525 | Self::FRD { .. }
1526 | Self::FischerKochS { .. }
1527 | Self::PMY { .. }
1528 | Self::Terrain { .. } => 1,
1529
1530 // Operations: 1 + children
1531 Self::Union { a, b }
1532 | Self::Intersection { a, b }
1533 | Self::Subtraction { a, b }
1534 | Self::SmoothUnion { a, b, .. }
1535 | Self::SmoothIntersection { a, b, .. }
1536 | Self::SmoothSubtraction { a, b, .. }
1537 | Self::ChamferUnion { a, b, .. }
1538 | Self::ChamferIntersection { a, b, .. }
1539 | Self::ChamferSubtraction { a, b, .. }
1540 | Self::StairsUnion { a, b, .. }
1541 | Self::StairsIntersection { a, b, .. }
1542 | Self::StairsSubtraction { a, b, .. }
1543 | Self::XOR { a, b }
1544 | Self::Morph { a, b, .. }
1545 | Self::ColumnsUnion { a, b, .. }
1546 | Self::ColumnsIntersection { a, b, .. }
1547 | Self::ColumnsSubtraction { a, b, .. }
1548 | Self::ExpSmoothUnion { a, b, .. }
1549 | Self::ExpSmoothIntersection { a, b, .. }
1550 | Self::ExpSmoothSubtraction { a, b, .. }
1551 | Self::Pipe { a, b, .. }
1552 | Self::Engrave { a, b, .. }
1553 | Self::Groove { a, b, .. }
1554 | Self::Tongue { a, b, .. } => 1 + a.node_count() + b.node_count(),
1555
1556 // Transforms and modifiers: 1 + child
1557 Self::Translate { child, .. }
1558 | Self::Rotate { child, .. }
1559 | Self::Scale { child, .. }
1560 | Self::ScaleNonUniform { child, .. }
1561 | Self::ProjectiveTransform { child, .. }
1562 | Self::LatticeDeform { child, .. }
1563 | Self::SdfSkinning { child, .. }
1564 | Self::Twist { child, .. }
1565 | Self::Bend { child, .. }
1566 | Self::RepeatInfinite { child, .. }
1567 | Self::RepeatFinite { child, .. }
1568 | Self::Noise { child, .. }
1569 | Self::Round { child, .. }
1570 | Self::Onion { child, .. }
1571 | Self::Elongate { child, .. }
1572 | Self::Mirror { child, .. }
1573 | Self::Revolution { child, .. }
1574 | Self::Extrude { child, .. }
1575 | Self::SweepBezier { child, .. }
1576 | Self::Taper { child, .. }
1577 | Self::Displacement { child, .. }
1578 | Self::SineDisplacement { child, .. }
1579 | Self::PolarRepeat { child, .. }
1580 | Self::OctantMirror { child, .. }
1581 | Self::Shear { child, .. }
1582 | Self::Animated { child, .. }
1583 | Self::WithMaterial { child, .. }
1584 | Self::IcosahedralSymmetry { child, .. }
1585 | Self::IFS { child, .. }
1586 | Self::HeightmapDisplacement { child, .. }
1587 | Self::SurfaceRoughness { child, .. } => 1 + child.node_count(),
1588
1589 Self::MetricBlend { inner, outer, .. } => 1 + inner.node_count() + outer.node_count(),
1590
1591 #[allow(unreachable_patterns)]
1592 _ => 1,
1593 }
1594 }
1595}
1596
1597#[cfg(test)]
1598mod tests {
1599 use super::*;
1600
1601 #[test]
1602 fn test_sphere_creation() {
1603 let sphere = SdfNode::sphere(1.0);
1604 assert_eq!(sphere.node_count(), 1);
1605 }
1606
1607 #[test]
1608 fn test_union() {
1609 let a = SdfNode::sphere(1.0);
1610 let b = SdfNode::box3d(2.0, 2.0, 2.0);
1611 let union = a.union(b);
1612 assert_eq!(union.node_count(), 3);
1613 }
1614
1615 #[test]
1616 fn test_transform_chain() {
1617 let shape = SdfNode::sphere(1.0)
1618 .translate(1.0, 0.0, 0.0)
1619 .rotate_euler(0.0, std::f32::consts::PI, 0.0)
1620 .scale(2.0);
1621 assert_eq!(shape.node_count(), 4);
1622 }
1623
1624 #[test]
1625 fn test_category_primitive() {
1626 assert_eq!(SdfNode::sphere(1.0).category(), SdfCategory::Primitive);
1627 assert_eq!(
1628 SdfNode::box3d(1.0, 1.0, 1.0).category(),
1629 SdfCategory::Primitive
1630 );
1631 assert_eq!(
1632 SdfNode::cylinder(0.5, 1.0).category(),
1633 SdfCategory::Primitive
1634 );
1635 assert_eq!(SdfNode::torus(1.0, 0.3).category(), SdfCategory::Primitive);
1636 }
1637
1638 #[test]
1639 fn test_category_operation() {
1640 let u = SdfNode::sphere(1.0).union(SdfNode::box3d(1.0, 1.0, 1.0));
1641 assert_eq!(u.category(), SdfCategory::Operation);
1642 let s = SdfNode::sphere(1.0).smooth_union(SdfNode::box3d(1.0, 1.0, 1.0), 0.2);
1643 assert_eq!(s.category(), SdfCategory::Operation);
1644 }
1645
1646 #[test]
1647 fn test_category_transform() {
1648 let t = SdfNode::sphere(1.0).translate(1.0, 0.0, 0.0);
1649 assert_eq!(t.category(), SdfCategory::Transform);
1650 let r = SdfNode::sphere(1.0).rotate_euler(0.0, 1.0, 0.0);
1651 assert_eq!(r.category(), SdfCategory::Transform);
1652 let s = SdfNode::sphere(1.0).scale(2.0);
1653 assert_eq!(s.category(), SdfCategory::Transform);
1654 }
1655
1656 #[test]
1657 fn test_category_modifier() {
1658 let t = SdfNode::sphere(1.0).twist(0.5);
1659 assert_eq!(t.category(), SdfCategory::Modifier);
1660 let b = SdfNode::sphere(1.0).bend(0.3);
1661 assert_eq!(b.category(), SdfCategory::Modifier);
1662 }
1663
1664 #[test]
1665 fn test_category_total() {
1666 assert_eq!(SdfCategory::total(), 130);
1667 }
1668
1669 #[test]
1670 fn test_node_count_leaf() {
1671 assert_eq!(SdfNode::sphere(1.0).node_count(), 1);
1672 assert_eq!(SdfNode::box3d(1.0, 1.0, 1.0).node_count(), 1);
1673 assert_eq!(SdfNode::torus(1.0, 0.3).node_count(), 1);
1674 }
1675
1676 #[test]
1677 fn test_node_count_deep_tree() {
1678 let tree = SdfNode::sphere(1.0)
1679 .union(SdfNode::box3d(1.0, 1.0, 1.0))
1680 .translate(1.0, 0.0, 0.0)
1681 .scale(2.0)
1682 .twist(0.5);
1683 assert_eq!(tree.node_count(), 6); // sphere + box + union + translate + scale + twist
1684 }
1685
1686 #[test]
1687 fn test_clone_equivalence() {
1688 let a = SdfNode::sphere(1.0).smooth_union(SdfNode::box3d(1.0, 1.0, 1.0), 0.2);
1689 let b = a.clone();
1690 assert_eq!(format!("{:?}", a), format!("{:?}", b));
1691 }
1692
1693 #[test]
1694 fn test_sdf_tree_creation() {
1695 let node = SdfNode::sphere(1.0).union(SdfNode::box3d(1.0, 1.0, 1.0));
1696 let tree = SdfTree::new(node);
1697 assert!(tree.node_count() >= 3);
1698 }
1699
1700 #[test]
1701 fn test_node_count_operations() {
1702 let xor = SdfNode::sphere(1.0).xor(SdfNode::box3d(1.0, 1.0, 1.0));
1703 assert_eq!(xor.node_count(), 3);
1704 let morph = SdfNode::sphere(1.0).morph(SdfNode::box3d(1.0, 1.0, 1.0), 0.5);
1705 assert_eq!(morph.node_count(), 3);
1706 }
1707}