Skip to main content

alice_sdf/types/
constructors.rs

1//! Primitive and 2D constructors for SdfNode
2//!
3//! Author: Moroya Sakamoto
4
5use glam::{Vec2, Vec3};
6
7use std::sync::Arc;
8
9use super::SdfNode;
10
11impl SdfNode {
12    // === Primitive constructors ===
13
14    /// Create a sphere with the given radius
15    #[must_use]
16    #[inline]
17    pub const fn sphere(radius: f32) -> Self {
18        Self::Sphere { radius }
19    }
20
21    /// Create an axis-aligned box with the given **full** dimensions
22    /// (`width` × `height` × `depth`; the node stores half of each).
23    ///
24    /// # Naming hazard
25    ///
26    /// This is the one box constructor that takes full extents. Both
27    /// [`SdfNode::rounded_box`] and the LOL DSL `box3d(hx, hy, hz)` take
28    /// **half-extents** — `box3d(20, 20, 3)` in LOL is a 40 × 40 × 6 mm box,
29    /// while `SdfNode::box3d(20.0, 20.0, 3.0)` in Rust is 20 × 20 × 3 mm.
30    /// Use [`SdfNode::box3d_half_extents`] when you hold half-extents (for
31    /// example when porting a LOL expression to Rust) so the intent is
32    /// visible at the call site. Neither spelling can change without
33    /// breaking published callers, so the difference is documented rather
34    /// than unified.
35    #[must_use]
36    #[inline]
37    pub fn box3d(width: f32, height: f32, depth: f32) -> Self {
38        Self::Box3d {
39            half_extents: Vec3::new(width * 0.5, height * 0.5, depth * 0.5),
40        }
41    }
42
43    /// Create an axis-aligned box from **half-extents** — the same
44    /// convention as [`SdfNode::rounded_box`] and the LOL DSL `box3d`.
45    ///
46    /// ```
47    /// use alice_sdf::types::SdfNode;
48    /// // LOL `box3d(20, 10, 1.5)` (40 × 20 × 3 mm) ported to Rust:
49    /// let a = SdfNode::box3d_half_extents(20.0, 10.0, 1.5);
50    /// let b = SdfNode::box3d(40.0, 20.0, 3.0);
51    /// let p = glam::Vec3::new(19.0, 9.0, 1.0);
52    /// assert_eq!(alice_sdf::eval::eval(&a, p), alice_sdf::eval::eval(&b, p));
53    /// ```
54    #[must_use]
55    #[inline]
56    pub const fn box3d_half_extents(hx: f32, hy: f32, hz: f32) -> Self {
57        Self::Box3d {
58            half_extents: Vec3::new(hx, hy, hz),
59        }
60    }
61
62    /// Create a cylinder along Y-axis
63    #[must_use]
64    #[inline]
65    pub fn cylinder(radius: f32, height: f32) -> Self {
66        Self::Cylinder {
67            radius,
68            half_height: height * 0.5,
69        }
70    }
71
72    /// Create a torus in the XZ plane
73    #[must_use]
74    #[inline]
75    pub const fn torus(major_radius: f32, minor_radius: f32) -> Self {
76        Self::Torus {
77            major_radius,
78            minor_radius,
79        }
80    }
81
82    /// Create an infinite plane
83    #[must_use]
84    #[inline]
85    pub fn plane(normal: Vec3, distance: f32) -> Self {
86        Self::Plane {
87            normal: normal.normalize(),
88            distance,
89        }
90    }
91
92    /// Create a capsule between two points
93    #[must_use]
94    #[inline]
95    pub const fn capsule(a: Vec3, b: Vec3, radius: f32) -> Self {
96        Self::Capsule {
97            point_a: a,
98            point_b: b,
99            radius,
100        }
101    }
102
103    /// Create a cone along Y-axis
104    #[must_use]
105    #[inline]
106    pub fn cone(radius: f32, height: f32) -> Self {
107        Self::Cone {
108            radius,
109            half_height: height * 0.5,
110        }
111    }
112
113    /// Create an ellipsoid with given semi-axis radii
114    #[must_use]
115    #[inline]
116    pub const fn ellipsoid(rx: f32, ry: f32, rz: f32) -> Self {
117        Self::Ellipsoid {
118            radii: Vec3::new(rx, ry, rz),
119        }
120    }
121
122    /// Create a rounded cone along Y-axis
123    #[must_use]
124    #[inline]
125    pub fn rounded_cone(r1: f32, r2: f32, height: f32) -> Self {
126        Self::RoundedCone {
127            r1,
128            r2,
129            half_height: height * 0.5,
130        }
131    }
132
133    /// Create a 4-sided pyramid
134    #[must_use]
135    #[inline]
136    pub fn pyramid(height: f32) -> Self {
137        Self::Pyramid {
138            half_height: height * 0.5,
139        }
140    }
141
142    /// Create a regular octahedron
143    #[must_use]
144    #[inline]
145    pub const fn octahedron(size: f32) -> Self {
146        Self::Octahedron { size }
147    }
148
149    /// Create a hexagonal prism
150    #[must_use]
151    #[inline]
152    pub fn hex_prism(hex_radius: f32, height: f32) -> Self {
153        Self::HexPrism {
154            hex_radius,
155            half_height: height * 0.5,
156        }
157    }
158
159    /// Create a chain link shape
160    #[must_use]
161    #[inline]
162    pub fn link(length: f32, r1: f32, r2: f32) -> Self {
163        Self::Link {
164            half_length: length * 0.5,
165            r1,
166            r2,
167        }
168    }
169
170    /// Create a triangle from three vertices
171    #[must_use]
172    #[inline]
173    pub const fn triangle(a: Vec3, b: Vec3, c: Vec3) -> Self {
174        Self::Triangle {
175            point_a: a,
176            point_b: b,
177            point_c: c,
178        }
179    }
180
181    /// Create a quadratic Bezier curve tube
182    #[must_use]
183    #[inline]
184    pub const fn bezier(a: Vec3, b: Vec3, c: Vec3, radius: f32) -> Self {
185        Self::Bezier {
186            point_a: a,
187            point_b: b,
188            point_c: c,
189            radius,
190        }
191    }
192
193    /// Create a rounded box from **half-extents** (`hx`, `hy`, `hz`) and an
194    /// edge rounding radius.
195    ///
196    /// Note the asymmetry with [`SdfNode::box3d`], which takes full
197    /// dimensions; see [`SdfNode::box3d_half_extents`] for the half-extent
198    /// spelling of the plain box.
199    #[must_use]
200    #[inline]
201    pub const fn rounded_box(hx: f32, hy: f32, hz: f32, round_radius: f32) -> Self {
202        Self::RoundedBox {
203            half_extents: Vec3::new(hx, hy, hz),
204            round_radius,
205        }
206    }
207
208    /// Create a capped cone (frustum)
209    #[must_use]
210    #[inline]
211    pub fn capped_cone(height: f32, r1: f32, r2: f32) -> Self {
212        Self::CappedCone {
213            half_height: height * 0.5,
214            r1,
215            r2,
216        }
217    }
218
219    /// Create a capped torus (arc)
220    #[must_use]
221    #[inline]
222    pub const fn capped_torus(major_radius: f32, minor_radius: f32, cap_angle: f32) -> Self {
223        Self::CappedTorus {
224            major_radius,
225            minor_radius,
226            cap_angle,
227        }
228    }
229
230    /// Create a rounded cylinder
231    #[must_use]
232    #[inline]
233    pub fn rounded_cylinder(radius: f32, round_radius: f32, height: f32) -> Self {
234        Self::RoundedCylinder {
235            radius,
236            round_radius,
237            half_height: height * 0.5,
238        }
239    }
240
241    /// Create a triangular prism
242    #[must_use]
243    #[inline]
244    pub fn triangular_prism(width: f32, depth: f32) -> Self {
245        Self::TriangularPrism {
246            width,
247            half_depth: depth * 0.5,
248        }
249    }
250
251    /// Create a cut sphere
252    #[must_use]
253    #[inline]
254    pub const fn cut_sphere(radius: f32, cut_height: f32) -> Self {
255        Self::CutSphere { radius, cut_height }
256    }
257
258    /// Create a cut hollow sphere
259    #[must_use]
260    #[inline]
261    pub const fn cut_hollow_sphere(radius: f32, cut_height: f32, thickness: f32) -> Self {
262        Self::CutHollowSphere {
263            radius,
264            cut_height,
265            thickness,
266        }
267    }
268
269    /// Create a Death Star shape
270    #[must_use]
271    #[inline]
272    pub const fn death_star(ra: f32, rb: f32, d: f32) -> Self {
273        Self::DeathStar { ra, rb, d }
274    }
275
276    /// Create a solid angle
277    #[must_use]
278    #[inline]
279    pub const fn solid_angle(angle: f32, radius: f32) -> Self {
280        Self::SolidAngle { angle, radius }
281    }
282
283    /// Create a rhombus
284    #[must_use]
285    #[inline]
286    pub fn rhombus(la: f32, lb: f32, height: f32, round_radius: f32) -> Self {
287        Self::Rhombus {
288            la,
289            lb,
290            half_height: height * 0.5,
291            round_radius,
292        }
293    }
294
295    /// Create a horseshoe shape
296    #[must_use]
297    #[inline]
298    pub fn horseshoe(angle: f32, radius: f32, length: f32, width: f32, thickness: f32) -> Self {
299        Self::Horseshoe {
300            angle,
301            radius,
302            half_length: length * 0.5,
303            width,
304            thickness,
305        }
306    }
307
308    /// Create a 3D vesica
309    #[must_use]
310    #[inline]
311    pub fn vesica(radius: f32, dist: f32) -> Self {
312        Self::Vesica {
313            radius,
314            half_dist: dist * 0.5,
315        }
316    }
317
318    /// Create an infinite cylinder
319    #[must_use]
320    #[inline]
321    pub const fn infinite_cylinder(radius: f32) -> Self {
322        Self::InfiniteCylinder { radius }
323    }
324
325    /// Create an infinite cone
326    #[must_use]
327    #[inline]
328    pub const fn infinite_cone(angle: f32) -> Self {
329        Self::InfiniteCone { angle }
330    }
331
332    /// Create a gyroid surface
333    #[must_use]
334    #[inline]
335    pub const fn gyroid(scale: f32, thickness: f32) -> Self {
336        Self::Gyroid { scale, thickness }
337    }
338
339    /// Create the ball of a weighted norm.
340    ///
341    /// `(0, 1, 0)` gives a sphere, `(0, 0, 1)` a cube, `(1, 0, 0)` an
342    /// octahedron. Taking a validated
343    /// [`MetricWeights`](alice_det_math::metric::MetricWeights) is what keeps
344    /// a non-metric (a negative weight, whose unit ball is not convex) out of
345    /// the tree.
346    #[must_use]
347    #[inline]
348    pub const fn metric_ball(radius: f32, weights: alice_det_math::metric::MetricWeights) -> Self {
349        let (w_l1, w_l2, w_linf) = weights.weights();
350        Self::MetricBall {
351            radius,
352            w_l1,
353            w_l2,
354            w_linf,
355        }
356    }
357
358    /// Blend two fields over a spherical skin: `inner` within `radius` of
359    /// `center`, `outer` beyond `radius + skin`, smoothly joined between.
360    ///
361    /// Outside the bubble the result is `outer` bit for bit, so a bubble is
362    /// a local edit of the world and nothing else.
363    #[must_use]
364    #[inline]
365    pub fn metric_blend(inner: Self, outer: Self, center: Vec3, radius: f32, skin: f32) -> Self {
366        Self::MetricBlend {
367            inner: Arc::new(inner),
368            outer: Arc::new(outer),
369            center,
370            radius,
371            skin,
372        }
373    }
374
375    /// Create a 3D heart
376    #[must_use]
377    #[inline]
378    pub const fn heart(size: f32) -> Self {
379        Self::Heart { size }
380    }
381
382    /// Create a tube (hollow cylinder) along Y-axis
383    #[must_use]
384    #[inline]
385    pub fn tube(outer_radius: f32, thickness: f32, height: f32) -> Self {
386        Self::Tube {
387            outer_radius,
388            thickness,
389            half_height: height * 0.5,
390        }
391    }
392
393    /// Create a barrel along Y-axis
394    #[must_use]
395    #[inline]
396    pub fn barrel(radius: f32, height: f32, bulge: f32) -> Self {
397        Self::Barrel {
398            radius,
399            half_height: height * 0.5,
400            bulge,
401        }
402    }
403
404    /// Create a diamond (bipyramid)
405    #[must_use]
406    #[inline]
407    pub fn diamond(radius: f32, height: f32) -> Self {
408        Self::Diamond {
409            radius,
410            half_height: height * 0.5,
411        }
412    }
413
414    /// Create a chamfered cube
415    #[must_use]
416    #[inline]
417    pub fn chamfered_cube(width: f32, height: f32, depth: f32, chamfer: f32) -> Self {
418        Self::ChamferedCube {
419            half_extents: Vec3::new(width * 0.5, height * 0.5, depth * 0.5),
420            chamfer,
421        }
422    }
423
424    /// Create a Schwarz P surface
425    #[must_use]
426    #[inline]
427    pub const fn schwarz_p(scale: f32, thickness: f32) -> Self {
428        Self::SchwarzP { scale, thickness }
429    }
430
431    /// Create a superellipsoid
432    #[must_use]
433    #[inline]
434    pub fn superellipsoid(width: f32, height: f32, depth: f32, e1: f32, e2: f32) -> Self {
435        Self::Superellipsoid {
436            half_extents: Vec3::new(width * 0.5, height * 0.5, depth * 0.5),
437            e1,
438            e2,
439        }
440    }
441
442    /// Create a rounded X shape
443    #[must_use]
444    #[inline]
445    pub fn rounded_x(width: f32, round_radius: f32, height: f32) -> Self {
446        Self::RoundedX {
447            width,
448            round_radius,
449            half_height: height * 0.5,
450        }
451    }
452
453    /// Create a pie (sector) shape
454    #[must_use]
455    #[inline]
456    pub fn pie(angle: f32, radius: f32, height: f32) -> Self {
457        Self::Pie {
458            angle,
459            radius,
460            half_height: height * 0.5,
461        }
462    }
463
464    /// Create a trapezoid prism
465    #[must_use]
466    #[inline]
467    pub fn trapezoid(r1: f32, r2: f32, trap_height: f32, depth: f32) -> Self {
468        Self::Trapezoid {
469            r1,
470            r2,
471            trap_height: trap_height * 0.5,
472            half_depth: depth * 0.5,
473        }
474    }
475
476    /// Create a parallelogram prism
477    #[must_use]
478    #[inline]
479    pub fn parallelogram(width: f32, para_height: f32, skew: f32, depth: f32) -> Self {
480        Self::Parallelogram {
481            width,
482            para_height: para_height * 0.5,
483            skew,
484            half_depth: depth * 0.5,
485        }
486    }
487
488    /// Create a tunnel shape
489    #[must_use]
490    #[inline]
491    pub fn tunnel(width: f32, height_2d: f32, depth: f32) -> Self {
492        Self::Tunnel {
493            width,
494            height_2d,
495            half_depth: depth * 0.5,
496        }
497    }
498
499    /// Create an uneven capsule prism
500    #[must_use]
501    #[inline]
502    pub fn uneven_capsule(r1: f32, r2: f32, cap_height: f32, depth: f32) -> Self {
503        Self::UnevenCapsule {
504            r1,
505            r2,
506            cap_height: cap_height * 0.5,
507            half_depth: depth * 0.5,
508        }
509    }
510
511    /// Create an egg shape
512    #[must_use]
513    #[inline]
514    pub const fn egg(ra: f32, rb: f32) -> Self {
515        Self::Egg { ra, rb }
516    }
517
518    /// Create an arc shape (thick ring sector)
519    #[must_use]
520    pub fn arc_shape(aperture: f32, radius: f32, thickness: f32, height: f32) -> Self {
521        Self::ArcShape {
522            aperture,
523            radius,
524            thickness,
525            half_height: height * 0.5,
526        }
527    }
528
529    /// Create a moon (crescent) shape
530    #[must_use]
531    pub fn moon(d: f32, ra: f32, rb: f32, height: f32) -> Self {
532        Self::Moon {
533            d,
534            ra,
535            rb,
536            half_height: height * 0.5,
537        }
538    }
539
540    /// Create a cross (plus) shape
541    #[must_use]
542    pub fn cross_shape(length: f32, thickness: f32, round_radius: f32, height: f32) -> Self {
543        Self::CrossShape {
544            length,
545            thickness,
546            round_radius,
547            half_height: height * 0.5,
548        }
549    }
550
551    /// Create a blobby cross (organic) shape
552    #[must_use]
553    pub fn blobby_cross(size: f32, height: f32) -> Self {
554        Self::BlobbyCross {
555            size,
556            half_height: height * 0.5,
557        }
558    }
559
560    /// Create a parabola segment
561    #[must_use]
562    pub fn parabola_segment(width: f32, para_height: f32, depth: f32) -> Self {
563        Self::ParabolaSegment {
564            width,
565            para_height,
566            half_depth: depth * 0.5,
567        }
568    }
569
570    /// Create a regular N-sided polygon prism
571    #[must_use]
572    pub fn regular_polygon(radius: f32, n_sides: u32, height: f32) -> Self {
573        Self::RegularPolygon {
574            radius,
575            n_sides: n_sides as f32,
576            half_height: height * 0.5,
577        }
578    }
579
580    /// Create a star polygon prism
581    #[must_use]
582    pub fn star_polygon(radius: f32, n_points: u32, m: f32, height: f32) -> Self {
583        Self::StarPolygon {
584            radius,
585            n_points: n_points as f32,
586            m,
587            half_height: height * 0.5,
588        }
589    }
590
591    /// Create a staircase shape
592    #[must_use]
593    pub fn stairs(step_width: f32, step_height: f32, n_steps: u32, depth: f32) -> Self {
594        Self::Stairs {
595            step_width,
596            step_height,
597            n_steps: n_steps as f32,
598            half_depth: depth * 0.5,
599        }
600    }
601
602    /// Create a helix (spiral tube)
603    #[must_use]
604    pub fn helix(major_r: f32, minor_r: f32, pitch: f32, height: f32) -> Self {
605        Self::Helix {
606            major_r,
607            minor_r,
608            pitch,
609            half_height: height * 0.5,
610        }
611    }
612
613    /// Create a regular tetrahedron
614    #[must_use]
615    #[inline]
616    pub const fn tetrahedron(size: f32) -> Self {
617        Self::Tetrahedron { size }
618    }
619
620    /// Create a regular dodecahedron
621    #[must_use]
622    #[inline]
623    pub const fn dodecahedron(radius: f32) -> Self {
624        Self::Dodecahedron { radius }
625    }
626
627    /// Create a regular icosahedron
628    #[must_use]
629    #[inline]
630    pub const fn icosahedron(radius: f32) -> Self {
631        Self::Icosahedron { radius }
632    }
633
634    /// Create a truncated octahedron
635    #[must_use]
636    #[inline]
637    pub const fn truncated_octahedron(radius: f32) -> Self {
638        Self::TruncatedOctahedron { radius }
639    }
640
641    /// Create a truncated icosahedron (soccer ball)
642    #[must_use]
643    #[inline]
644    pub const fn truncated_icosahedron(radius: f32) -> Self {
645        Self::TruncatedIcosahedron { radius }
646    }
647
648    /// Create a box frame (wireframe box)
649    #[must_use]
650    #[inline]
651    pub const fn box_frame(half_extents: Vec3, edge: f32) -> Self {
652        Self::BoxFrame { half_extents, edge }
653    }
654
655    /// Create a diamond surface (TPMS)
656    #[must_use]
657    #[inline]
658    pub const fn diamond_surface(scale: f32, thickness: f32) -> Self {
659        Self::DiamondSurface { scale, thickness }
660    }
661
662    /// Create a neovius surface (TPMS)
663    #[must_use]
664    #[inline]
665    pub const fn neovius(scale: f32, thickness: f32) -> Self {
666        Self::Neovius { scale, thickness }
667    }
668
669    /// Create a lidinoid surface (TPMS)
670    #[must_use]
671    #[inline]
672    pub const fn lidinoid(scale: f32, thickness: f32) -> Self {
673        Self::Lidinoid { scale, thickness }
674    }
675
676    /// Create an IWP surface (TPMS)
677    #[must_use]
678    #[inline]
679    pub const fn iwp(scale: f32, thickness: f32) -> Self {
680        Self::IWP { scale, thickness }
681    }
682
683    /// Create an FRD surface (TPMS)
684    #[must_use]
685    #[inline]
686    pub const fn frd(scale: f32, thickness: f32) -> Self {
687        Self::FRD { scale, thickness }
688    }
689
690    /// Create a Fischer-Koch S surface (TPMS)
691    #[must_use]
692    #[inline]
693    pub const fn fischer_koch_s(scale: f32, thickness: f32) -> Self {
694        Self::FischerKochS { scale, thickness }
695    }
696
697    /// Create a PMY surface (TPMS)
698    #[must_use]
699    #[inline]
700    pub const fn pmy(scale: f32, thickness: f32) -> Self {
701        Self::PMY { scale, thickness }
702    }
703
704    // === 2D Primitive constructors ===
705
706    /// Create a 2D circle extruded along Z
707    #[must_use]
708    #[inline]
709    pub const fn circle_2d(radius: f32, half_height: f32) -> Self {
710        Self::Circle2D {
711            radius,
712            half_height,
713        }
714    }
715
716    /// Create a 2D rectangle extruded along Z
717    #[must_use]
718    #[inline]
719    pub const fn rect_2d(half_w: f32, half_h: f32, half_height: f32) -> Self {
720        Self::Rect2D {
721            half_extents: Vec2::new(half_w, half_h),
722            half_height,
723        }
724    }
725
726    /// Create a 2D line segment extruded along Z
727    #[must_use]
728    #[inline]
729    pub const fn segment_2d(
730        ax: f32,
731        ay: f32,
732        bx: f32,
733        by: f32,
734        thickness: f32,
735        half_height: f32,
736    ) -> Self {
737        Self::Segment2D {
738            a: Vec2::new(ax, ay),
739            b: Vec2::new(bx, by),
740            thickness,
741            half_height,
742        }
743    }
744
745    /// Create a 2D polygon extruded along Z
746    #[must_use]
747    #[inline]
748    pub const fn polygon_2d(vertices: Vec<Vec2>, half_height: f32) -> Self {
749        Self::Polygon2D {
750            vertices,
751            half_height,
752        }
753    }
754
755    /// Create a 2D rounded rectangle extruded along Z
756    #[must_use]
757    #[inline]
758    pub const fn rounded_rect_2d(
759        half_w: f32,
760        half_h: f32,
761        round_radius: f32,
762        half_height: f32,
763    ) -> Self {
764        Self::RoundedRect2D {
765            half_extents: Vec2::new(half_w, half_h),
766            round_radius,
767            half_height,
768        }
769    }
770
771    /// Create a 2D annular (ring) shape extruded along Z
772    #[must_use]
773    #[inline]
774    pub const fn annular_2d(outer_radius: f32, thickness: f32, half_height: f32) -> Self {
775        Self::Annular2D {
776            outer_radius,
777            thickness,
778            half_height,
779        }
780    }
781}