Add random map and position selection for exploration example

Add pixel_to_world() coordinate conversion to SimulationMap, enabling
the exploration example to randomly select maps from the data/maps
directory and find valid start positions with sufficient clearance.
This makes test runs more varied and robust.
This commit is contained in:
codetiger
2025-12-24 17:07:09 +05:30
parent 5cf2a3cc81
commit f08c4538fa
2 changed files with 139 additions and 4 deletions
+15
View File
@@ -144,6 +144,21 @@ impl SimulationMap {
self.resolution
}
/// Get map origin (world coordinates of bottom-left pixel)
pub fn origin(&self) -> (f32, f32) {
self.origin
}
/// Convert pixel coordinates to world coordinates
///
/// Returns (x, y) in meters. The center of the pixel is returned.
pub fn pixel_to_world(&self, px: u32, py: u32) -> (f32, f32) {
let x = self.origin.0 + (px as f32 + 0.5) * self.resolution;
// Y is inverted: bottom of image = origin, top of image = +Y
let y = self.origin.1 + (self.pixels.height() as f32 - 1.0 - py as f32 + 0.5) * self.resolution;
(x, y)
}
/// Convert world coordinates to pixel coordinates
fn world_to_pixel(&self, x: f32, y: f32) -> Option<(u32, u32)> {
let px = ((x - self.origin.0) / self.resolution) as i32;
+124 -4
View File
@@ -18,6 +18,8 @@
mod harness;
use harness::{HarnessConfig, TestHarness};
use rand::seq::IndexedRandom;
use sangam_io::devices::mock::map_loader::SimulationMap;
use vastu_map::Map;
use vastu_map::Path as PlannedPath;
use vastu_map::config::ExplorationConfig as MapExplorationConfig;
@@ -32,6 +34,114 @@ use vastu_map::query::cbvg::ClearanceVisibilityGraph;
/// Maximum simulation time in seconds.
const MAX_TIME: f32 = 200.0; // 3.3 minutes
/// Directory containing available maps.
const MAPS_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/data/maps");
/// Select a random map file from the maps directory.
///
/// # Returns
/// The full path to a randomly selected .yaml map file.
///
/// # Panics
/// If no map files are found in the directory.
fn select_random_map() -> String {
let maps_path = std::path::Path::new(MAPS_DIR);
// Collect all .yaml files in the maps directory
let map_files: Vec<_> = std::fs::read_dir(maps_path)
.expect("Failed to read maps directory")
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "yaml") {
Some(path)
} else {
None
}
})
.collect();
if map_files.is_empty() {
panic!("No map files found in {}", MAPS_DIR);
}
// Pick a random map
let mut rng = rand::rng();
let selected = map_files.choose(&mut rng).unwrap();
let map_name = selected.file_stem().unwrap().to_string_lossy();
println!("Selected random map: {}", map_name);
println!(" (from {} available maps)", map_files.len());
selected.to_string_lossy().into_owned()
}
/// Find a random reachable (free) position in the map with sufficient clearance.
///
/// # Arguments
/// * `map_file` - Path to the map YAML file
/// * `robot_radius` - Robot radius in meters for clearance checking
///
/// # Returns
/// A tuple (x, y) of world coordinates for a valid start position.
///
/// # Panics
/// If no valid position can be found after sampling.
fn find_random_start_position(map_file: &str, robot_radius: f32) -> (f32, f32) {
let map = SimulationMap::load(map_file).expect("Failed to load map for position sampling");
// Collect all free pixels with sufficient clearance
let mut valid_positions = Vec::new();
// Calculate clearance in pixels (add some margin)
let clearance_pixels = ((robot_radius * 1.5) / map.resolution()).ceil() as i32;
for py in 0..map.height() {
for px in 0..map.width() {
let (x, y) = map.pixel_to_world(px, py);
// Check if this position is free
if map.is_occupied(x, y) {
continue;
}
// Check clearance around the robot
let mut has_clearance = true;
'clearance: for dy in -clearance_pixels..=clearance_pixels {
for dx in -clearance_pixels..=clearance_pixels {
let check_x = x + dx as f32 * map.resolution();
let check_y = y + dy as f32 * map.resolution();
if map.is_occupied(check_x, check_y) {
has_clearance = false;
break 'clearance;
}
}
}
if has_clearance {
valid_positions.push((x, y));
}
}
}
if valid_positions.is_empty() {
panic!("No valid start positions found in map with sufficient clearance");
}
// Pick a random position
let mut rng = rand::rng();
let (x, y) = *valid_positions.choose(&mut rng).unwrap();
println!("Selected random start position: ({:.2}, {:.2})", x, y);
println!(
" (from {} valid positions with {:.2}m clearance)",
valid_positions.len(),
robot_radius * 1.5
);
(x, y)
}
/// Result of exploration.
#[derive(Debug)]
pub struct ExplorationResult {
@@ -63,11 +173,21 @@ fn main() {
// Ensure results directory exists
std::fs::create_dir_all("results").ok();
// Setup harness with map file and starting position
// Select a random map from available maps
let map_file = select_random_map();
// Get default robot radius for clearance checking
let default_config = HarnessConfig::default();
let robot_radius = default_config.robot.robot_radius;
// Find a random reachable start position
let (start_x, start_y) = find_random_start_position(&map_file, robot_radius);
// Setup harness with map file and random starting position
let harness_config = HarnessConfig {
map_file: concat!(env!("CARGO_MANIFEST_DIR"), "/data/maps/medium_room.yaml").to_string(),
start_x: 4.0,
start_y: 4.0,
map_file,
start_x,
start_y,
..Default::default()
};