#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MinimapConfig {
pub width_px: u32,
pub height_px: u32,
pub world_extent: f32,
pub background: [f32; 4],
}
#[derive(Debug, Clone)]
pub struct MinimapMarker {
pub id: u32,
pub world_pos: [f32; 2],
pub color: [f32; 4],
pub label: String,
}
#[derive(Debug, Clone)]
pub struct MinimapView {
pub config: MinimapConfig,
pub zoom: f32,
pub center: [f32; 2],
pub markers: Vec<MinimapMarker>,
}
pub fn default_minimap_config() -> MinimapConfig {
MinimapConfig {
width_px: 200,
height_px: 200,
world_extent: 50.0,
background: [0.05, 0.05, 0.05, 0.8],
}
}
pub fn new_minimap_view(config: MinimapConfig) -> MinimapView {
MinimapView {
config,
zoom: 1.0,
center: [0.0, 0.0],
markers: Vec::new(),
}
}
pub fn minimap_add_marker(view: &mut MinimapView, marker: MinimapMarker) {
view.markers.push(marker);
}
pub fn minimap_remove_marker(view: &mut MinimapView, id: u32) -> bool {
let before = view.markers.len();
view.markers.retain(|m| m.id != id);
view.markers.len() < before
}
pub fn minimap_marker_count(view: &MinimapView) -> usize {
view.markers.len()
}
pub fn minimap_world_to_screen(view: &MinimapView, world_xz: [f32; 2]) -> Option<[f32; 2]> {
let half = view.config.world_extent / view.zoom;
let dx = world_xz[0] - view.center[0];
let dz = world_xz[1] - view.center[1];
if dx.abs() > half || dz.abs() > half {
return None;
}
let nx = (dx / half + 1.0) * 0.5;
let nz = (dz / half + 1.0) * 0.5;
Some([
nx * view.config.width_px as f32,
nz * view.config.height_px as f32,
])
}
pub fn minimap_set_zoom(view: &mut MinimapView, zoom: f32) {
view.zoom = zoom.clamp(0.1, 20.0);
}
pub fn minimap_center_on(view: &mut MinimapView, world_xz: [f32; 2]) {
view.center = world_xz;
}
pub fn minimap_clear(view: &mut MinimapView) {
view.markers.clear();
}
pub fn minimap_to_json(view: &MinimapView) -> String {
let mut out = format!(
"{{\"zoom\":{:.4},\"center\":[{:.4},{:.4}],\"markers\":[",
view.zoom, view.center[0], view.center[1]
);
for (i, m) in view.markers.iter().enumerate() {
let comma = if i + 1 < view.markers.len() { "," } else { "" };
out.push_str(&format!(
"{{\"id\":{},\"world\":[{:.4},{:.4}],\"label\":\"{}\"}}{}",
m.id, m.world_pos[0], m.world_pos[1], m.label, comma
));
}
out.push_str("]}");
out
}
fn make_marker(id: u32, x: f32, z: f32) -> MinimapMarker {
MinimapMarker {
id,
world_pos: [x, z],
color: [1.0, 0.0, 0.0, 1.0],
label: format!("marker_{}", id),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_view() -> MinimapView {
new_minimap_view(default_minimap_config())
}
#[test]
fn default_config_values() {
let cfg = default_minimap_config();
assert_eq!(cfg.width_px, 200);
assert!((cfg.world_extent - 50.0).abs() < 1e-5);
}
#[test]
fn new_view_has_no_markers() {
let v = default_view();
assert_eq!(minimap_marker_count(&v), 0);
}
#[test]
fn add_marker_increments_count() {
let mut v = default_view();
minimap_add_marker(&mut v, make_marker(1, 0.0, 0.0));
assert_eq!(minimap_marker_count(&v), 1);
}
#[test]
fn remove_marker_by_id() {
let mut v = default_view();
minimap_add_marker(&mut v, make_marker(1, 0.0, 0.0));
minimap_add_marker(&mut v, make_marker(2, 5.0, 5.0));
let removed = minimap_remove_marker(&mut v, 1);
assert!(removed);
assert_eq!(minimap_marker_count(&v), 1);
}
#[test]
fn remove_nonexistent_marker_returns_false() {
let mut v = default_view();
assert!(!minimap_remove_marker(&mut v, 99));
}
#[test]
fn world_to_screen_centre() {
let v = default_view();
let px = minimap_world_to_screen(&v, [0.0, 0.0]);
assert!(px.is_some());
let px = px.expect("should succeed");
assert!((px[0] - 100.0).abs() < 1.0);
assert!((px[1] - 100.0).abs() < 1.0);
}
#[test]
fn world_to_screen_out_of_bounds_returns_none() {
let v = default_view();
let px = minimap_world_to_screen(&v, [200.0, 0.0]);
assert!(px.is_none());
}
#[test]
fn set_zoom_clamped() {
let mut v = default_view();
minimap_set_zoom(&mut v, 0.0);
assert!((v.zoom - 0.1).abs() < 1e-5);
minimap_set_zoom(&mut v, 100.0);
assert!((v.zoom - 20.0).abs() < 1e-5);
}
#[test]
fn center_on_updates_center() {
let mut v = default_view();
minimap_center_on(&mut v, [10.0, -5.0]);
assert!((v.center[0] - 10.0).abs() < 1e-5);
assert!((v.center[1] - (-5.0)).abs() < 1e-5);
}
#[test]
fn clear_removes_all_markers() {
let mut v = default_view();
minimap_add_marker(&mut v, make_marker(1, 0.0, 0.0));
minimap_add_marker(&mut v, make_marker(2, 1.0, 1.0));
minimap_clear(&mut v);
assert_eq!(minimap_marker_count(&v), 0);
}
#[test]
fn to_json_contains_zoom_and_markers() {
let mut v = default_view();
minimap_add_marker(&mut v, make_marker(1, 0.0, 0.0));
let json = minimap_to_json(&v);
assert!(json.contains("\"zoom\""));
assert!(json.contains("\"markers\""));
assert!(json.contains("\"center\""));
}
}