Compare commits

...

4 Commits

Author SHA1 Message Date
Louis Erbkamm
1bcec3dcc0 Merge branch 'main' into copilot/fix-generation-stop-issue 2025-11-22 14:40:11 +01:00
copilot-swe-agent[bot]
e86af9e006 Add tests for zero-dimension elevation grid handling
Co-authored-by: louis-e <44675238+louis-e@users.noreply.github.com>
2025-11-17 21:17:50 +00:00
copilot-swe-agent[bot]
7575141837 Add defensive checks to prevent crash with zero-dimension elevation grids
Co-authored-by: louis-e <44675238+louis-e@users.noreply.github.com>
2025-11-17 21:16:23 +00:00
copilot-swe-agent[bot]
eb288e9a03 Initial plan 2025-11-17 21:10:11 +00:00
2 changed files with 147 additions and 2 deletions

View File

@@ -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];
@@ -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);
@@ -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();
@@ -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();
@@ -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());
}
}

View File

@@ -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))
@@ -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]
}
@@ -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());