Skip to main content

proof_engine/debug/
mod.rs

1//! Debug overlay and performance profiling.
2//!
3//! `DebugOverlay` renders FPS counters, entity counts, force field visualizations,
4//! collision bounds, and a math-function grapher directly into the scene as glyphs.
5//!
6//! Disable in release builds by not calling `DebugOverlay::render()`.
7//!
8//! # Example
9//!
10//! ```rust,no_run
11//! use proof_engine::prelude::*;
12//! use proof_engine::debug::DebugOverlay;
13//!
14//! let mut overlay = DebugOverlay::new();
15//! // In your update loop:
16//! // overlay.render(engine, dt);
17//! ```
18
19pub mod profiler;
20pub mod graph;
21pub mod console;
22pub mod metrics;
23pub mod inspector;
24
25pub use profiler::FrameProfiler;
26pub use graph::MathGraph;
27
28use crate::{ProofEngine, Glyph, RenderLayer, MathFunction};
29use crate::render::pipeline::FrameStats;
30use glam::{Vec3, Vec4};
31
32// ── DebugOverlay ──────────────────────────────────────────────────────────────
33
34/// Debug overlay: HUD glyph rendering of engine statistics.
35///
36/// All output is rendered as in-world glyphs at a fixed screen-space position
37/// using `RenderLayer::UI`.  Each call to `render()` clears the previous overlay
38/// glyphs and redraws fresh ones.
39pub struct DebugOverlay {
40    pub enabled:        bool,
41    pub show_fps:       bool,
42    pub show_counts:    bool,
43    pub show_fields:    bool,
44    pub show_camera:    bool,
45    pub show_time:      bool,
46    pub text_color:     Vec4,
47    pub warning_color:  Vec4,
48    pub critical_color: Vec4,
49    /// Glyph IDs spawned this frame (cleared each render()).
50    glyph_ids: Vec<crate::glyph::GlyphId>,
51    profiler:  FrameProfiler,
52    graph:     Option<MathGraph>,
53}
54
55impl DebugOverlay {
56    pub fn new() -> Self {
57        Self {
58            enabled:        true,
59            show_fps:       true,
60            show_counts:    true,
61            show_fields:    false,
62            show_camera:    false,
63            show_time:      true,
64            text_color:     Vec4::new(0.6, 1.0, 0.6, 0.8),
65            warning_color:  Vec4::new(1.0, 0.8, 0.2, 1.0),
66            critical_color: Vec4::new(1.0, 0.2, 0.2, 1.0),
67            glyph_ids:      Vec::new(),
68            profiler:       FrameProfiler::new(120),
69            graph:          None,
70        }
71    }
72
73    /// Attach a math graph to display in the corner.
74    pub fn with_graph(mut self, func: MathFunction, range: (f32, f32)) -> Self {
75        self.graph = Some(MathGraph::new(func, range));
76        self
77    }
78
79    /// Clear the previous overlay and render the current frame's debug info.
80    pub fn render(&mut self, engine: &mut ProofEngine, stats: &FrameStats) {
81        if !self.enabled { return; }
82
83        // Remove previous overlay glyphs
84        for id in self.glyph_ids.drain(..) {
85            engine.scene.glyphs.despawn(id);
86        }
87
88        // Camera "screen space" anchor — top-left corner of the visible area
89        let cam_pos  = engine.camera.target.position();
90        let cam_z    = engine.camera.position.position().z;
91        let fov_rad  = engine.camera.fov.position.to_radians();
92        let half_h   = cam_z * (fov_rad * 0.5).tan();
93        let aspect   = 16.0 / 9.0; // assume 16:9 until resize tracked
94        let half_w   = half_h * aspect;
95        let origin   = cam_pos + Vec3::new(-half_w + 0.5, half_h - 0.5, 0.0);
96
97        let mut line = 0;
98        let line_h   = -0.65_f32;
99        let char_w   =  0.40_f32;
100
101        // ── FPS ────────────────────────────────────────────────────────────────
102        if self.show_fps {
103            let fps = stats.fps;
104            let color = if fps >= 55.0 { self.text_color }
105                        else if fps >= 30.0 { self.warning_color }
106                        else { self.critical_color };
107            let label = format!("FPS:{:>5.1}  dt:{:.1}ms", fps, stats.dt * 1000.0);
108            self.draw_string(engine, &label, origin + Vec3::new(0.0, line as f32 * line_h, 0.0), char_w, color);
109            line += 1;
110        }
111
112        // ── Counts ─────────────────────────────────────────────────────────────
113        if self.show_counts {
114            let label = format!(
115                "G:{:<4} P:{:<4} F:{:<2}",
116                stats.glyph_count, stats.particle_count, stats.frame_number % 10000
117            );
118            self.draw_string(engine, &label, origin + Vec3::new(0.0, line as f32 * line_h, 0.0), char_w, self.text_color);
119            line += 1;
120        }
121
122        // ── Camera position ────────────────────────────────────────────────────
123        if self.show_camera {
124            let pos = engine.camera.position.position();
125            let label = format!("CAM ({:.1},{:.1},{:.1})", pos.x, pos.y, pos.z);
126            self.draw_string(engine, &label, origin + Vec3::new(0.0, line as f32 * line_h, 0.0), char_w, self.text_color);
127            line += 1;
128        }
129
130        // ── Scene time ─────────────────────────────────────────────────────────
131        if self.show_time {
132            let label = format!("T:{:.2}s", engine.scene.time);
133            self.draw_string(engine, &label, origin + Vec3::new(0.0, line as f32 * line_h, 0.0), char_w, self.text_color);
134        }
135
136        // ── Math graph ─────────────────────────────────────────────────────────
137        if let Some(ref graph) = self.graph {
138            let graph_origin = origin + Vec3::new(0.0, -4.0, 0.0);
139            let ids = graph.render(engine, graph_origin, 20.0, 6.0, engine.scene.time);
140            self.glyph_ids.extend(ids);
141        }
142    }
143
144    // ── Internal string renderer ───────────────────────────────────────────────
145
146    fn draw_string(
147        &mut self,
148        engine:   &mut ProofEngine,
149        text:     &str,
150        position: Vec3,
151        char_w:   f32,
152        color:    Vec4,
153    ) {
154        for (i, ch) in text.chars().enumerate() {
155            let x = position.x + i as f32 * char_w;
156            let id = engine.scene.spawn_glyph(Glyph {
157                character: ch,
158                position:  Vec3::new(x, position.y, position.z),
159                color,
160                emission:  0.2,
161                glow_color: Vec3::new(color.x, color.y, color.z),
162                glow_radius: 0.0,
163                scale:     glam::Vec2::splat(0.55),
164                layer:     RenderLayer::UI,
165                ..Default::default()
166            });
167            self.glyph_ids.push(id);
168        }
169    }
170}
171
172impl Default for DebugOverlay {
173    fn default() -> Self { Self::new() }
174}