1use std::collections::HashMap;
73
74use crate::renderer::{LabelItem, PointCloudItem, PolylineItem};
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
82pub enum DebugLayer {
83 #[default]
87 Dev,
88 Overlay,
91}
92
93#[derive(Debug, Clone)]
99pub enum DebugPrim {
100 Line {
102 start: glam::Vec3,
104 end: glam::Vec3,
106 colour: [f32; 4],
108 layer: DebugLayer,
110 },
111 Point {
113 position: glam::Vec3,
115 radius: f32,
117 colour: [f32; 4],
119 layer: DebugLayer,
121 },
122 Aabb {
124 min: glam::Vec3,
126 max: glam::Vec3,
128 colour: [f32; 4],
130 layer: DebugLayer,
132 },
133 Sphere {
135 center: glam::Vec3,
137 radius: f32,
139 colour: [f32; 4],
141 layer: DebugLayer,
143 },
144 Label {
146 position: glam::Vec3,
148 text: String,
150 colour: [f32; 4],
152 layer: DebugLayer,
154 },
155}
156
157impl DebugPrim {
158 pub fn layer(&self) -> DebugLayer {
160 match self {
161 DebugPrim::Line { layer, .. }
162 | DebugPrim::Point { layer, .. }
163 | DebugPrim::Aabb { layer, .. }
164 | DebugPrim::Sphere { layer, .. }
165 | DebugPrim::Label { layer, .. } => *layer,
166 }
167 }
168}
169
170pub struct DebugDraw {
184 pub enabled: bool,
186 pub dev_enabled: bool,
192
193 transient: Vec<DebugPrim>,
194 persistent: HashMap<u64, DebugPrim>,
195}
196
197impl Default for DebugDraw {
198 fn default() -> Self {
199 Self::new()
200 }
201}
202
203impl DebugDraw {
204 pub fn new() -> Self {
206 Self {
207 enabled: true,
208 dev_enabled: true,
209 transient: Vec::new(),
210 persistent: HashMap::new(),
211 }
212 }
213
214 pub fn overlay_only() -> Self {
219 Self {
220 enabled: true,
221 dev_enabled: false,
222 transient: Vec::new(),
223 persistent: HashMap::new(),
224 }
225 }
226
227 pub fn begin_frame(&mut self) {
236 self.transient.clear();
237 }
238
239 fn accept(&self, layer: DebugLayer) -> bool {
242 self.enabled && (layer == DebugLayer::Overlay || self.dev_enabled)
243 }
244
245 pub fn line(&mut self, start: glam::Vec3, end: glam::Vec3, colour: [f32; 4]) -> &mut Self {
250 self.line_layer(start, end, colour, DebugLayer::Dev)
251 }
252
253 pub fn line_overlay(
255 &mut self,
256 start: glam::Vec3,
257 end: glam::Vec3,
258 colour: [f32; 4],
259 ) -> &mut Self {
260 self.line_layer(start, end, colour, DebugLayer::Overlay)
261 }
262
263 pub fn line_layer(
265 &mut self,
266 start: glam::Vec3,
267 end: glam::Vec3,
268 colour: [f32; 4],
269 layer: DebugLayer,
270 ) -> &mut Self {
271 if self.accept(layer) {
272 self.transient.push(DebugPrim::Line { start, end, colour, layer });
273 }
274 self
275 }
276
277 pub fn point(
281 &mut self,
282 position: glam::Vec3,
283 radius: f32,
284 colour: [f32; 4],
285 ) -> &mut Self {
286 self.point_layer(position, radius, colour, DebugLayer::Dev)
287 }
288
289 pub fn point_overlay(
291 &mut self,
292 position: glam::Vec3,
293 radius: f32,
294 colour: [f32; 4],
295 ) -> &mut Self {
296 self.point_layer(position, radius, colour, DebugLayer::Overlay)
297 }
298
299 pub fn point_layer(
301 &mut self,
302 position: glam::Vec3,
303 radius: f32,
304 colour: [f32; 4],
305 layer: DebugLayer,
306 ) -> &mut Self {
307 if self.accept(layer) {
308 self.transient.push(DebugPrim::Point { position, radius, colour, layer });
309 }
310 self
311 }
312
313 pub fn aabb(
315 &mut self,
316 min: glam::Vec3,
317 max: glam::Vec3,
318 colour: [f32; 4],
319 ) -> &mut Self {
320 self.aabb_layer(min, max, colour, DebugLayer::Dev)
321 }
322
323 pub fn aabb_overlay(
325 &mut self,
326 min: glam::Vec3,
327 max: glam::Vec3,
328 colour: [f32; 4],
329 ) -> &mut Self {
330 self.aabb_layer(min, max, colour, DebugLayer::Overlay)
331 }
332
333 pub fn aabb_layer(
335 &mut self,
336 min: glam::Vec3,
337 max: glam::Vec3,
338 colour: [f32; 4],
339 layer: DebugLayer,
340 ) -> &mut Self {
341 if self.accept(layer) {
342 self.transient.push(DebugPrim::Aabb { min, max, colour, layer });
343 }
344 self
345 }
346
347 pub fn sphere(
351 &mut self,
352 center: glam::Vec3,
353 radius: f32,
354 colour: [f32; 4],
355 ) -> &mut Self {
356 self.sphere_layer(center, radius, colour, DebugLayer::Dev)
357 }
358
359 pub fn sphere_overlay(
361 &mut self,
362 center: glam::Vec3,
363 radius: f32,
364 colour: [f32; 4],
365 ) -> &mut Self {
366 self.sphere_layer(center, radius, colour, DebugLayer::Overlay)
367 }
368
369 pub fn sphere_layer(
371 &mut self,
372 center: glam::Vec3,
373 radius: f32,
374 colour: [f32; 4],
375 layer: DebugLayer,
376 ) -> &mut Self {
377 if self.accept(layer) {
378 self.transient.push(DebugPrim::Sphere { center, radius, colour, layer });
379 }
380 self
381 }
382
383 pub fn label(
385 &mut self,
386 position: glam::Vec3,
387 text: impl Into<String>,
388 colour: [f32; 4],
389 ) -> &mut Self {
390 self.label_layer(position, text, colour, DebugLayer::Dev)
391 }
392
393 pub fn label_overlay(
395 &mut self,
396 position: glam::Vec3,
397 text: impl Into<String>,
398 colour: [f32; 4],
399 ) -> &mut Self {
400 self.label_layer(position, text, colour, DebugLayer::Overlay)
401 }
402
403 pub fn label_layer(
405 &mut self,
406 position: glam::Vec3,
407 text: impl Into<String>,
408 colour: [f32; 4],
409 layer: DebugLayer,
410 ) -> &mut Self {
411 if self.accept(layer) {
412 self.transient.push(DebugPrim::Label {
413 position,
414 text: text.into(),
415 colour,
416 layer,
417 });
418 }
419 self
420 }
421
422 pub fn add_persistent(&mut self, id: u64, prim: DebugPrim) {
435 self.persistent.insert(id, prim);
436 }
437
438 pub fn remove_persistent(&mut self, id: u64) -> Option<DebugPrim> {
442 self.persistent.remove(&id)
443 }
444
445 pub fn has_persistent(&self, id: u64) -> bool {
447 self.persistent.contains_key(&id)
448 }
449
450 pub fn clear_persistent(&mut self) {
452 self.persistent.clear();
453 }
454
455 pub fn prims(&self) -> impl Iterator<Item = &DebugPrim> {
464 self.transient.iter().chain(self.persistent.values())
465 }
466
467 pub fn transient_count(&self) -> usize {
469 self.transient.len()
470 }
471
472 pub fn persistent_count(&self) -> usize {
474 self.persistent.len()
475 }
476
477 pub fn is_empty(&self) -> bool {
479 self.transient.is_empty() && self.persistent.is_empty()
480 }
481
482 pub fn to_polylines(&self) -> Vec<PolylineItem> {
490 if !self.enabled {
491 return Vec::new();
492 }
493
494 let mut groups: HashMap<u32, (Vec<[f32; 3]>, Vec<u32>, Vec<[f32; 4]>)> = HashMap::new();
496
497 for prim in self.prims() {
498 if prim.layer() == DebugLayer::Dev && !self.dev_enabled {
499 continue;
500 }
501 match prim {
502 DebugPrim::Line { start, end, colour, .. } => {
503 let key = colour_key(*colour);
504 let entry = groups.entry(key).or_default();
505 entry.0.push((*start).into());
506 entry.0.push((*end).into());
507 entry.1.push(2);
508 entry.2.push(*colour);
509 entry.2.push(*colour);
510 }
511 DebugPrim::Aabb { min, max, colour, .. } => {
512 let key = colour_key(*colour);
513 let entry = groups.entry(key).or_default();
514 let (positions, strips, colours) = (&mut entry.0, &mut entry.1, &mut entry.2);
515 let mn = *min;
517 let mx = *max;
518 let aabb_positions: &[[f32; 3]] = &[
519 [mn.x, mn.y, mn.z],
521 [mx.x, mn.y, mn.z],
522 [mx.x, mx.y, mn.z],
523 [mn.x, mx.y, mn.z],
524 [mn.x, mn.y, mn.z],
525 [mn.x, mn.y, mx.z],
527 [mx.x, mn.y, mx.z],
528 [mx.x, mx.y, mx.z],
529 [mn.x, mx.y, mx.z],
530 [mn.x, mn.y, mx.z],
531 [mn.x, mn.y, mn.z],
533 [mn.x, mn.y, mx.z],
534 [mx.x, mn.y, mn.z],
535 [mx.x, mn.y, mx.z],
536 [mx.x, mx.y, mn.z],
537 [mx.x, mx.y, mx.z],
538 [mn.x, mx.y, mn.z],
539 [mn.x, mx.y, mx.z],
540 ];
541 let strip_lens: &[u32] = &[5, 5, 2, 2, 2, 2];
542 for &p in aabb_positions {
543 positions.push(p);
544 colours.push(*colour);
545 }
546 strips.extend_from_slice(strip_lens);
547 }
548 DebugPrim::Sphere { center, radius, colour, .. } => {
549 let key = colour_key(*colour);
550 let entry = groups.entry(key).or_default();
551 let (positions, strips, colours) = (&mut entry.0, &mut entry.1, &mut entry.2);
552 const SEGS: usize = 32;
553 for circle_axis in 0..3_usize {
555 for i in 0..=SEGS {
556 let t = i as f32 / SEGS as f32 * std::f32::consts::TAU;
557 let (s, c) = (t.sin(), t.cos());
558 let p = match circle_axis {
559 0 => glam::Vec3::new(c * *radius + center.x, s * *radius + center.y, center.z),
560 1 => glam::Vec3::new(c * *radius + center.x, center.y, s * *radius + center.z),
561 _ => glam::Vec3::new(center.x, c * *radius + center.y, s * *radius + center.z),
562 };
563 positions.push(p.into());
564 colours.push(*colour);
565 }
566 strips.push((SEGS + 1) as u32);
567 }
568 }
569 _ => {} }
571 }
572
573 groups
574 .into_values()
575 .filter(|(positions, _, _)| !positions.is_empty())
576 .map(|(positions, strip_lengths, node_colours)| PolylineItem {
577 positions,
578 strip_lengths,
579 node_colours,
580 line_width: 1.5,
581 ..PolylineItem::default()
582 })
583 .collect()
584 }
585
586 pub fn to_point_cloud(&self) -> Option<PointCloudItem> {
591 if !self.enabled {
592 return None;
593 }
594
595 let mut positions = Vec::new();
596 let mut colours = Vec::new();
597 let mut radii = Vec::new();
598
599 for prim in self.prims() {
600 if prim.layer() == DebugLayer::Dev && !self.dev_enabled {
601 continue;
602 }
603 if let DebugPrim::Point { position, radius, colour, .. } = prim {
604 positions.push((*position).into());
605 colours.push(*colour);
606 radii.push(*radius);
607 }
608 }
609
610 if positions.is_empty() {
611 return None;
612 }
613
614 Some(PointCloudItem {
615 positions,
616 colours,
617 radii,
618 ..PointCloudItem::default()
619 })
620 }
621
622 pub fn to_labels(&self) -> Vec<LabelItem> {
627 if !self.enabled {
628 return Vec::new();
629 }
630
631 let mut out = Vec::new();
632 for prim in self.prims() {
633 if prim.layer() == DebugLayer::Dev && !self.dev_enabled {
634 continue;
635 }
636 if let DebugPrim::Label { position, text, colour, .. } = prim {
637 out.push(LabelItem {
638 world_anchor: Some((*position).into()),
639 text: text.clone(),
640 colour: *colour,
641 leader_line: true,
642 font_size: 12.0,
643 ..LabelItem::default()
644 });
645 }
646 }
647 out
648 }
649}
650
651fn colour_key(c: [f32; 4]) -> u32 {
657 let r = (c[0].clamp(0.0, 1.0) * 255.0).round() as u32;
658 let g = (c[1].clamp(0.0, 1.0) * 255.0).round() as u32;
659 let b = (c[2].clamp(0.0, 1.0) * 255.0).round() as u32;
660 let a = (c[3].clamp(0.0, 1.0) * 255.0).round() as u32;
661 (r << 24) | (g << 16) | (b << 8) | a
662}
663
664#[cfg(test)]
669mod tests {
670 use super::*;
671
672 fn red() -> [f32; 4] { [1.0, 0.0, 0.0, 1.0] }
673 fn green() -> [f32; 4] { [0.0, 1.0, 0.0, 1.0] }
674
675 #[test]
678 fn test_line_accumulates() {
679 let mut dd = DebugDraw::new();
680 dd.line(glam::Vec3::ZERO, glam::Vec3::X, red());
681 assert_eq!(dd.transient_count(), 1);
682 assert!(matches!(dd.prims().next(), Some(DebugPrim::Line { .. })));
683 }
684
685 #[test]
686 fn test_point_accumulates() {
687 let mut dd = DebugDraw::new();
688 dd.point(glam::Vec3::Y, 4.0, green());
689 assert_eq!(dd.transient_count(), 1);
690 assert!(matches!(dd.prims().next(), Some(DebugPrim::Point { .. })));
691 }
692
693 #[test]
694 fn test_aabb_accumulates() {
695 let mut dd = DebugDraw::new();
696 dd.aabb(glam::Vec3::NEG_ONE, glam::Vec3::ONE, red());
697 assert_eq!(dd.transient_count(), 1);
698 }
699
700 #[test]
701 fn test_sphere_accumulates() {
702 let mut dd = DebugDraw::new();
703 dd.sphere(glam::Vec3::ZERO, 1.0, green());
704 assert_eq!(dd.transient_count(), 1);
705 }
706
707 #[test]
708 fn test_label_accumulates() {
709 let mut dd = DebugDraw::new();
710 dd.label(glam::Vec3::ZERO, "hello", red());
711 assert_eq!(dd.transient_count(), 1);
712 assert!(matches!(dd.prims().next(), Some(DebugPrim::Label { .. })));
713 }
714
715 #[test]
716 fn test_multiple_prims_accumulate() {
717 let mut dd = DebugDraw::new();
718 dd.line(glam::Vec3::ZERO, glam::Vec3::X, red());
719 dd.line(glam::Vec3::ZERO, glam::Vec3::Y, green());
720 dd.point(glam::Vec3::Z, 5.0, red());
721 assert_eq!(dd.transient_count(), 3);
722 }
723
724 #[test]
727 fn test_begin_frame_clears_transient() {
728 let mut dd = DebugDraw::new();
729 dd.line(glam::Vec3::ZERO, glam::Vec3::X, red());
730 dd.sphere(glam::Vec3::ZERO, 1.0, green());
731 assert_eq!(dd.transient_count(), 2);
732
733 dd.begin_frame();
734 assert_eq!(dd.transient_count(), 0);
735 assert!(dd.is_empty());
736 }
737
738 #[test]
739 fn test_persistent_survives_begin_frame() {
740 let mut dd = DebugDraw::new();
741 dd.add_persistent(1, DebugPrim::Line {
742 start: glam::Vec3::ZERO,
743 end: glam::Vec3::X,
744 colour: red(),
745 layer: DebugLayer::Dev,
746 });
747 assert_eq!(dd.persistent_count(), 1);
748
749 dd.begin_frame();
750 assert_eq!(dd.transient_count(), 0);
751 assert_eq!(dd.persistent_count(), 1);
752 assert!(!dd.is_empty());
753 }
754
755 #[test]
756 fn test_persistent_removed_explicitly() {
757 let mut dd = DebugDraw::new();
758 dd.add_persistent(42, DebugPrim::Sphere {
759 center: glam::Vec3::ZERO,
760 radius: 1.0,
761 colour: green(),
762 layer: DebugLayer::Dev,
763 });
764 assert!(dd.has_persistent(42));
765
766 let removed = dd.remove_persistent(42);
767 assert!(removed.is_some());
768 assert!(!dd.has_persistent(42));
769 assert_eq!(dd.persistent_count(), 0);
770 }
771
772 #[test]
773 fn test_persistent_remove_missing_returns_none() {
774 let mut dd = DebugDraw::new();
775 assert!(dd.remove_persistent(999).is_none());
776 }
777
778 #[test]
779 fn test_persistent_and_transient_both_in_prims() {
780 let mut dd = DebugDraw::new();
781 dd.line(glam::Vec3::ZERO, glam::Vec3::X, red());
782 dd.add_persistent(1, DebugPrim::Sphere {
783 center: glam::Vec3::ZERO,
784 radius: 1.0,
785 colour: green(),
786 layer: DebugLayer::Dev,
787 });
788 assert_eq!(dd.prims().count(), 2);
789 }
790
791 #[test]
792 fn test_clear_persistent() {
793 let mut dd = DebugDraw::new();
794 dd.add_persistent(1, DebugPrim::Point { position: glam::Vec3::ZERO, radius: 4.0, colour: red(), layer: DebugLayer::Dev });
795 dd.add_persistent(2, DebugPrim::Point { position: glam::Vec3::X, radius: 4.0, colour: green(), layer: DebugLayer::Dev });
796 dd.clear_persistent();
797 assert_eq!(dd.persistent_count(), 0);
798 }
799
800 #[test]
803 fn test_enabled_false_is_noop() {
804 let mut dd = DebugDraw::new();
805 dd.enabled = false;
806 dd.line(glam::Vec3::ZERO, glam::Vec3::X, red());
807 dd.sphere(glam::Vec3::ZERO, 1.0, green());
808 dd.label(glam::Vec3::ZERO, "text", red());
809 assert_eq!(dd.transient_count(), 0);
810 assert!(dd.to_polylines().is_empty());
811 assert!(dd.to_point_cloud().is_none());
812 assert!(dd.to_labels().is_empty());
813 }
814
815 #[test]
816 fn test_dev_disabled_suppresses_dev_prims_in_output() {
817 let mut dd = DebugDraw::new();
818 dd.dev_enabled = false;
819
820 dd.line(glam::Vec3::ZERO, glam::Vec3::X, red()); assert_eq!(dd.transient_count(), 0);
824 assert!(dd.to_polylines().is_empty());
825 }
826
827 #[test]
828 fn test_overlay_layer_shown_when_dev_disabled() {
829 let mut dd = DebugDraw::new();
830 dd.dev_enabled = false;
831
832 dd.line_overlay(glam::Vec3::ZERO, glam::Vec3::X, red());
834 assert_eq!(dd.transient_count(), 1);
835 assert!(!dd.to_polylines().is_empty());
836 }
837
838 #[test]
839 fn test_persistent_dev_skipped_in_rendering_when_dev_disabled() {
840 let mut dd = DebugDraw::new();
841 dd.add_persistent(1, DebugPrim::Line {
843 start: glam::Vec3::ZERO,
844 end: glam::Vec3::X,
845 colour: red(),
846 layer: DebugLayer::Dev,
847 });
848 dd.dev_enabled = false;
850 assert_eq!(dd.persistent_count(), 1);
852 assert!(dd.to_polylines().is_empty());
853 }
854
855 #[test]
858 fn test_to_polylines_produces_output_for_lines() {
859 let mut dd = DebugDraw::new();
860 dd.line(glam::Vec3::ZERO, glam::Vec3::X, red());
861 let polylines = dd.to_polylines();
862 assert!(!polylines.is_empty());
863 let total_positions: usize = polylines.iter().map(|p| p.positions.len()).sum();
864 assert_eq!(total_positions, 2);
865 }
866
867 #[test]
868 fn test_to_polylines_aabb_18_positions() {
869 let mut dd = DebugDraw::new();
870 dd.aabb(glam::Vec3::NEG_ONE, glam::Vec3::ONE, red());
871 let polylines = dd.to_polylines();
872 assert!(!polylines.is_empty());
873 let total: usize = polylines.iter().map(|p| p.positions.len()).sum();
875 assert_eq!(total, 18);
876 }
877
878 #[test]
879 fn test_to_polylines_sphere_three_circles() {
880 let mut dd = DebugDraw::new();
881 dd.sphere(glam::Vec3::ZERO, 1.0, green());
882 let polylines = dd.to_polylines();
883 assert!(!polylines.is_empty());
884 let total: usize = polylines.iter().map(|p| p.positions.len()).sum();
886 assert_eq!(total, 3 * 33);
887 }
888
889 #[test]
890 fn test_to_point_cloud_produces_output() {
891 let mut dd = DebugDraw::new();
892 dd.point(glam::Vec3::ZERO, 5.0, red());
893 dd.point(glam::Vec3::X, 3.0, green());
894 let pc = dd.to_point_cloud();
895 assert!(pc.is_some());
896 let pc = pc.unwrap();
897 assert_eq!(pc.positions.len(), 2);
898 assert_eq!(pc.colours.len(), 2);
899 assert_eq!(pc.radii.len(), 2);
900 }
901
902 #[test]
903 fn test_to_labels_produces_output() {
904 let mut dd = DebugDraw::new();
905 dd.label(glam::Vec3::ZERO, "A", red());
906 dd.label(glam::Vec3::X, "B", green());
907 let labels = dd.to_labels();
908 assert_eq!(labels.len(), 2);
909 assert_eq!(labels[0].text, "A");
910 assert_eq!(labels[1].text, "B");
911 }
912
913 #[test]
914 fn test_same_colour_grouped_into_one_polyline() {
915 let mut dd = DebugDraw::new();
916 dd.line(glam::Vec3::ZERO, glam::Vec3::X, red());
917 dd.line(glam::Vec3::Y, glam::Vec3::Z, red());
918 let polylines = dd.to_polylines();
920 assert_eq!(polylines.len(), 1);
921 assert_eq!(polylines[0].positions.len(), 4);
922 assert_eq!(polylines[0].strip_lengths, vec![2, 2]);
923 }
924
925 #[test]
926 fn test_different_colours_produce_separate_polylines() {
927 let mut dd = DebugDraw::new();
928 dd.line(glam::Vec3::ZERO, glam::Vec3::X, red());
929 dd.line(glam::Vec3::Y, glam::Vec3::Z, green());
930 let polylines = dd.to_polylines();
931 assert_eq!(polylines.len(), 2);
932 }
933}