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