Skip to main content

bevy_gizmos/primitives/
dim2.rs

1//! A module for rendering each of the 2D [`bevy_shape`] primitives with [`GizmoBuffer`].
2
3use core::f32::consts::{FRAC_PI_2, PI};
4
5use super::helpers::*;
6
7use bevy_color::Color;
8use bevy_math::{Dir2, Isometry2d, Rot2, Vec2};
9use bevy_shape::{
10    Annulus, Arc2d, Capsule2d, Circle, CircularSector, CircularSegment, Ellipse, Line2d, Plane2d,
11    Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Rhombus, Segment2d, Triangle2d,
12};
13
14use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
15
16// some magic number since using directions as offsets will result in lines of length 1 pixel
17const MIN_LINE_LEN: f32 = 50.0;
18const HALF_MIN_LINE_LEN: f32 = 25.0;
19// length used to simulate infinite lines
20const INFINITE_LEN: f32 = 100_000.0;
21
22/// A trait for rendering 2D geometric primitives (`P`) with [`GizmoBuffer`].
23pub trait GizmoPrimitive2d<P: Primitive2d> {
24    /// The output of `primitive_2d`. This is a builder to set non-default values.
25    type Output<'a>
26    where
27        Self: 'a;
28
29    /// Renders a 2D primitive with its associated details.
30    fn primitive_2d(
31        &mut self,
32        primitive: &P,
33        isometry: impl Into<Isometry2d>,
34        color: impl Into<Color>,
35    ) -> Self::Output<'_>;
36}
37
38// direction 2d
39
40impl<Config, Clear> GizmoPrimitive2d<Dir2> for GizmoBuffer<Config, Clear>
41where
42    Config: GizmoConfigGroup,
43    Clear: 'static + Send + Sync,
44{
45    type Output<'a>
46        = ()
47    where
48        Self: 'a;
49
50    fn primitive_2d(
51        &mut self,
52        primitive: &Dir2,
53        isometry: impl Into<Isometry2d>,
54        color: impl Into<Color>,
55    ) -> Self::Output<'_> {
56        if !self.enabled {
57            return;
58        }
59        let isometry = isometry.into();
60        let start = Vec2::ZERO;
61        let end = *primitive * MIN_LINE_LEN;
62        self.arrow_2d(isometry * start, isometry * end, color);
63    }
64}
65
66// arc 2d
67
68impl<Config, Clear> GizmoPrimitive2d<Arc2d> for GizmoBuffer<Config, Clear>
69where
70    Config: GizmoConfigGroup,
71    Clear: 'static + Send + Sync,
72{
73    type Output<'a>
74        = ()
75    where
76        Self: 'a;
77
78    fn primitive_2d(
79        &mut self,
80        primitive: &Arc2d,
81        isometry: impl Into<Isometry2d>,
82        color: impl Into<Color>,
83    ) -> Self::Output<'_> {
84        if !self.enabled {
85            return;
86        }
87
88        let isometry = isometry.into();
89        let start_iso = isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.half_angle));
90
91        self.arc_2d(
92            start_iso,
93            primitive.half_angle * 2.0,
94            primitive.radius,
95            color,
96        );
97    }
98}
99
100// circle 2d
101
102impl<Config, Clear> GizmoPrimitive2d<Circle> for GizmoBuffer<Config, Clear>
103where
104    Config: GizmoConfigGroup,
105    Clear: 'static + Send + Sync,
106{
107    type Output<'a>
108        = crate::circles::Ellipse2dBuilder<'a, Config, Clear>
109    where
110        Self: 'a;
111
112    fn primitive_2d(
113        &mut self,
114        primitive: &Circle,
115        isometry: impl Into<Isometry2d>,
116        color: impl Into<Color>,
117    ) -> Self::Output<'_> {
118        self.circle_2d(isometry, primitive.radius, color)
119    }
120}
121
122// circular sector 2d
123
124impl<Config, Clear> GizmoPrimitive2d<CircularSector> for GizmoBuffer<Config, Clear>
125where
126    Config: GizmoConfigGroup,
127    Clear: 'static + Send + Sync,
128{
129    type Output<'a>
130        = ()
131    where
132        Self: 'a;
133
134    fn primitive_2d(
135        &mut self,
136        primitive: &CircularSector,
137        isometry: impl Into<Isometry2d>,
138        color: impl Into<Color>,
139    ) -> Self::Output<'_> {
140        if !self.enabled {
141            return;
142        }
143
144        let isometry = isometry.into();
145        let color = color.into();
146
147        let start_iso =
148            isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle));
149        let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle));
150
151        // we need to draw the arc part of the sector, and the two lines connecting the arc and the center
152        self.arc_2d(
153            start_iso,
154            primitive.arc.half_angle * 2.0,
155            primitive.arc.radius,
156            color,
157        );
158
159        let end_position = primitive.arc.radius * Vec2::Y;
160        self.line_2d(isometry * Vec2::ZERO, start_iso * end_position, color);
161        self.line_2d(isometry * Vec2::ZERO, end_iso * end_position, color);
162    }
163}
164
165// circular segment 2d
166
167impl<Config, Clear> GizmoPrimitive2d<CircularSegment> for GizmoBuffer<Config, Clear>
168where
169    Config: GizmoConfigGroup,
170    Clear: 'static + Send + Sync,
171{
172    type Output<'a>
173        = ()
174    where
175        Self: 'a;
176
177    fn primitive_2d(
178        &mut self,
179        primitive: &CircularSegment,
180        isometry: impl Into<Isometry2d>,
181        color: impl Into<Color>,
182    ) -> Self::Output<'_> {
183        if !self.enabled {
184            return;
185        }
186
187        let isometry = isometry.into();
188        let color = color.into();
189
190        let start_iso =
191            isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle));
192        let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle));
193
194        // we need to draw the arc part of the segment, and the line connecting the two ends
195        self.arc_2d(
196            start_iso,
197            primitive.arc.half_angle * 2.0,
198            primitive.arc.radius,
199            color,
200        );
201
202        let position = primitive.arc.radius * Vec2::Y;
203        self.line_2d(start_iso * position, end_iso * position, color);
204    }
205}
206
207// ellipse 2d
208
209impl<Config, Clear> GizmoPrimitive2d<Ellipse> for GizmoBuffer<Config, Clear>
210where
211    Config: GizmoConfigGroup,
212    Clear: 'static + Send + Sync,
213{
214    type Output<'a>
215        = crate::circles::Ellipse2dBuilder<'a, Config, Clear>
216    where
217        Self: 'a;
218
219    fn primitive_2d<'a>(
220        &mut self,
221        primitive: &Ellipse,
222        isometry: impl Into<Isometry2d>,
223        color: impl Into<Color>,
224    ) -> Self::Output<'_> {
225        self.ellipse_2d(isometry, primitive.half_size, color)
226    }
227}
228
229// annulus 2d
230
231/// Builder for configuring the drawing options of [`Annulus`].
232pub struct Annulus2dBuilder<'a, Config, Clear>
233where
234    Config: GizmoConfigGroup,
235    Clear: 'static + Send + Sync,
236{
237    gizmos: &'a mut GizmoBuffer<Config, Clear>,
238    isometry: Isometry2d,
239    inner_radius: f32,
240    outer_radius: f32,
241    color: Color,
242    inner_resolution: u32,
243    outer_resolution: u32,
244}
245
246impl<Config, Clear> Annulus2dBuilder<'_, Config, Clear>
247where
248    Config: GizmoConfigGroup,
249    Clear: 'static + Send + Sync,
250{
251    /// Set the number of line-segments for each circle of the annulus.
252    pub fn resolution(mut self, resolution: u32) -> Self {
253        self.outer_resolution = resolution;
254        self.inner_resolution = resolution;
255        self
256    }
257
258    /// Set the number of line-segments for the outer circle of the annulus.
259    pub fn outer_resolution(mut self, resolution: u32) -> Self {
260        self.outer_resolution = resolution;
261        self
262    }
263
264    /// Set the number of line-segments for the inner circle of the annulus.
265    pub fn inner_resolution(mut self, resolution: u32) -> Self {
266        self.inner_resolution = resolution;
267        self
268    }
269}
270
271impl<Config, Clear> GizmoPrimitive2d<Annulus> for GizmoBuffer<Config, Clear>
272where
273    Config: GizmoConfigGroup,
274    Clear: 'static + Send + Sync,
275{
276    type Output<'a>
277        = Annulus2dBuilder<'a, Config, Clear>
278    where
279        Self: 'a;
280
281    fn primitive_2d(
282        &mut self,
283        primitive: &Annulus,
284        isometry: impl Into<Isometry2d>,
285        color: impl Into<Color>,
286    ) -> Self::Output<'_> {
287        Annulus2dBuilder {
288            gizmos: self,
289            isometry: isometry.into(),
290            inner_radius: primitive.inner_circle.radius,
291            outer_radius: primitive.outer_circle.radius,
292            color: color.into(),
293            inner_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,
294            outer_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,
295        }
296    }
297}
298
299impl<Config, Clear> Drop for Annulus2dBuilder<'_, Config, Clear>
300where
301    Config: GizmoConfigGroup,
302    Clear: 'static + Send + Sync,
303{
304    fn drop(&mut self) {
305        if !self.gizmos.enabled {
306            return;
307        }
308
309        let Annulus2dBuilder {
310            gizmos,
311            isometry,
312            inner_radius,
313            outer_radius,
314            inner_resolution,
315            outer_resolution,
316            color,
317            ..
318        } = self;
319
320        gizmos
321            .circle_2d(*isometry, *outer_radius, *color)
322            .resolution(*outer_resolution);
323        gizmos
324            .circle_2d(*isometry, *inner_radius, *color)
325            .resolution(*inner_resolution);
326    }
327}
328
329// rhombus 2d
330
331impl<Config, Clear> GizmoPrimitive2d<Rhombus> for GizmoBuffer<Config, Clear>
332where
333    Config: GizmoConfigGroup,
334    Clear: 'static + Send + Sync,
335{
336    type Output<'a>
337        = ()
338    where
339        Self: 'a;
340
341    fn primitive_2d(
342        &mut self,
343        primitive: &Rhombus,
344        isometry: impl Into<Isometry2d>,
345        color: impl Into<Color>,
346    ) -> Self::Output<'_> {
347        if !self.enabled {
348            return;
349        };
350        let isometry = isometry.into();
351        let [a, b, c, d] =
352            [(1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), (0.0, -1.0)].map(|(sign_x, sign_y)| {
353                Vec2::new(
354                    primitive.half_diagonals.x * sign_x,
355                    primitive.half_diagonals.y * sign_y,
356                )
357            });
358        let positions = [a, b, c, d].map(|vec2| isometry * vec2);
359        self.lineloop_2d(positions, color);
360    }
361}
362
363// capsule 2d
364
365impl<Config, Clear> GizmoPrimitive2d<Capsule2d> for GizmoBuffer<Config, Clear>
366where
367    Config: GizmoConfigGroup,
368    Clear: 'static + Send + Sync,
369{
370    type Output<'a>
371        = ()
372    where
373        Self: 'a;
374
375    fn primitive_2d(
376        &mut self,
377        primitive: &Capsule2d,
378        isometry: impl Into<Isometry2d>,
379        color: impl Into<Color>,
380    ) -> Self::Output<'_> {
381        let isometry = isometry.into();
382        let polymorphic_color: Color = color.into();
383
384        if !self.enabled {
385            return;
386        }
387
388        // transform points from the reference unit square to capsule "rectangle"
389        let [top_left, top_right, bottom_left, bottom_right, top_center, bottom_center] = [
390            [-1.0, 1.0],
391            [1.0, 1.0],
392            [-1.0, -1.0],
393            [1.0, -1.0],
394            // just reuse the pipeline for these points as well
395            [0.0, 1.0],
396            [0.0, -1.0],
397        ]
398        .map(|[sign_x, sign_y]| Vec2::X * sign_x + Vec2::Y * sign_y)
399        .map(|reference_point| {
400            let scaling = Vec2::X * primitive.radius + Vec2::Y * primitive.half_length;
401            reference_point * scaling
402        })
403        .map(|vec2| isometry * vec2);
404
405        // draw left and right side of capsule "rectangle"
406        self.line_2d(bottom_left, top_left, polymorphic_color);
407        self.line_2d(bottom_right, top_right, polymorphic_color);
408
409        let start_angle_top = isometry.rotation.as_radians() - FRAC_PI_2;
410        let start_angle_bottom = isometry.rotation.as_radians() + FRAC_PI_2;
411
412        // draw arcs
413        self.arc_2d(
414            Isometry2d::new(top_center, Rot2::radians(start_angle_top)),
415            PI,
416            primitive.radius,
417            polymorphic_color,
418        );
419        self.arc_2d(
420            Isometry2d::new(bottom_center, Rot2::radians(start_angle_bottom)),
421            PI,
422            primitive.radius,
423            polymorphic_color,
424        );
425    }
426}
427
428// line 2d
429//
430/// Builder for configuring the drawing options of [`Line2d`].
431pub struct Line2dBuilder<'a, Config, Clear>
432where
433    Config: GizmoConfigGroup,
434    Clear: 'static + Send + Sync,
435{
436    gizmos: &'a mut GizmoBuffer<Config, Clear>,
437
438    direction: Dir2, // Direction of the line
439
440    isometry: Isometry2d,
441    color: Color, // color of the line
442
443    draw_arrow: bool, // decides whether to indicate the direction of the line with an arrow
444}
445
446impl<Config, Clear> Line2dBuilder<'_, Config, Clear>
447where
448    Config: GizmoConfigGroup,
449    Clear: 'static + Send + Sync,
450{
451    /// Set the drawing mode of the line (arrow vs. plain line)
452    pub fn draw_arrow(mut self, is_enabled: bool) -> Self {
453        self.draw_arrow = is_enabled;
454        self
455    }
456}
457
458impl<Config, Clear> GizmoPrimitive2d<Line2d> for GizmoBuffer<Config, Clear>
459where
460    Config: GizmoConfigGroup,
461    Clear: 'static + Send + Sync,
462{
463    type Output<'a>
464        = Line2dBuilder<'a, Config, Clear>
465    where
466        Self: 'a;
467
468    fn primitive_2d(
469        &mut self,
470        primitive: &Line2d,
471        isometry: impl Into<Isometry2d>,
472        color: impl Into<Color>,
473    ) -> Self::Output<'_> {
474        Line2dBuilder {
475            gizmos: self,
476            direction: primitive.direction,
477            isometry: isometry.into(),
478            color: color.into(),
479            draw_arrow: false,
480        }
481    }
482}
483
484impl<Config, Clear> Drop for Line2dBuilder<'_, Config, Clear>
485where
486    Config: GizmoConfigGroup,
487    Clear: 'static + Send + Sync,
488{
489    fn drop(&mut self) {
490        if !self.gizmos.enabled {
491            return;
492        }
493
494        let [start, end] = [1.0, -1.0]
495            .map(|sign| sign * INFINITE_LEN)
496            // offset the line from the origin infinitely into the given direction
497            .map(|length| self.direction * length)
498            // transform the line with the given isometry
499            .map(|offset| self.isometry * offset);
500
501        self.gizmos.line_2d(start, end, self.color);
502
503        // optionally draw an arrow head at the center of the line
504        if self.draw_arrow {
505            self.gizmos.arrow_2d(
506                self.isometry * (-self.direction * MIN_LINE_LEN),
507                self.isometry * Vec2::ZERO,
508                self.color,
509            );
510        }
511    }
512}
513
514// plane 2d
515
516impl<Config, Clear> GizmoPrimitive2d<Plane2d> for GizmoBuffer<Config, Clear>
517where
518    Config: GizmoConfigGroup,
519    Clear: 'static + Send + Sync,
520{
521    type Output<'a>
522        = ()
523    where
524        Self: 'a;
525
526    fn primitive_2d(
527        &mut self,
528        primitive: &Plane2d,
529        isometry: impl Into<Isometry2d>,
530        color: impl Into<Color>,
531    ) -> Self::Output<'_> {
532        let isometry = isometry.into();
533        let polymorphic_color: Color = color.into();
534
535        if !self.enabled {
536            return;
537        }
538        // draw normal of the plane (orthogonal to the plane itself)
539        let normal = primitive.normal;
540        let normal_segment = Segment2d::from_direction_and_length(normal, HALF_MIN_LINE_LEN * 2.);
541        self.primitive_2d(
542            &normal_segment,
543            // offset the normal so it starts on the plane line
544            Isometry2d::new(isometry * (HALF_MIN_LINE_LEN * normal), isometry.rotation),
545            polymorphic_color,
546        )
547        .draw_arrow(true);
548
549        // draw the plane line
550        let direction = -normal.perpendicular();
551
552        self.primitive_2d(&Line2d { direction }, isometry, polymorphic_color)
553            .draw_arrow(false);
554
555        // draw an arrow such that the normal is always left side of the plane with respect to the
556        // planes direction. This is to follow the "counter-clockwise" convention
557        self.arrow_2d(
558            isometry * Vec2::ZERO,
559            isometry * (MIN_LINE_LEN * direction),
560            polymorphic_color,
561        );
562    }
563}
564
565// segment 2d
566
567/// Builder for configuring the drawing options of [`Segment2d`].
568pub struct Segment2dBuilder<'a, Config, Clear>
569where
570    Config: GizmoConfigGroup,
571    Clear: 'static + Send + Sync,
572{
573    gizmos: &'a mut GizmoBuffer<Config, Clear>,
574
575    point1: Vec2, // First point of the segment
576    point2: Vec2, // Second point of the segment
577
578    isometry: Isometry2d, // isometric transformation of the line segment
579    color: Color,         // color of the line segment
580
581    draw_arrow: bool, // decides whether to draw just a line or an arrow
582}
583
584impl<Config, Clear> Segment2dBuilder<'_, Config, Clear>
585where
586    Config: GizmoConfigGroup,
587    Clear: 'static + Send + Sync,
588{
589    /// Set the drawing mode of the line (arrow vs. plain line)
590    pub fn draw_arrow(mut self, is_enabled: bool) -> Self {
591        self.draw_arrow = is_enabled;
592        self
593    }
594}
595
596impl<Config, Clear> GizmoPrimitive2d<Segment2d> for GizmoBuffer<Config, Clear>
597where
598    Config: GizmoConfigGroup,
599    Clear: 'static + Send + Sync,
600{
601    type Output<'a>
602        = Segment2dBuilder<'a, Config, Clear>
603    where
604        Self: 'a;
605
606    fn primitive_2d(
607        &mut self,
608        primitive: &Segment2d,
609        isometry: impl Into<Isometry2d>,
610        color: impl Into<Color>,
611    ) -> Self::Output<'_> {
612        Segment2dBuilder {
613            gizmos: self,
614            point1: primitive.point1(),
615            point2: primitive.point2(),
616
617            isometry: isometry.into(),
618            color: color.into(),
619
620            draw_arrow: Default::default(),
621        }
622    }
623}
624
625impl<Config, Clear> Drop for Segment2dBuilder<'_, Config, Clear>
626where
627    Config: GizmoConfigGroup,
628    Clear: 'static + Send + Sync,
629{
630    fn drop(&mut self) {
631        if !self.gizmos.enabled {
632            return;
633        }
634
635        let segment = Segment2d::new(self.point1, self.point2).transformed(self.isometry);
636
637        if self.draw_arrow {
638            self.gizmos
639                .arrow_2d(segment.point1(), segment.point2(), self.color);
640        } else {
641            self.gizmos
642                .line_2d(segment.point1(), segment.point2(), self.color);
643        }
644    }
645}
646
647// polyline 2d
648
649impl<Config, Clear> GizmoPrimitive2d<Polyline2d> for GizmoBuffer<Config, Clear>
650where
651    Config: GizmoConfigGroup,
652    Clear: 'static + Send + Sync,
653{
654    type Output<'a>
655        = ()
656    where
657        Self: 'a;
658
659    fn primitive_2d(
660        &mut self,
661        primitive: &Polyline2d,
662        isometry: impl Into<Isometry2d>,
663        color: impl Into<Color>,
664    ) -> Self::Output<'_> {
665        if !self.enabled {
666            return;
667        }
668
669        let isometry = isometry.into();
670
671        self.linestrip_2d(
672            primitive
673                .vertices
674                .iter()
675                .copied()
676                .map(|vec2| isometry * vec2),
677            color,
678        );
679    }
680}
681
682// triangle 2d
683
684impl<Config, Clear> GizmoPrimitive2d<Triangle2d> for GizmoBuffer<Config, Clear>
685where
686    Config: GizmoConfigGroup,
687    Clear: 'static + Send + Sync,
688{
689    type Output<'a>
690        = ()
691    where
692        Self: 'a;
693
694    fn primitive_2d(
695        &mut self,
696        primitive: &Triangle2d,
697        isometry: impl Into<Isometry2d>,
698        color: impl Into<Color>,
699    ) -> Self::Output<'_> {
700        if !self.enabled {
701            return;
702        }
703
704        let isometry = isometry.into();
705
706        let [a, b, c] = primitive.vertices;
707        let positions = [a, b, c].map(|vec2| isometry * vec2);
708        self.lineloop_2d(positions, color);
709    }
710}
711
712// rectangle 2d
713
714impl<Config, Clear> GizmoPrimitive2d<Rectangle> for GizmoBuffer<Config, Clear>
715where
716    Config: GizmoConfigGroup,
717    Clear: 'static + Send + Sync,
718{
719    type Output<'a>
720        = ()
721    where
722        Self: 'a;
723
724    fn primitive_2d(
725        &mut self,
726        primitive: &Rectangle,
727        isometry: impl Into<Isometry2d>,
728        color: impl Into<Color>,
729    ) -> Self::Output<'_> {
730        if !self.enabled {
731            return;
732        }
733
734        let isometry = isometry.into();
735
736        let [a, b, c, d] =
737            [(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0)].map(|(sign_x, sign_y)| {
738                Vec2::new(
739                    primitive.half_size.x * sign_x,
740                    primitive.half_size.y * sign_y,
741                )
742            });
743        let positions = [a, b, c, d].map(|vec2| isometry * vec2);
744        self.lineloop_2d(positions, color);
745    }
746}
747
748// polygon 2d
749
750impl<Config, Clear> GizmoPrimitive2d<Polygon> for GizmoBuffer<Config, Clear>
751where
752    Config: GizmoConfigGroup,
753    Clear: 'static + Send + Sync,
754{
755    type Output<'a>
756        = ()
757    where
758        Self: 'a;
759
760    fn primitive_2d(
761        &mut self,
762        primitive: &Polygon,
763        isometry: impl Into<Isometry2d>,
764        color: impl Into<Color>,
765    ) -> Self::Output<'_> {
766        if !self.enabled {
767            return;
768        }
769
770        let isometry = isometry.into();
771
772        let vertices = if primitive.vertices.first() == primitive.vertices.last() {
773            // Strip closing point if there is one
774            &primitive.vertices[..primitive.vertices.len() - 1]
775        } else {
776            &primitive.vertices[..]
777        };
778
779        self.lineloop_2d(vertices.iter().map(|&vec2| isometry * vec2), color);
780    }
781}
782
783// regular polygon 2d
784
785impl<Config, Clear> GizmoPrimitive2d<RegularPolygon> for GizmoBuffer<Config, Clear>
786where
787    Config: GizmoConfigGroup,
788    Clear: 'static + Send + Sync,
789{
790    type Output<'a>
791        = ()
792    where
793        Self: 'a;
794
795    fn primitive_2d(
796        &mut self,
797        primitive: &RegularPolygon,
798        isometry: impl Into<Isometry2d>,
799        color: impl Into<Color>,
800    ) -> Self::Output<'_> {
801        if !self.enabled {
802            return;
803        }
804
805        let isometry = isometry.into();
806
807        let points = (0..primitive.sides)
808            .map(|n| single_circle_coordinate(primitive.circumcircle.radius, primitive.sides, n))
809            .map(|vec2| isometry * vec2);
810        self.lineloop_2d(points, color);
811    }
812}