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