Skip to main content

bevy_gizmos/primitives/
dim3.rs

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