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