Skip to main content

bevy_gizmos/primitives/
dim3.rs

1//! A module for rendering each of the 3D [`bevy_shape`] primitives with [`GizmoBuffer`].
2
3use super::helpers::*;
4
5use bevy_color::Color;
6use bevy_math::{Dir3, Isometry3d, Quat, UVec2, Vec2, Vec3};
7use bevy_shape::{
8    Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d, Primitive3d,
9    Segment3d, Sphere, Tetrahedron, Torus, Triangle3d,
10};
11
12use crate::{circles::SphereBuilder, gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
13
14const DEFAULT_RESOLUTION: u32 = 5;
15const DEFAULT_CAPSULE_RESOLUTION: u32 = 4;
16// length used to simulate infinite lines
17const INFINITE_LEN: f32 = 10_000.0;
18
19/// A trait for rendering 3D geometric primitives (`P`) with [`GizmoBuffer`].
20pub trait GizmoPrimitive3d<P: Primitive3d> {
21    /// The output of `primitive_3d`. This is a builder to set non-default values.
22    type Output<'a>
23    where
24        Self: 'a;
25
26    /// Renders a 3D primitive with its associated details.
27    fn primitive_3d(
28        &mut self,
29        primitive: &P,
30        isometry: impl Into<Isometry3d>,
31        color: impl Into<Color>,
32    ) -> Self::Output<'_>;
33}
34
35// direction 3d
36
37impl<Config, Clear> GizmoPrimitive3d<Dir3> for GizmoBuffer<Config, Clear>
38where
39    Config: GizmoConfigGroup,
40    Clear: 'static + Send + Sync,
41{
42    type Output<'a>
43        = ()
44    where
45        Self: 'a;
46
47    fn primitive_3d(
48        &mut self,
49        primitive: &Dir3,
50        isometry: impl Into<Isometry3d>,
51        color: impl Into<Color>,
52    ) -> Self::Output<'_> {
53        let isometry = isometry.into();
54        let start = Vec3::ZERO;
55        let end = primitive.as_vec3();
56        self.arrow(isometry * start, isometry * end, color);
57    }
58}
59
60// sphere
61
62impl<Config, Clear> GizmoPrimitive3d<Sphere> for GizmoBuffer<Config, Clear>
63where
64    Config: GizmoConfigGroup,
65    Clear: 'static + Send + Sync,
66{
67    type Output<'a>
68        = SphereBuilder<'a, Config, Clear>
69    where
70        Self: 'a;
71
72    fn primitive_3d(
73        &mut self,
74        primitive: &Sphere,
75        isometry: impl Into<Isometry3d>,
76        color: impl Into<Color>,
77    ) -> Self::Output<'_> {
78        self.sphere(isometry, primitive.radius, color)
79    }
80}
81
82// plane 3d
83
84/// Builder for configuring the drawing options of [`Plane3d`].
85pub struct Plane3dBuilder<'a, Config, Clear>
86where
87    Config: GizmoConfigGroup,
88    Clear: 'static + Send + Sync,
89{
90    gizmos: &'a mut GizmoBuffer<Config, Clear>,
91
92    // Direction of the normal orthogonal to the plane
93    normal: Dir3,
94
95    isometry: Isometry3d,
96    // Color of the plane
97    color: Color,
98
99    // Defines the amount of cells in the x and y axes
100    cell_count: UVec2,
101    // Defines the distance between cells along the x and y axes
102    spacing: Vec2,
103}
104
105impl<Config, Clear> Plane3dBuilder<'_, Config, Clear>
106where
107    Config: GizmoConfigGroup,
108    Clear: 'static + Send + Sync,
109{
110    /// Set the number of cells in the x and y axes direction.
111    pub fn cell_count(mut self, cell_count: UVec2) -> Self {
112        self.cell_count = cell_count;
113        self
114    }
115
116    /// Set the distance between cells along the x and y axes.
117    pub fn spacing(mut self, spacing: Vec2) -> Self {
118        self.spacing = spacing;
119        self
120    }
121}
122
123impl<Config, Clear> GizmoPrimitive3d<Plane3d> for GizmoBuffer<Config, Clear>
124where
125    Config: GizmoConfigGroup,
126    Clear: 'static + Send + Sync,
127{
128    type Output<'a>
129        = Plane3dBuilder<'a, Config, Clear>
130    where
131        Self: 'a;
132
133    fn primitive_3d(
134        &mut self,
135        primitive: &Plane3d,
136        isometry: impl Into<Isometry3d>,
137        color: impl Into<Color>,
138    ) -> Self::Output<'_> {
139        Plane3dBuilder {
140            gizmos: self,
141            normal: primitive.normal,
142            isometry: isometry.into(),
143            color: color.into(),
144            cell_count: UVec2::splat(3),
145            spacing: Vec2::splat(1.0),
146        }
147    }
148}
149
150impl<Config, Clear> Drop for Plane3dBuilder<'_, Config, Clear>
151where
152    Config: GizmoConfigGroup,
153    Clear: 'static + Send + Sync,
154{
155    fn drop(&mut self) {
156        if !self.gizmos.enabled {
157            return;
158        }
159
160        self.gizmos
161            .primitive_3d(&self.normal, self.isometry, self.color);
162        // the default orientation of the grid is Z-up
163        let rot = Quat::from_rotation_arc(Vec3::Z, self.normal.as_vec3());
164        self.gizmos.grid(
165            Isometry3d::new(self.isometry.translation, self.isometry.rotation * rot),
166            self.cell_count,
167            self.spacing,
168            self.color,
169        );
170    }
171}
172
173// line 3d
174
175impl<Config, Clear> GizmoPrimitive3d<Line3d> for GizmoBuffer<Config, Clear>
176where
177    Config: GizmoConfigGroup,
178    Clear: 'static + Send + Sync,
179{
180    type Output<'a>
181        = ()
182    where
183        Self: 'a;
184
185    fn primitive_3d(
186        &mut self,
187        primitive: &Line3d,
188        isometry: impl Into<Isometry3d>,
189        color: impl Into<Color>,
190    ) -> Self::Output<'_> {
191        if !self.enabled {
192            return;
193        }
194
195        let isometry = isometry.into();
196        let color = color.into();
197        let direction = primitive.direction.as_vec3();
198        self.arrow(isometry * Vec3::ZERO, isometry * direction, color);
199
200        let [start, end] = [1.0, -1.0]
201            .map(|sign| sign * INFINITE_LEN)
202            .map(|length| primitive.direction * length)
203            .map(|offset| isometry * offset);
204        self.line(start, end, color);
205    }
206}
207
208// segment 3d
209
210impl<Config, Clear> GizmoPrimitive3d<Segment3d> for GizmoBuffer<Config, Clear>
211where
212    Config: GizmoConfigGroup,
213    Clear: 'static + Send + Sync,
214{
215    type Output<'a>
216        = ()
217    where
218        Self: 'a;
219
220    fn primitive_3d(
221        &mut self,
222        primitive: &Segment3d,
223        isometry: impl Into<Isometry3d>,
224        color: impl Into<Color>,
225    ) -> Self::Output<'_> {
226        if !self.enabled {
227            return;
228        }
229
230        let transformed = primitive.transformed(isometry);
231        self.line(transformed.point1(), transformed.point2(), color);
232    }
233}
234
235// polyline 3d
236
237impl<Config, Clear> GizmoPrimitive3d<Polyline3d> for GizmoBuffer<Config, Clear>
238where
239    Config: GizmoConfigGroup,
240    Clear: 'static + Send + Sync,
241{
242    type Output<'a>
243        = ()
244    where
245        Self: 'a;
246
247    fn primitive_3d(
248        &mut self,
249        primitive: &Polyline3d,
250        isometry: impl Into<Isometry3d>,
251        color: impl Into<Color>,
252    ) -> Self::Output<'_> {
253        if !self.enabled {
254            return;
255        }
256
257        let isometry = isometry.into();
258        self.linestrip(
259            primitive.vertices.iter().map(|vec3| isometry * *vec3),
260            color,
261        );
262    }
263}
264
265// triangle 3d
266
267impl<Config, Clear> GizmoPrimitive3d<Triangle3d> for GizmoBuffer<Config, Clear>
268where
269    Config: GizmoConfigGroup,
270    Clear: 'static + Send + Sync,
271{
272    type Output<'a>
273        = ()
274    where
275        Self: 'a;
276
277    fn primitive_3d(
278        &mut self,
279        primitive: &Triangle3d,
280        isometry: impl Into<Isometry3d>,
281        color: impl Into<Color>,
282    ) -> Self::Output<'_> {
283        if !self.enabled {
284            return;
285        }
286
287        let isometry = isometry.into();
288        let [a, b, c] = primitive.vertices;
289        self.lineloop([a, b, c].map(|vec3| isometry * vec3), color);
290    }
291}
292
293// cuboid
294
295impl<Config, Clear> GizmoPrimitive3d<Cuboid> for GizmoBuffer<Config, Clear>
296where
297    Config: GizmoConfigGroup,
298    Clear: 'static + Send + Sync,
299{
300    type Output<'a>
301        = ()
302    where
303        Self: 'a;
304
305    fn primitive_3d(
306        &mut self,
307        primitive: &Cuboid,
308        isometry: impl Into<Isometry3d>,
309        color: impl Into<Color>,
310    ) -> Self::Output<'_> {
311        if !self.enabled {
312            return;
313        }
314
315        let isometry = isometry.into();
316
317        // transform the points from the reference unit cube to the cuboid coords
318        let vertices @ [a, b, c, d, e, f, g, h] = [
319            [1.0, 1.0, 1.0],
320            [-1.0, 1.0, 1.0],
321            [-1.0, -1.0, 1.0],
322            [1.0, -1.0, 1.0],
323            [1.0, 1.0, -1.0],
324            [-1.0, 1.0, -1.0],
325            [-1.0, -1.0, -1.0],
326            [1.0, -1.0, -1.0],
327        ]
328        .map(Vec3::from)
329        .map(|vec3| vec3 * primitive.half_size)
330        .map(|vec3| isometry * vec3);
331
332        // lines for the upper rectangle of the cuboid
333        let upper = [a, b, c, d]
334            .into_iter()
335            .zip([a, b, c, d].into_iter().cycle().skip(1));
336
337        // lines for the lower rectangle of the cuboid
338        let lower = [e, f, g, h]
339            .into_iter()
340            .zip([e, f, g, h].into_iter().cycle().skip(1));
341
342        // lines connecting upper and lower rectangles of the cuboid
343        let connections = vertices.into_iter().zip(vertices.into_iter().skip(4));
344
345        let color = color.into();
346        upper
347            .chain(lower)
348            .chain(connections)
349            .for_each(|(start, end)| {
350                self.line(start, end, color);
351            });
352    }
353}
354
355// cylinder 3d
356
357/// Builder for configuring the drawing options of [`Cylinder`].
358pub struct Cylinder3dBuilder<'a, Config, Clear>
359where
360    Config: GizmoConfigGroup,
361    Clear: 'static + Send + Sync,
362{
363    gizmos: &'a mut GizmoBuffer<Config, Clear>,
364
365    // Radius of the cylinder
366    radius: f32,
367    // Half height of the cylinder
368    half_height: f32,
369
370    isometry: Isometry3d,
371    // Color of the cylinder
372    color: Color,
373
374    // Number of lines used to approximate the cylinder geometry
375    resolution: u32,
376}
377
378impl<Config, Clear> Cylinder3dBuilder<'_, Config, Clear>
379where
380    Config: GizmoConfigGroup,
381    Clear: 'static + Send + Sync,
382{
383    /// Set the number of lines used to approximate the top and bottom of the cylinder geometry.
384    pub fn resolution(mut self, resolution: u32) -> Self {
385        self.resolution = resolution;
386        self
387    }
388}
389
390impl<Config, Clear> GizmoPrimitive3d<Cylinder> for GizmoBuffer<Config, Clear>
391where
392    Config: GizmoConfigGroup,
393    Clear: 'static + Send + Sync,
394{
395    type Output<'a>
396        = Cylinder3dBuilder<'a, Config, Clear>
397    where
398        Self: 'a;
399
400    fn primitive_3d(
401        &mut self,
402        primitive: &Cylinder,
403        isometry: impl Into<Isometry3d>,
404        color: impl Into<Color>,
405    ) -> Self::Output<'_> {
406        Cylinder3dBuilder {
407            gizmos: self,
408            radius: primitive.radius,
409            half_height: primitive.half_height,
410            isometry: isometry.into(),
411            color: color.into(),
412            resolution: DEFAULT_RESOLUTION,
413        }
414    }
415}
416
417impl<Config, Clear> Drop for Cylinder3dBuilder<'_, Config, Clear>
418where
419    Config: GizmoConfigGroup,
420    Clear: 'static + Send + Sync,
421{
422    fn drop(&mut self) {
423        if !self.gizmos.enabled {
424            return;
425        }
426
427        self.gizmos
428            .primitive_3d(
429                &ConicalFrustum {
430                    radius_top: self.radius,
431                    radius_bottom: self.radius,
432                    height: self.half_height * 2.0,
433                },
434                self.isometry,
435                self.color,
436            )
437            .resolution(self.resolution);
438    }
439}
440
441// capsule 3d
442
443/// Builder for configuring the drawing options of [`Capsule3d`].
444pub struct Capsule3dBuilder<'a, Config, Clear>
445where
446    Config: GizmoConfigGroup,
447    Clear: 'static + Send + Sync,
448{
449    gizmos: &'a mut GizmoBuffer<Config, Clear>,
450
451    // Radius of the capsule
452    radius: f32,
453    // Half length of the capsule
454    half_length: f32,
455
456    isometry: Isometry3d,
457    // Color of the capsule
458    color: Color,
459
460    // Number of lines used to approximate the capsule geometry
461    resolution: u32,
462}
463
464impl<Config, Clear> Capsule3dBuilder<'_, Config, Clear>
465where
466    Config: GizmoConfigGroup,
467    Clear: 'static + Send + Sync,
468{
469    /// Set the number of lines used to approximate the capsule geometry.
470    pub fn resolution(mut self, resolution: u32) -> Self {
471        self.resolution = resolution;
472        self
473    }
474}
475
476impl<Config, Clear> GizmoPrimitive3d<Capsule3d> for GizmoBuffer<Config, Clear>
477where
478    Config: GizmoConfigGroup,
479    Clear: 'static + Send + Sync,
480{
481    type Output<'a>
482        = Capsule3dBuilder<'a, Config, Clear>
483    where
484        Self: 'a;
485
486    fn primitive_3d(
487        &mut self,
488        primitive: &Capsule3d,
489        isometry: impl Into<Isometry3d>,
490        color: impl Into<Color>,
491    ) -> Self::Output<'_> {
492        Capsule3dBuilder {
493            gizmos: self,
494            radius: primitive.radius,
495            half_length: primitive.half_length,
496            isometry: isometry.into(),
497            color: color.into(),
498            resolution: DEFAULT_CAPSULE_RESOLUTION,
499        }
500    }
501}
502
503impl<Config, Clear> Drop for Capsule3dBuilder<'_, Config, Clear>
504where
505    Config: GizmoConfigGroup,
506    Clear: 'static + Send + Sync,
507{
508    fn drop(&mut self) {
509        if !self.gizmos.enabled {
510            return;
511        }
512
513        let [upper_apex, lower_apex] = [-1.0, 1.0]
514            .map(|sign| Vec3::Y * sign * (self.half_length + self.radius))
515            .map(|vec3| self.isometry * vec3);
516        let [upper_center, lower_center] = [-1.0, 1.0]
517            .map(|sign| Vec3::Y * sign * self.half_length)
518            .map(|vec3| self.isometry * vec3);
519        let [upper_points, lower_points] = [-1.0, 1.0]
520            .map(|sign| Vec3::Y * sign * self.half_length)
521            .map(|vec3| {
522                circle_coordinates_closed(self.radius, self.resolution)
523                    .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + vec3)
524                    .map(|vec3| self.isometry * vec3)
525                    .collect::<Vec<_>>()
526            });
527
528        upper_points.iter().skip(1).copied().for_each(|start| {
529            self.gizmos
530                .short_arc_3d_between(upper_center, start, upper_apex, self.color);
531        });
532        lower_points.iter().skip(1).copied().for_each(|start| {
533            self.gizmos
534                .short_arc_3d_between(lower_center, start, lower_apex, self.color);
535        });
536
537        let circle_rotation = self
538            .isometry
539            .rotation
540            .mul_quat(Quat::from_rotation_x(core::f32::consts::FRAC_PI_2));
541        self.gizmos.circle(
542            Isometry3d::new(upper_center, circle_rotation),
543            self.radius,
544            self.color,
545        );
546        self.gizmos.circle(
547            Isometry3d::new(lower_center, circle_rotation),
548            self.radius,
549            self.color,
550        );
551
552        let connection_lines = upper_points.into_iter().zip(lower_points).skip(1);
553        connection_lines.for_each(|(start, end)| {
554            self.gizmos.line(start, end, self.color);
555        });
556    }
557}
558
559// cone 3d
560
561/// Builder for configuring the drawing options of [`Cone`].
562pub struct Cone3dBuilder<'a, Config, Clear>
563where
564    Config: GizmoConfigGroup,
565    Clear: 'static + Send + Sync,
566{
567    gizmos: &'a mut GizmoBuffer<Config, Clear>,
568
569    // Radius of the cone
570    radius: f32,
571    // Height of the cone
572    height: f32,
573
574    isometry: Isometry3d,
575    // Color of the cone
576    color: Color,
577
578    // Number of lines used to approximate the cone base geometry
579    base_resolution: u32,
580
581    // Number of lines used to approximate the cone height geometry
582    height_resolution: u32,
583}
584
585impl<Config, Clear> Cone3dBuilder<'_, Config, Clear>
586where
587    Config: GizmoConfigGroup,
588    Clear: 'static + Send + Sync,
589{
590    /// Set the number of lines used to approximate the cone geometry for its base and height.
591    pub fn resolution(mut self, resolution: u32) -> Self {
592        self.base_resolution = resolution;
593        self.height_resolution = resolution;
594        self
595    }
596
597    /// Set the number of lines used to approximate the height of the cone geometry.
598    ///
599    /// `resolution` should be a multiple of the value passed to [`Self::height_resolution`]
600    /// for the height to connect properly with the base.
601    pub fn base_resolution(mut self, resolution: u32) -> Self {
602        self.base_resolution = resolution;
603        self
604    }
605
606    /// Set the number of lines used to approximate the height of the cone geometry.
607    ///
608    /// `resolution` should be a divisor of the value passed to [`Self::base_resolution`]
609    /// for the height to connect properly with the base.
610    pub fn height_resolution(mut self, resolution: u32) -> Self {
611        self.height_resolution = resolution;
612        self
613    }
614}
615
616impl<Config, Clear> GizmoPrimitive3d<Cone> for GizmoBuffer<Config, Clear>
617where
618    Config: GizmoConfigGroup,
619    Clear: 'static + Send + Sync,
620{
621    type Output<'a>
622        = Cone3dBuilder<'a, Config, Clear>
623    where
624        Self: 'a;
625
626    fn primitive_3d(
627        &mut self,
628        primitive: &Cone,
629        isometry: impl Into<Isometry3d>,
630        color: impl Into<Color>,
631    ) -> Self::Output<'_> {
632        Cone3dBuilder {
633            gizmos: self,
634            radius: primitive.radius,
635            height: primitive.height,
636            isometry: isometry.into(),
637            color: color.into(),
638            base_resolution: DEFAULT_RESOLUTION,
639            height_resolution: DEFAULT_RESOLUTION,
640        }
641    }
642}
643
644impl<Config, Clear> Drop for Cone3dBuilder<'_, Config, Clear>
645where
646    Config: GizmoConfigGroup,
647    Clear: 'static + Send + Sync,
648{
649    fn drop(&mut self) {
650        if !self.gizmos.enabled {
651            return;
652        }
653
654        let half_height = self.height * 0.5;
655        let apex = self.isometry * (Vec3::Y * half_height);
656        let circle_center = half_height * Vec3::NEG_Y;
657        let circle_coords = circle_coordinates_closed(self.radius, self.height_resolution)
658            .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + circle_center)
659            .map(|vec3| self.isometry * vec3)
660            .collect::<Vec<_>>();
661
662        // connections to apex
663        circle_coords
664            .iter()
665            .skip(1)
666            .map(|vec3| (*vec3, apex))
667            .for_each(|(start, end)| {
668                self.gizmos.line(start, end, self.color);
669            });
670
671        // base circle
672        circle_coords.array_windows().for_each(|&[start, end]| {
673            self.gizmos.line(start, end, self.color);
674        });
675    }
676}
677
678// conical frustum 3d
679
680/// Builder for configuring the drawing options of [`ConicalFrustum`].
681pub struct ConicalFrustum3dBuilder<'a, Config, Clear>
682where
683    Config: GizmoConfigGroup,
684    Clear: 'static + Send + Sync,
685{
686    gizmos: &'a mut GizmoBuffer<Config, Clear>,
687
688    // Radius of the top circle
689    radius_top: f32,
690    // Radius of the bottom circle
691    radius_bottom: f32,
692    // Height of the conical frustum
693    height: f32,
694
695    isometry: Isometry3d,
696    // Color of the conical frustum
697    color: Color,
698
699    // Number of lines used to approximate the curved surfaces
700    resolution: u32,
701}
702
703impl<Config, Clear> ConicalFrustum3dBuilder<'_, Config, Clear>
704where
705    Config: GizmoConfigGroup,
706    Clear: 'static + Send + Sync,
707{
708    /// Set the number of lines used to approximate the curved surfaces.
709    pub fn resolution(mut self, resolution: u32) -> Self {
710        self.resolution = resolution;
711        self
712    }
713}
714
715impl<Config, Clear> GizmoPrimitive3d<ConicalFrustum> for GizmoBuffer<Config, Clear>
716where
717    Config: GizmoConfigGroup,
718    Clear: 'static + Send + Sync,
719{
720    type Output<'a>
721        = ConicalFrustum3dBuilder<'a, Config, Clear>
722    where
723        Self: 'a;
724
725    fn primitive_3d(
726        &mut self,
727        primitive: &ConicalFrustum,
728        isometry: impl Into<Isometry3d>,
729        color: impl Into<Color>,
730    ) -> Self::Output<'_> {
731        ConicalFrustum3dBuilder {
732            gizmos: self,
733            radius_top: primitive.radius_top,
734            radius_bottom: primitive.radius_bottom,
735            height: primitive.height,
736            isometry: isometry.into(),
737            color: color.into(),
738            resolution: DEFAULT_RESOLUTION,
739        }
740    }
741}
742
743impl<Config, Clear> Drop for ConicalFrustum3dBuilder<'_, Config, Clear>
744where
745    Config: GizmoConfigGroup,
746    Clear: 'static + Send + Sync,
747{
748    fn drop(&mut self) {
749        if !self.gizmos.enabled {
750            return;
751        }
752
753        let half_height = self.height * 0.5;
754        let [upper_points, lower_points] = [(-1.0, self.radius_bottom), (1.0, self.radius_top)]
755            .map(|(sign, radius)| {
756                let translation = Vec3::Y * sign * half_height;
757                circle_coordinates_closed(radius, self.resolution)
758                    .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation)
759                    .map(|vec3| self.isometry * vec3)
760                    .collect::<Vec<_>>()
761            });
762
763        let upper_lines = upper_points.array_windows();
764        let lower_lines = lower_points.array_windows();
765        upper_lines.chain(lower_lines).for_each(|&[start, end]| {
766            self.gizmos.line(start, end, self.color);
767        });
768
769        let connection_lines = upper_points.into_iter().zip(lower_points).skip(1);
770        connection_lines.for_each(|(start, end)| {
771            self.gizmos.line(start, end, self.color);
772        });
773    }
774}
775
776// torus 3d
777
778/// Builder for configuring the drawing options of [`Torus`].
779pub struct Torus3dBuilder<'a, Config, Clear>
780where
781    Config: GizmoConfigGroup,
782    Clear: 'static + Send + Sync,
783{
784    gizmos: &'a mut GizmoBuffer<Config, Clear>,
785
786    // Radius of the minor circle (tube)
787    minor_radius: f32,
788    // Radius of the major circle (ring)
789    major_radius: f32,
790
791    isometry: Isometry3d,
792    // Color of the torus
793    color: Color,
794
795    // Number of lines in the minor (tube) direction
796    minor_resolution: u32,
797    // Number of lines in the major (ring) direction
798    major_resolution: u32,
799}
800
801impl<Config, Clear> Torus3dBuilder<'_, Config, Clear>
802where
803    Config: GizmoConfigGroup,
804    Clear: 'static + Send + Sync,
805{
806    /// Set the number of lines in the minor (tube) direction.
807    pub fn minor_resolution(mut self, minor_resolution: u32) -> Self {
808        self.minor_resolution = minor_resolution;
809        self
810    }
811
812    /// Set the number of lines in the major (ring) direction.
813    pub fn major_resolution(mut self, major_resolution: u32) -> Self {
814        self.major_resolution = major_resolution;
815        self
816    }
817}
818
819impl<Config, Clear> GizmoPrimitive3d<Torus> for GizmoBuffer<Config, Clear>
820where
821    Config: GizmoConfigGroup,
822    Clear: 'static + Send + Sync,
823{
824    type Output<'a>
825        = Torus3dBuilder<'a, Config, Clear>
826    where
827        Self: 'a;
828
829    fn primitive_3d(
830        &mut self,
831        primitive: &Torus,
832        isometry: impl Into<Isometry3d>,
833        color: impl Into<Color>,
834    ) -> Self::Output<'_> {
835        Torus3dBuilder {
836            gizmos: self,
837            minor_radius: primitive.minor_radius,
838            major_radius: primitive.major_radius,
839            isometry: isometry.into(),
840            color: color.into(),
841            minor_resolution: DEFAULT_RESOLUTION,
842            major_resolution: DEFAULT_RESOLUTION,
843        }
844    }
845}
846
847impl<Config, Clear> Drop for Torus3dBuilder<'_, Config, Clear>
848where
849    Config: GizmoConfigGroup,
850    Clear: 'static + Send + Sync,
851{
852    fn drop(&mut self) {
853        if !self.gizmos.enabled {
854            return;
855        }
856
857        // draw 4 circles with major_radius
858        let [inner, outer, top, bottom] = [
859            (self.major_radius - self.minor_radius, 0.0),
860            (self.major_radius + self.minor_radius, 0.0),
861            (self.major_radius, self.minor_radius),
862            (self.major_radius, -self.minor_radius),
863        ]
864        .map(|(radius, height)| {
865            let translation = height * Vec3::Y;
866            circle_coordinates_closed(radius, self.major_resolution)
867                .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation)
868                .map(|vec3| self.isometry * vec3)
869                .collect::<Vec<_>>()
870        });
871
872        [&inner, &outer, &top, &bottom]
873            .iter()
874            .flat_map(|points| points.array_windows())
875            .for_each(|&[start, end]| {
876                self.gizmos.line(start, end, self.color);
877            });
878
879        inner
880            .into_iter()
881            .zip(top)
882            .zip(outer)
883            .zip(bottom)
884            .flat_map(|(((inner, top), outer), bottom)| {
885                let center = (inner + top + outer + bottom) * 0.25;
886                [(inner, top), (top, outer), (outer, bottom), (bottom, inner)]
887                    .map(|(start, end)| (start, end, center))
888            })
889            .for_each(|(from, to, center)| {
890                self.gizmos
891                    .short_arc_3d_between(center, from, to, self.color)
892                    .resolution(self.minor_resolution);
893            });
894    }
895}
896
897// tetrahedron
898
899impl<Config, Clear> GizmoPrimitive3d<Tetrahedron> for GizmoBuffer<Config, Clear>
900where
901    Config: GizmoConfigGroup,
902    Clear: 'static + Send + Sync,
903{
904    type Output<'a>
905        = ()
906    where
907        Self: 'a;
908
909    fn primitive_3d(
910        &mut self,
911        primitive: &Tetrahedron,
912        isometry: impl Into<Isometry3d>,
913        color: impl Into<Color>,
914    ) -> Self::Output<'_> {
915        if !self.enabled {
916            return;
917        }
918
919        let isometry = isometry.into();
920
921        let [a, b, c, d] = primitive.vertices.map(|vec3| isometry * vec3);
922
923        let lines = [(a, b), (a, c), (a, d), (b, c), (b, d), (c, d)];
924
925        let color = color.into();
926        lines.into_iter().for_each(|(start, end)| {
927            self.line(start, end, color);
928        });
929    }
930}