1use 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;
17const INFINITE_LEN: f32 = 10_000.0;
19
20pub trait GizmoPrimitive3d<P: Primitive3d> {
22 type Output<'a>
24 where
25 Self: 'a;
26
27 fn primitive_3d(
29 &mut self,
30 primitive: &P,
31 isometry: impl Into<Isometry3d>,
32 color: impl Into<Color>,
33 ) -> Self::Output<'_>;
34}
35
36impl<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
61impl<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
83pub struct Plane3dBuilder<'a, Config, Clear>
87where
88 Config: GizmoConfigGroup,
89 Clear: 'static + Send + Sync,
90{
91 gizmos: &'a mut GizmoBuffer<Config, Clear>,
92
93 normal: Dir3,
95
96 isometry: Isometry3d,
97 color: Color,
99
100 cell_count: UVec2,
102 spacing: Vec2,
104}
105
106impl<Config, Clear> Plane3dBuilder<'_, Config, Clear>
107where
108 Config: GizmoConfigGroup,
109 Clear: 'static + Send + Sync,
110{
111 pub fn cell_count(mut self, cell_count: UVec2) -> Self {
113 self.cell_count = cell_count;
114 self
115 }
116
117 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 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
174impl<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
209impl<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
236impl<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
266impl<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
294impl<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 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 let upper = [a, b, c, d]
335 .into_iter()
336 .zip([a, b, c, d].into_iter().cycle().skip(1));
337
338 let lower = [e, f, g, h]
340 .into_iter()
341 .zip([e, f, g, h].into_iter().cycle().skip(1));
342
343 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
356pub 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: f32,
368 half_height: f32,
370
371 isometry: Isometry3d,
372 color: Color,
374
375 resolution: u32,
377}
378
379impl<Config, Clear> Cylinder3dBuilder<'_, Config, Clear>
380where
381 Config: GizmoConfigGroup,
382 Clear: 'static + Send + Sync,
383{
384 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
442pub 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: f32,
454 half_length: f32,
456
457 isometry: Isometry3d,
458 color: Color,
460
461 resolution: u32,
463}
464
465impl<Config, Clear> Capsule3dBuilder<'_, Config, Clear>
466where
467 Config: GizmoConfigGroup,
468 Clear: 'static + Send + Sync,
469{
470 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
560pub 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: f32,
572 height: f32,
574
575 isometry: Isometry3d,
576 color: Color,
578
579 base_resolution: u32,
581
582 height_resolution: u32,
584}
585
586impl<Config, Clear> Cone3dBuilder<'_, Config, Clear>
587where
588 Config: GizmoConfigGroup,
589 Clear: 'static + Send + Sync,
590{
591 pub fn resolution(mut self, resolution: u32) -> Self {
593 self.base_resolution = resolution;
594 self.height_resolution = resolution;
595 self
596 }
597
598 pub fn base_resolution(mut self, resolution: u32) -> Self {
603 self.base_resolution = resolution;
604 self
605 }
606
607 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 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 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
682pub 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_top: f32,
694 radius_bottom: f32,
696 height: f32,
698
699 isometry: Isometry3d,
700 color: Color,
702
703 resolution: u32,
705}
706
707impl<Config, Clear> ConicalFrustum3dBuilder<'_, Config, Clear>
708where
709 Config: GizmoConfigGroup,
710 Clear: 'static + Send + Sync,
711{
712 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
780pub struct Torus3dBuilder<'a, Config, Clear>
784where
785 Config: GizmoConfigGroup,
786 Clear: 'static + Send + Sync,
787{
788 gizmos: &'a mut GizmoBuffer<Config, Clear>,
789
790 minor_radius: f32,
792 major_radius: f32,
794
795 isometry: Isometry3d,
796 color: Color,
798
799 minor_resolution: u32,
801 major_resolution: u32,
803}
804
805impl<Config, Clear> Torus3dBuilder<'_, Config, Clear>
806where
807 Config: GizmoConfigGroup,
808 Clear: 'static + Send + Sync,
809{
810 pub fn minor_resolution(mut self, minor_resolution: u32) -> Self {
812 self.minor_resolution = minor_resolution;
813 self
814 }
815
816 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 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
901impl<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}