Skip to main content

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}