Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 60 additions & 2 deletions src/elevation_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ pub fn fetch_elevation_data(
let tiles: Vec<(u32, u32)> = get_tile_coordinates(bbox, zoom);

// Match grid dimensions with Minecraft world size
let grid_width: usize = scale_factor_x as usize;
let grid_height: usize = scale_factor_z as usize;
// Ensure minimum grid size of 1 to prevent division by zero and indexing errors
let grid_width: usize = (scale_factor_x as usize).max(1);
let grid_height: usize = (scale_factor_z as usize).max(1);

// Initialize height grid with proper dimensions
let mut height_grid: Vec<Vec<f64>> = vec![vec![f64::NAN; grid_width]; grid_height];
Expand Down Expand Up @@ -381,6 +382,11 @@ fn get_tile_coordinates(bbox: &LLBBox, zoom: u8) -> Vec<(u32, u32)> {
}

fn apply_gaussian_blur(heights: &[Vec<f64>], sigma: f64) -> Vec<Vec<f64>> {
// Guard against empty input
if heights.is_empty() || heights[0].is_empty() {
return heights.to_owned();
}

let kernel_size: usize = (sigma * 3.0).ceil() as usize * 2 + 1;
let kernel: Vec<f64> = create_gaussian_kernel(kernel_size, sigma);

Expand Down Expand Up @@ -450,6 +456,11 @@ fn create_gaussian_kernel(size: usize, sigma: f64) -> Vec<f64> {
}

fn fill_nan_values(height_grid: &mut [Vec<f64>]) {
// Guard against empty grid
if height_grid.is_empty() || height_grid[0].is_empty() {
return;
}

let height: usize = height_grid.len();
let width: usize = height_grid[0].len();

Expand Down Expand Up @@ -490,6 +501,11 @@ fn fill_nan_values(height_grid: &mut [Vec<f64>]) {
}

fn filter_elevation_outliers(height_grid: &mut [Vec<f64>]) {
// Guard against empty grid
if height_grid.is_empty() || height_grid[0].is_empty() {
return;
}

let height = height_grid.len();
let width = height_grid[0].len();

Expand Down Expand Up @@ -604,4 +620,46 @@ mod tests {
.unwrap()
.contains("image"));
}

#[test]
fn test_empty_grid_handling() {
// Test that empty grids don't cause panics
let empty_grid: Vec<Vec<f64>> = vec![];
let result = apply_gaussian_blur(&empty_grid, 5.0);
assert!(result.is_empty());

// Test grid with empty rows
let grid_with_empty_rows: Vec<Vec<f64>> = vec![vec![]];
let result = apply_gaussian_blur(&grid_with_empty_rows, 5.0);
assert_eq!(result.len(), 1);
assert!(result[0].is_empty());
}

#[test]
fn test_fill_nan_values_empty_grid() {
// Test that empty grids don't cause panics
let mut empty_grid: Vec<Vec<f64>> = vec![];
fill_nan_values(&mut empty_grid);
assert!(empty_grid.is_empty());

// Test grid with empty rows
let mut grid_with_empty_rows: Vec<Vec<f64>> = vec![vec![]];
fill_nan_values(&mut grid_with_empty_rows);
assert_eq!(grid_with_empty_rows.len(), 1);
assert!(grid_with_empty_rows[0].is_empty());
}

#[test]
fn test_filter_outliers_empty_grid() {
// Test that empty grids don't cause panics
let mut empty_grid: Vec<Vec<f64>> = vec![];
filter_elevation_outliers(&mut empty_grid);
assert!(empty_grid.is_empty());

// Test grid with empty rows
let mut grid_with_empty_rows: Vec<Vec<f64>> = vec![vec![]];
filter_elevation_outliers(&mut grid_with_empty_rows);
assert_eq!(grid_with_empty_rows.len(), 1);
assert!(grid_with_empty_rows[0].is_empty());
}
}
87 changes: 87 additions & 0 deletions src/ground.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ impl Ground {
/// Converts game coordinates to elevation data coordinates
#[inline(always)]
fn get_data_coordinates(&self, coord: XZPoint, data: &ElevationData) -> (f64, f64) {
// Guard against division by zero for edge cases
if data.width == 0 || data.height == 0 {
return (0.0, 0.0);
}
let x_ratio: f64 = coord.x as f64 / data.width as f64;
let z_ratio: f64 = coord.z as f64 / data.height as f64;
(x_ratio.clamp(0.0, 1.0), z_ratio.clamp(0.0, 1.0))
Expand All @@ -83,8 +87,17 @@ impl Ground {
/// Interpolates height value from the elevation grid
#[inline(always)]
fn interpolate_height(&self, x_ratio: f64, z_ratio: f64, data: &ElevationData) -> i32 {
// Guard against out of bounds access
if data.width == 0 || data.height == 0 || data.heights.is_empty() {
return self.ground_level;
}
let x: usize = ((x_ratio * (data.width - 1) as f64).round() as usize).min(data.width - 1);
let z: usize = ((z_ratio * (data.height - 1) as f64).round() as usize).min(data.height - 1);

// Additional safety check for row length
if z >= data.heights.len() || x >= data.heights[z].len() {
return self.ground_level;
}
data.heights[z][x]
}

Expand Down Expand Up @@ -138,6 +151,80 @@ impl Ground {
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_ground_level_with_zero_dimensions() {
// Test that zero-dimension elevation data doesn't cause panic
let elevation_data = ElevationData {
heights: vec![],
width: 0,
height: 0,
};

let ground = Ground {
elevation_enabled: true,
ground_level: 64,
elevation_data: Some(elevation_data),
};

// This should not panic and should return ground_level
let level = ground.level(XZPoint::new(10, 10));
assert_eq!(level, 64);
}

#[test]
fn test_ground_level_with_one_dimension_zero() {
// Test that partial zero dimensions don't cause panic
let elevation_data = ElevationData {
heights: vec![vec![100]],
width: 0,
height: 1,
};

let ground = Ground {
elevation_enabled: true,
ground_level: 64,
elevation_data: Some(elevation_data),
};

// This should not panic and should return ground_level
let level = ground.level(XZPoint::new(5, 5));
assert_eq!(level, 64);
}

#[test]
fn test_ground_level_normal_case() {
// Test that normal elevation data works correctly
let elevation_data = ElevationData {
heights: vec![vec![80, 85], vec![90, 95]],
width: 2,
height: 2,
};

let ground = Ground {
elevation_enabled: true,
ground_level: 64,
elevation_data: Some(elevation_data),
};

// This should work normally
let level = ground.level(XZPoint::new(0, 0));
assert!(level >= 64); // Should be one of the elevation values
}

#[test]
fn test_ground_level_disabled() {
// Test that disabled elevation returns ground_level
let ground = Ground::new_flat(70);

let level = ground.level(XZPoint::new(100, 100));
assert_eq!(level, 70);
}
}

pub fn generate_ground_data(args: &Args) -> Ground {
if args.terrain {
println!("{} Fetching elevation...", "[3/7]".bold());
Expand Down
Loading