diff --git a/src/app.rs b/src/app.rs index d7af82e..b796b18 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,7 @@ use std::time::{Duration, Instant}; use anyhow::{anyhow, Result}; +use glam::Vec2; use sdl2::event::{Event, WindowEvent}; use sdl2::keyboard::Keycode; use sdl2::render::{Canvas, ScaleMode, Texture, TextureCreator}; @@ -19,6 +20,7 @@ pub struct App<'a> { backbuffer: Texture<'a>, paused: bool, last_tick: Instant, + cursor_pos: Vec2, } impl App<'_> { @@ -56,7 +58,7 @@ impl App<'_> { // Initial draw game.draw(&mut canvas, &mut backbuffer)?; - game.present_backbuffer(&mut canvas, &backbuffer)?; + game.present_backbuffer(&mut canvas, &backbuffer, glam::Vec2::ZERO)?; Ok(Self { game, @@ -65,6 +67,7 @@ impl App<'_> { backbuffer, paused: false, last_tick: Instant::now(), + cursor_pos: Vec2::ZERO, }) } @@ -109,6 +112,10 @@ impl App<'_> { Event::KeyDown { keycode, .. } => { self.game.keyboard_event(keycode.unwrap()); } + Event::MouseMotion { x, y, .. } => { + // Convert window coordinates to logical coordinates + self.cursor_pos = Vec2::new(x as f32, y as f32); + } _ => {} } } @@ -121,7 +128,10 @@ impl App<'_> { if let Err(e) = self.game.draw(&mut self.canvas, &mut self.backbuffer) { error!("Failed to draw game: {e}"); } - if let Err(e) = self.game.present_backbuffer(&mut self.canvas, &self.backbuffer) { + if let Err(e) = self + .game + .present_backbuffer(&mut self.canvas, &self.backbuffer, self.cursor_pos) + { error!("Failed to present backbuffer: {e}"); } } diff --git a/src/game.rs b/src/game.rs index 35cbfd7..0731555 100644 --- a/src/game.rs +++ b/src/game.rs @@ -108,10 +108,16 @@ impl Game { Ok(()) } - pub fn present_backbuffer(&mut self, canvas: &mut Canvas, backbuffer: &Texture) -> Result<()> { + pub fn present_backbuffer( + &mut self, + canvas: &mut Canvas, + backbuffer: &Texture, + cursor_pos: glam::Vec2, + ) -> Result<()> { canvas.copy(backbuffer, None, None).map_err(anyhow::Error::msg)?; if self.debug_mode { - self.map.debug_render_nodes(canvas); + self.map + .debug_render_with_cursor(canvas, &mut self.text_texture, &mut self.atlas, cursor_pos); } self.draw_hud(canvas)?; canvas.present(); diff --git a/src/map/builder.rs b/src/map/builder.rs index 94d9552..a3bf5b7 100644 --- a/src/map/builder.rs +++ b/src/map/builder.rs @@ -184,13 +184,18 @@ impl Map { MapRenderer::render_map(canvas, atlas, map_texture); } - /// Renders a debug visualization of the navigation graph. + /// Renders a debug visualization with cursor-based highlighting. /// - /// This function is intended for development and debugging purposes. It draws the - /// nodes and edges of the graph on top of the map, allowing for visual - /// inspection of the navigation paths. - pub fn debug_render_nodes(&self, canvas: &mut Canvas) { - MapRenderer::debug_render_nodes(&self.graph, canvas); + /// This function provides interactive debugging by highlighting the nearest node + /// to the cursor, showing its ID, and highlighting its connections. + pub fn debug_render_with_cursor( + &self, + canvas: &mut Canvas, + text_renderer: &mut crate::texture::text::TextTexture, + atlas: &mut SpriteAtlas, + cursor_pos: glam::Vec2, + ) { + MapRenderer::debug_render_with_cursor(&self.graph, canvas, text_renderer, atlas, cursor_pos); } /// Builds the house structure in the graph. diff --git a/src/map/render.rs b/src/map/render.rs index efb7895..c01c825 100644 --- a/src/map/render.rs +++ b/src/map/render.rs @@ -1,6 +1,8 @@ //! Map rendering functionality. use crate::texture::sprite::{AtlasTile, SpriteAtlas}; +use crate::texture::text::TextTexture; +use glam::Vec2; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; use sdl2::render::{Canvas, RenderTarget}; @@ -23,45 +25,93 @@ impl MapRenderer { let _ = map_texture.render(canvas, atlas, dest); } - /// Renders a debug visualization of the navigation graph. + /// Renders a debug visualization with cursor-based highlighting. /// - /// This function is intended for development and debugging purposes. It draws the - /// nodes and edges of the graph on top of the map, allowing for visual - /// inspection of the navigation paths. - pub fn debug_render_nodes(graph: &crate::entity::graph::Graph, canvas: &mut Canvas) { + /// This function provides interactive debugging by highlighting the nearest node + /// to the cursor, showing its ID, and highlighting its connections. + pub fn debug_render_with_cursor( + graph: &crate::entity::graph::Graph, + canvas: &mut Canvas, + text_renderer: &mut TextTexture, + atlas: &mut SpriteAtlas, + cursor_pos: Vec2, + ) { + // Find the nearest node to the cursor + let nearest_node = Self::find_nearest_node(graph, cursor_pos); + + // Draw all connections in blue + canvas.set_draw_color(Color::RGB(0, 0, 128)); // Dark blue for regular connections for i in 0..graph.node_count() { let node = graph.get_node(i).unwrap(); let pos = node.position + crate::constants::BOARD_PIXEL_OFFSET.as_vec2(); - // Draw connections - canvas.set_draw_color(Color::BLUE); - for edge in graph.adjacency_list[i].edges() { let end_pos = graph.get_node(edge.target).unwrap().position + crate::constants::BOARD_PIXEL_OFFSET.as_vec2(); canvas .draw_line((pos.x as i32, pos.y as i32), (end_pos.x as i32, end_pos.y as i32)) .unwrap(); } + } + + // Draw all nodes in green + canvas.set_draw_color(Color::RGB(0, 128, 0)); // Dark green for regular nodes + for i in 0..graph.node_count() { + let node = graph.get_node(i).unwrap(); + let pos = node.position + crate::constants::BOARD_PIXEL_OFFSET.as_vec2(); - // Draw node - // let color = if pacman.position.from_node_idx() == i.into() { - // Color::GREEN - // } else if let Some(to_idx) = pacman.position.to_node_idx() { - // if to_idx == i.into() { - // Color::CYAN - // } else { - // Color::RED - // } - // } else { - // Color::RED - // }; - canvas.set_draw_color(Color::GREEN); canvas .fill_rect(Rect::new(0, 0, 3, 3).centered_on(Point::new(pos.x as i32, pos.y as i32))) .unwrap(); + } - // Draw node index - // text.render(canvas, atlas, &i.to_string(), pos.as_uvec2()).unwrap(); + // Highlight connections from the nearest node in bright blue + if let Some(nearest_id) = nearest_node { + let nearest_pos = graph.get_node(nearest_id).unwrap().position + crate::constants::BOARD_PIXEL_OFFSET.as_vec2(); + + canvas.set_draw_color(Color::RGB(0, 255, 255)); // Bright cyan for highlighted connections + for edge in graph.adjacency_list[nearest_id].edges() { + let end_pos = graph.get_node(edge.target).unwrap().position + crate::constants::BOARD_PIXEL_OFFSET.as_vec2(); + canvas + .draw_line( + (nearest_pos.x as i32, nearest_pos.y as i32), + (end_pos.x as i32, end_pos.y as i32), + ) + .unwrap(); + } + + // Highlight the nearest node in bright green + canvas.set_draw_color(Color::RGB(0, 255, 0)); // Bright green for highlighted node + canvas + .fill_rect(Rect::new(0, 0, 5, 5).centered_on(Point::new(nearest_pos.x as i32, nearest_pos.y as i32))) + .unwrap(); + + // Draw node ID text (small, offset to top right) + text_renderer.set_scale(0.5); // Small text + let id_text = format!("#{}", nearest_id); + let text_pos = glam::UVec2::new( + (nearest_pos.x + 4.0) as u32, // Offset to the right + (nearest_pos.y - 6.0) as u32, // Offset to the top + ); + let _ = text_renderer.render(canvas, atlas, &id_text, text_pos); } } + + /// Finds the nearest node to the given cursor position. + pub fn find_nearest_node(graph: &crate::entity::graph::Graph, cursor_pos: Vec2) -> Option { + let mut nearest_id = None; + let mut nearest_distance = f32::INFINITY; + + for i in 0..graph.node_count() { + let node = graph.get_node(i).unwrap(); + let node_pos = node.position + crate::constants::BOARD_PIXEL_OFFSET.as_vec2(); + let distance = cursor_pos.distance(node_pos); + + if distance < nearest_distance { + nearest_distance = distance; + nearest_id = Some(i); + } + } + + nearest_id + } } diff --git a/tests/debug_rendering.rs b/tests/debug_rendering.rs new file mode 100644 index 0000000..a91dea4 --- /dev/null +++ b/tests/debug_rendering.rs @@ -0,0 +1,34 @@ +use glam::Vec2; +use pacman::entity::graph::{Graph, Node}; +use pacman::map::render::MapRenderer; + +#[test] +fn test_find_nearest_node() { + let mut graph = Graph::new(); + + // Add some test nodes + let node1 = graph.add_node(Node { + position: Vec2::new(10.0, 10.0), + }); + let node2 = graph.add_node(Node { + position: Vec2::new(50.0, 50.0), + }); + let node3 = graph.add_node(Node { + position: Vec2::new(100.0, 100.0), + }); + + // Test cursor near node1 + let cursor_pos = Vec2::new(12.0, 8.0); + let nearest = MapRenderer::find_nearest_node(&graph, cursor_pos); + assert_eq!(nearest, Some(node1)); + + // Test cursor near node2 + let cursor_pos = Vec2::new(45.0, 55.0); + let nearest = MapRenderer::find_nearest_node(&graph, cursor_pos); + assert_eq!(nearest, Some(node2)); + + // Test cursor near node3 + let cursor_pos = Vec2::new(98.0, 102.0); + let nearest = MapRenderer::find_nearest_node(&graph, cursor_pos); + assert_eq!(nearest, Some(node3)); +}