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.linestrip([a, b, c, a].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
674            .windows(2)
675            .map(|win| (win[0], win[1]))
676            .for_each(|(start, end)| {
677                self.gizmos.line(start, end, self.color);
678            });
679    }
680}
681
682// conical frustum 3d
683
684/// Builder for configuring the drawing options of [`ConicalFrustum`].
685pub struct ConicalFrustum3dBuilder<'a, Config, Clear>
686where
687    Config: GizmoConfigGroup,
688    Clear: 'static + Send + Sync,
689{
690    gizmos: &'a mut GizmoBuffer<Config, Clear>,
691
692    // Radius of the top circle
693    radius_top: f32,
694    // Radius of the bottom circle
695    radius_bottom: f32,
696    // Height of the conical frustum
697    height: f32,
698
699    isometry: Isometry3d,
700    // Color of the conical frustum
701    color: Color,
702
703    // Number of lines used to approximate the curved surfaces
704    resolution: u32,
705}
706
707impl<Config, Clear> ConicalFrustum3dBuilder<'_, Config, Clear>
708where
709    Config: GizmoConfigGroup,
710    Clear: 'static + Send + Sync,
711{
712    /// Set the number of lines used to approximate the curved surfaces.
713    pub fn resolution(mut self, resolution: u32) -> Self {
714        self.resolution = resolution;
715        self
716    }
717}
718
719impl<Config, Clear> GizmoPrimitive3d<ConicalFrustum> for GizmoBuffer<Config, Clear>
720where
721    Config: GizmoConfigGroup,
722    Clear: 'static + Send + Sync,
723{
724    type Output<'a>
725        = ConicalFrustum3dBuilder<'a, Config, Clear>
726    where
727        Self: 'a;
728
729    fn primitive_3d(
730        &mut self,
731        primitive: &ConicalFrustum,
732        isometry: impl Into<Isometry3d>,
733        color: impl Into<Color>,
734    ) -> Self::Output<'_> {
735        ConicalFrustum3dBuilder {
736            gizmos: self,
737            radius_top: primitive.radius_top,
738            radius_bottom: primitive.radius_bottom,
739            height: primitive.height,
740            isometry: isometry.into(),
741            color: color.into(),
742            resolution: DEFAULT_RESOLUTION,
743        }
744    }
745}
746
747impl<Config, Clear> Drop for ConicalFrustum3dBuilder<'_, Config, Clear>
748where
749    Config: GizmoConfigGroup,
750    Clear: 'static + Send + Sync,
751{
752    fn drop(&mut self) {
753        if !self.gizmos.enabled {
754            return;
755        }
756
757        let half_height = self.height * 0.5;
758        let [upper_points, lower_points] = [(-1.0, self.radius_bottom), (1.0, self.radius_top)]
759            .map(|(sign, radius)| {
760                let translation = Vec3::Y * sign * half_height;
761                circle_coordinates_closed(radius, self.resolution)
762                    .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation)
763                    .map(|vec3| self.isometry * vec3)
764                    .collect::<Vec<_>>()
765            });
766
767        let upper_lines = upper_points.windows(2).map(|win| (win[0], win[1]));
768        let lower_lines = lower_points.windows(2).map(|win| (win[0], win[1]));
769        upper_lines.chain(lower_lines).for_each(|(start, end)| {
770            self.gizmos.line(start, end, self.color);
771        });
772
773        let connection_lines = upper_points.into_iter().zip(lower_points).skip(1);
774        connection_lines.for_each(|(start, end)| {
775            self.gizmos.line(start, end, self.color);
776        });
777    }
778}
779
780// torus 3d
781
782/// Builder for configuring the drawing options of [`Torus`].
783pub struct Torus3dBuilder<'a, Config, Clear>
784where
785    Config: GizmoConfigGroup,
786    Clear: 'static + Send + Sync,
787{
788    gizmos: &'a mut GizmoBuffer<Config, Clear>,
789
790    // Radius of the minor circle (tube)
791    minor_radius: f32,
792    // Radius of the major circle (ring)
793    major_radius: f32,
794
795    isometry: Isometry3d,
796    // Color of the torus
797    color: Color,
798
799    // Number of lines in the minor (tube) direction
800    minor_resolution: u32,
801    // Number of lines in the major (ring) direction
802    major_resolution: u32,
803}
804
805impl<Config, Clear> Torus3dBuilder<'_, Config, Clear>
806where
807    Config: GizmoConfigGroup,
808    Clear: 'static + Send + Sync,
809{
810    /// Set the number of lines in the minor (tube) direction.
811    pub fn minor_resolution(mut self, minor_resolution: u32) -> Self {
812        self.minor_resolution = minor_resolution;
813        self
814    }
815
816    /// Set the number of lines in the major (ring) direction.
817    pub fn major_resolution(mut self, major_resolution: u32) -> Self {
818        self.major_resolution = major_resolution;
819        self
820    }
821}
822
823impl<Config, Clear> GizmoPrimitive3d<Torus> for GizmoBuffer<Config, Clear>
824where
825    Config: GizmoConfigGroup,
826    Clear: 'static + Send + Sync,
827{
828    type Output<'a>
829        = Torus3dBuilder<'a, Config, Clear>
830    where
831        Self: 'a;
832
833    fn primitive_3d(
834        &mut self,
835        primitive: &Torus,
836        isometry: impl Into<Isometry3d>,
837        color: impl Into<Color>,
838    ) -> Self::Output<'_> {
839        Torus3dBuilder {
840            gizmos: self,
841            minor_radius: primitive.minor_radius,
842            major_radius: primitive.major_radius,
843            isometry: isometry.into(),
844            color: color.into(),
845            minor_resolution: DEFAULT_RESOLUTION,
846            major_resolution: DEFAULT_RESOLUTION,
847        }
848    }
849}
850
851impl<Config, Clear> Drop for Torus3dBuilder<'_, Config, Clear>
852where
853    Config: GizmoConfigGroup,
854    Clear: 'static + Send + Sync,
855{
856    fn drop(&mut self) {
857        if !self.gizmos.enabled {
858            return;
859        }
860
861        // draw 4 circles with major_radius
862        let [inner, outer, top, bottom] = [
863            (self.major_radius - self.minor_radius, 0.0),
864            (self.major_radius + self.minor_radius, 0.0),
865            (self.major_radius, self.minor_radius),
866            (self.major_radius, -self.minor_radius),
867        ]
868        .map(|(radius, height)| {
869            let translation = height * Vec3::Y;
870            circle_coordinates_closed(radius, self.major_resolution)
871                .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation)
872                .map(|vec3| self.isometry * vec3)
873                .collect::<Vec<_>>()
874        });
875
876        [&inner, &outer, &top, &bottom]
877            .iter()
878            .flat_map(|points| points.windows(2).map(|win| (win[0], win[1])))
879            .for_each(|(start, end)| {
880                self.gizmos.line(start, end, self.color);
881            });
882
883        inner
884            .into_iter()
885            .zip(top)
886            .zip(outer)
887            .zip(bottom)
888            .flat_map(|(((inner, top), outer), bottom)| {
889                let center = (inner + top + outer + bottom) * 0.25;
890                [(inner, top), (top, outer), (outer, bottom), (bottom, inner)]
891                    .map(|(start, end)| (start, end, center))
892            })
893            .for_each(|(from, to, center)| {
894                self.gizmos
895                    .short_arc_3d_between(center, from, to, self.color)
896                    .resolution(self.minor_resolution);
897            });
898    }
899}
900
901// tetrahedron
902
903impl<Config, Clear> GizmoPrimitive3d<Tetrahedron> for GizmoBuffer<Config, Clear>
904where
905    Config: GizmoConfigGroup,
906    Clear: 'static + Send + Sync,
907{
908    type Output<'a>
909        = ()
910    where
911        Self: 'a;
912
913    fn primitive_3d(
914        &mut self,
915        primitive: &Tetrahedron,
916        isometry: impl Into<Isometry3d>,
917        color: impl Into<Color>,
918    ) -> Self::Output<'_> {
919        if !self.enabled {
920            return;
921        }
922
923        let isometry = isometry.into();
924
925        let [a, b, c, d] = primitive.vertices.map(|vec3| isometry * vec3);
926
927        let lines = [(a, b), (a, c), (a, d), (b, c), (b, d), (c, d)];
928
929        let color = color.into();
930        lines.into_iter().for_each(|(start, end)| {
931            self.line(start, end, color);
932        });
933    }
934}