use crate::error::{AlgorithmError, Result};
pub fn terrain_slope_simd(
dem: &[f32],
slope: &mut [f32],
width: usize,
height: usize,
cell_size: f32,
) -> Result<()> {
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Width and height must be at least 3".to_string(),
});
}
if cell_size <= 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "cell_size",
message: "Cell size must be positive".to_string(),
});
}
if dem.len() != width * height || slope.len() != width * height {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer_size",
message: "Buffer sizes must match width * height".to_string(),
});
}
let scale = 1.0 / (8.0 * cell_size);
for y in 1..(height - 1) {
let prev_row = (y - 1) * width;
let curr_row = y * width;
let next_row = (y + 1) * width;
const LANES: usize = 8;
let chunks = (width - 2) / LANES;
for chunk in 0..chunks {
let x_start = 1 + chunk * LANES;
let x_end = x_start + LANES;
for x in x_start..x_end {
let dzdx =
((dem[prev_row + x + 1] + 2.0 * dem[curr_row + x + 1] + dem[next_row + x + 1])
- (dem[prev_row + x - 1]
+ 2.0 * dem[curr_row + x - 1]
+ dem[next_row + x - 1]))
* scale;
let dzdy = ((dem[next_row + x - 1]
+ 2.0 * dem[next_row + x]
+ dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[prev_row + x] + dem[prev_row + x + 1]))
* scale;
let slope_rad = (dzdx * dzdx + dzdy * dzdy).sqrt().atan();
slope[curr_row + x] = slope_rad.to_degrees();
}
}
let remainder_start = 1 + chunks * LANES;
for x in remainder_start..(width - 1) {
let dzdx = ((dem[prev_row + x + 1]
+ 2.0 * dem[curr_row + x + 1]
+ dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[curr_row + x - 1] + dem[next_row + x - 1]))
* scale;
let dzdy = ((dem[next_row + x - 1] + 2.0 * dem[next_row + x] + dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[prev_row + x] + dem[prev_row + x + 1]))
* scale;
let slope_rad = (dzdx * dzdx + dzdy * dzdy).sqrt().atan();
slope[curr_row + x] = slope_rad.to_degrees();
}
}
for x in 0..width {
slope[x] = slope[width + x]; slope[(height - 1) * width + x] = slope[(height - 2) * width + x]; }
for y in 0..height {
slope[y * width] = slope[y * width + 1]; slope[y * width + width - 1] = slope[y * width + width - 2]; }
Ok(())
}
pub fn terrain_aspect_simd(
dem: &[f32],
aspect: &mut [f32],
width: usize,
height: usize,
cell_size: f32,
) -> Result<()> {
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Width and height must be at least 3".to_string(),
});
}
if cell_size <= 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "cell_size",
message: "Cell size must be positive".to_string(),
});
}
if dem.len() != width * height || aspect.len() != width * height {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer_size",
message: "Buffer sizes must match width * height".to_string(),
});
}
let scale = 1.0 / (8.0 * cell_size);
for y in 1..(height - 1) {
let prev_row = (y - 1) * width;
let curr_row = y * width;
let next_row = (y + 1) * width;
const LANES: usize = 8;
let chunks = (width - 2) / LANES;
for chunk in 0..chunks {
let x_start = 1 + chunk * LANES;
let x_end = x_start + LANES;
for x in x_start..x_end {
let dzdx =
((dem[prev_row + x + 1] + 2.0 * dem[curr_row + x + 1] + dem[next_row + x + 1])
- (dem[prev_row + x - 1]
+ 2.0 * dem[curr_row + x - 1]
+ dem[next_row + x - 1]))
* scale;
let dzdy = ((dem[next_row + x - 1]
+ 2.0 * dem[next_row + x]
+ dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[prev_row + x] + dem[prev_row + x + 1]))
* scale;
let mut aspect_deg = dzdy.atan2(dzdx).to_degrees();
aspect_deg = 90.0 - aspect_deg;
if aspect_deg < 0.0 {
aspect_deg += 360.0;
}
aspect[curr_row + x] = aspect_deg;
}
}
let remainder_start = 1 + chunks * LANES;
for x in remainder_start..(width - 1) {
let dzdx = ((dem[prev_row + x + 1]
+ 2.0 * dem[curr_row + x + 1]
+ dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[curr_row + x - 1] + dem[next_row + x - 1]))
* scale;
let dzdy = ((dem[next_row + x - 1] + 2.0 * dem[next_row + x] + dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[prev_row + x] + dem[prev_row + x + 1]))
* scale;
let mut aspect_deg = dzdy.atan2(dzdx).to_degrees();
aspect_deg = 90.0 - aspect_deg;
if aspect_deg < 0.0 {
aspect_deg += 360.0;
}
aspect[curr_row + x] = aspect_deg;
}
}
for x in 0..width {
aspect[x] = aspect[width + x];
aspect[(height - 1) * width + x] = aspect[(height - 2) * width + x];
}
for y in 0..height {
aspect[y * width] = aspect[y * width + 1];
aspect[y * width + width - 1] = aspect[y * width + width - 2];
}
Ok(())
}
pub fn terrain_tpi_simd(
dem: &[f32],
tpi: &mut [f32],
width: usize,
height: usize,
radius: usize,
) -> Result<()> {
if width == 0 || height == 0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Width and height must be greater than zero".to_string(),
});
}
if radius == 0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "radius",
message: "Radius must be greater than zero".to_string(),
});
}
if dem.len() != width * height || tpi.len() != width * height {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer_size",
message: "Buffer sizes must match width * height".to_string(),
});
}
for y in 0..height {
for x in 0..width {
let y_start = y.saturating_sub(radius);
let y_end = (y + radius + 1).min(height);
let x_start = x.saturating_sub(radius);
let x_end = (x + radius + 1).min(width);
let mut sum = 0.0_f32;
let mut count = 0_usize;
for dy in y_start..y_end {
let row_offset = dy * width;
const LANES: usize = 8;
let window_width = x_end - x_start;
let chunks = window_width / LANES;
for chunk in 0..chunks {
let dx_start = x_start + chunk * LANES;
let dx_end = dx_start + LANES;
for dx in dx_start..dx_end {
sum += dem[row_offset + dx];
count += 1;
}
}
let remainder_start = x_start + chunks * LANES;
for dx in remainder_start..x_end {
sum += dem[row_offset + dx];
count += 1;
}
}
let mean = if count > 0 { sum / count as f32 } else { 0.0 };
tpi[y * width + x] = dem[y * width + x] - mean;
}
}
Ok(())
}
pub fn terrain_tri_simd(dem: &[f32], tri: &mut [f32], width: usize, height: usize) -> Result<()> {
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Width and height must be at least 3".to_string(),
});
}
if dem.len() != width * height || tri.len() != width * height {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer_size",
message: "Buffer sizes must match width * height".to_string(),
});
}
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let center = dem[y * width + x];
let mut diff_sum = 0.0_f32;
for dy in -1..=1_i64 {
for dx in -1..=1_i64 {
if dx == 0 && dy == 0 {
continue;
}
let ny = (y as i64 + dy) as usize;
let nx = (x as i64 + dx) as usize;
diff_sum += (dem[ny * width + nx] - center).abs();
}
}
tri[y * width + x] = diff_sum / 8.0;
}
}
for x in 0..width {
tri[x] = tri[width + x];
tri[(height - 1) * width + x] = tri[(height - 2) * width + x];
}
for y in 0..height {
tri[y * width] = tri[y * width + 1];
tri[y * width + width - 1] = tri[y * width + width - 2];
}
Ok(())
}
pub fn terrain_roughness_simd(
dem: &[f32],
roughness: &mut [f32],
width: usize,
height: usize,
radius: usize,
) -> Result<()> {
if width == 0 || height == 0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Width and height must be greater than zero".to_string(),
});
}
if radius == 0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "radius",
message: "Radius must be greater than zero".to_string(),
});
}
if dem.len() != width * height || roughness.len() != width * height {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer_size",
message: "Buffer sizes must match width * height".to_string(),
});
}
for y in 0..height {
for x in 0..width {
let y_start = y.saturating_sub(radius);
let y_end = (y + radius + 1).min(height);
let x_start = x.saturating_sub(radius);
let x_end = (x + radius + 1).min(width);
let mut min_val = f32::INFINITY;
let mut max_val = f32::NEG_INFINITY;
for dy in y_start..y_end {
let row_offset = dy * width;
const LANES: usize = 8;
let window_width = x_end - x_start;
let chunks = window_width / LANES;
for chunk in 0..chunks {
let dx_start = x_start + chunk * LANES;
let dx_end = dx_start + LANES;
for dx in dx_start..dx_end {
let val = dem[row_offset + dx];
min_val = min_val.min(val);
max_val = max_val.max(val);
}
}
let remainder_start = x_start + chunks * LANES;
for dx in remainder_start..x_end {
let val = dem[row_offset + dx];
min_val = min_val.min(val);
max_val = max_val.max(val);
}
}
roughness[y * width + x] = max_val - min_val;
}
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CurvatureTypeSIMD {
Profile,
Planform,
Total,
Mean,
Gaussian,
}
pub fn terrain_curvature_simd(
dem: &[f32],
curvature: &mut [f32],
width: usize,
height: usize,
cell_size: f32,
curvature_type: CurvatureTypeSIMD,
) -> Result<()> {
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Width and height must be at least 3".to_string(),
});
}
if cell_size <= 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "cell_size",
message: "Cell size must be positive".to_string(),
});
}
if dem.len() != width * height || curvature.len() != width * height {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer_size",
message: "Buffer sizes must match width * height".to_string(),
});
}
let l_squared = cell_size * cell_size;
for y in 1..(height - 1) {
for x in 1..(width - 1) {
let prev_row = (y - 1) * width;
let curr_row = y * width;
let next_row = (y + 1) * width;
let z = [
[
dem[prev_row + x - 1],
dem[prev_row + x],
dem[prev_row + x + 1],
],
[
dem[curr_row + x - 1],
dem[curr_row + x],
dem[curr_row + x + 1],
],
[
dem[next_row + x - 1],
dem[next_row + x],
dem[next_row + x + 1],
],
];
let dz_dx = (z[1][2] - z[1][0]) / (2.0 * cell_size);
let dz_dy = (z[2][1] - z[0][1]) / (2.0 * cell_size);
let d2z_dx2 = (z[1][2] - 2.0 * z[1][1] + z[1][0]) / l_squared;
let d2z_dy2 = (z[2][1] - 2.0 * z[1][1] + z[0][1]) / l_squared;
let d2z_dxdy = (z[2][2] - z[2][0] - z[0][2] + z[0][0]) / (4.0 * l_squared);
let p = dz_dx;
let q = dz_dy;
let p2 = p * p;
let q2 = q * q;
let curv_value = match curvature_type {
CurvatureTypeSIMD::Profile => {
if p2 + q2 == 0.0 {
0.0
} else {
-100.0 * (p2 * d2z_dx2 + 2.0 * p * q * d2z_dxdy + q2 * d2z_dy2)
/ (p2 + q2).powf(1.5)
}
}
CurvatureTypeSIMD::Planform => {
if p2 + q2 == 0.0 {
0.0
} else {
100.0 * (q2 * d2z_dx2 - 2.0 * p * q * d2z_dxdy + p2 * d2z_dy2)
/ (p2 + q2).powf(1.5)
}
}
CurvatureTypeSIMD::Total => -100.0 * (d2z_dx2 + d2z_dy2),
CurvatureTypeSIMD::Mean => {
let denominator = (1.0 + p2 + q2).powf(1.5);
if denominator == 0.0 {
0.0
} else {
-100.0
* ((1.0 + q2) * d2z_dx2 - 2.0 * p * q * d2z_dxdy + (1.0 + p2) * d2z_dy2)
/ denominator
}
}
CurvatureTypeSIMD::Gaussian => {
let denominator = (1.0 + p2 + q2).powi(2);
if denominator == 0.0 {
0.0
} else {
10000.0 * (d2z_dx2 * d2z_dy2 - d2z_dxdy.powi(2)) / denominator
}
}
};
curvature[curr_row + x] = curv_value;
}
}
for x in 0..width {
curvature[x] = curvature[width + x];
curvature[(height - 1) * width + x] = curvature[(height - 2) * width + x];
}
for y in 0..height {
curvature[y * width] = curvature[y * width + 1];
curvature[y * width + width - 1] = curvature[y * width + width - 2];
}
Ok(())
}
pub fn terrain_vrm_simd(
dem: &[f32],
vrm: &mut [f32],
width: usize,
height: usize,
cell_size: f32,
radius: usize,
) -> Result<()> {
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Width and height must be at least 3".to_string(),
});
}
if cell_size <= 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "cell_size",
message: "Cell size must be positive".to_string(),
});
}
if radius == 0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "radius",
message: "Radius must be greater than zero".to_string(),
});
}
if dem.len() != width * height || vrm.len() != width * height {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer_size",
message: "Buffer sizes must match width * height".to_string(),
});
}
let margin = radius + 1;
for v in vrm.iter_mut() {
*v = 0.0;
}
for y in margin..(height - margin) {
for x in margin..(width - margin) {
let mut sum_nx = 0.0_f32;
let mut sum_ny = 0.0_f32;
let mut sum_nz = 0.0_f32;
let mut count = 0_usize;
for dy in -(radius as i64)..=(radius as i64) {
for dx in -(radius as i64)..=(radius as i64) {
let cx = (x as i64 + dx) as usize;
let cy = (y as i64 + dy) as usize;
let z_center = dem[cy * width + cx];
let z_east = dem[cy * width + cx + 1];
let z_north = dem[(cy - 1) * width + cx];
let dz_dx = (z_east - z_center) / cell_size;
let dz_dy = (z_center - z_north) / cell_size;
let nx = -dz_dx;
let ny = -dz_dy;
let nz = 1.0_f32;
let mag = (nx * nx + ny * ny + nz * nz).sqrt();
if mag > 0.0 {
sum_nx += nx / mag;
sum_ny += ny / mag;
sum_nz += nz / mag;
count += 1;
}
}
}
if count > 0 {
let resultant_mag = (sum_nx * sum_nx + sum_ny * sum_ny + sum_nz * sum_nz).sqrt();
vrm[y * width + x] = 1.0 - (resultant_mag / count as f32);
}
}
}
Ok(())
}
pub fn terrain_slope_aspect_combined_simd(
dem: &[f32],
slope: &mut [f32],
aspect: &mut [f32],
width: usize,
height: usize,
cell_size: f32,
) -> Result<()> {
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Width and height must be at least 3".to_string(),
});
}
if cell_size <= 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "cell_size",
message: "Cell size must be positive".to_string(),
});
}
if dem.len() != width * height
|| slope.len() != width * height
|| aspect.len() != width * height
{
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer_size",
message: "Buffer sizes must match width * height".to_string(),
});
}
let scale = 1.0 / (8.0 * cell_size);
for y in 1..(height - 1) {
let prev_row = (y - 1) * width;
let curr_row = y * width;
let next_row = (y + 1) * width;
for x in 1..(width - 1) {
let dzdx = ((dem[prev_row + x + 1]
+ 2.0 * dem[curr_row + x + 1]
+ dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[curr_row + x - 1] + dem[next_row + x - 1]))
* scale;
let dzdy = ((dem[next_row + x - 1] + 2.0 * dem[next_row + x] + dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[prev_row + x] + dem[prev_row + x + 1]))
* scale;
let slope_rad = (dzdx * dzdx + dzdy * dzdy).sqrt().atan();
slope[curr_row + x] = slope_rad.to_degrees();
let mut aspect_deg = dzdy.atan2(dzdx).to_degrees();
aspect_deg = 90.0 - aspect_deg;
if aspect_deg < 0.0 {
aspect_deg += 360.0;
}
aspect[curr_row + x] = aspect_deg;
}
}
for x in 0..width {
slope[x] = slope[width + x];
slope[(height - 1) * width + x] = slope[(height - 2) * width + x];
aspect[x] = aspect[width + x];
aspect[(height - 1) * width + x] = aspect[(height - 2) * width + x];
}
for y in 0..height {
slope[y * width] = slope[y * width + 1];
slope[y * width + width - 1] = slope[y * width + width - 2];
aspect[y * width] = aspect[y * width + 1];
aspect[y * width + width - 1] = aspect[y * width + width - 2];
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn terrain_hillshade_simd(
dem: &[f32],
hillshade: &mut [f32],
width: usize,
height: usize,
cell_size: f32,
azimuth: f32,
altitude: f32,
) -> Result<()> {
if width < 3 || height < 3 {
return Err(AlgorithmError::InvalidParameter {
parameter: "dimensions",
message: "Width and height must be at least 3".to_string(),
});
}
if cell_size <= 0.0 {
return Err(AlgorithmError::InvalidParameter {
parameter: "cell_size",
message: "Cell size must be positive".to_string(),
});
}
if dem.len() != width * height || hillshade.len() != width * height {
return Err(AlgorithmError::InvalidParameter {
parameter: "buffer_size",
message: "Buffer sizes must match width * height".to_string(),
});
}
let scale = 1.0 / (8.0 * cell_size);
let azimuth_rad = (360.0 - azimuth + 90.0).to_radians();
let altitude_rad = altitude.to_radians();
let zenith_rad = (90.0_f32).to_radians() - altitude_rad;
let sin_zenith = zenith_rad.sin();
let cos_zenith = zenith_rad.cos();
for y in 1..(height - 1) {
let prev_row = (y - 1) * width;
let curr_row = y * width;
let next_row = (y + 1) * width;
for x in 1..(width - 1) {
let dzdx = ((dem[prev_row + x + 1]
+ 2.0 * dem[curr_row + x + 1]
+ dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[curr_row + x - 1] + dem[next_row + x - 1]))
* scale;
let dzdy = ((dem[next_row + x - 1] + 2.0 * dem[next_row + x] + dem[next_row + x + 1])
- (dem[prev_row + x - 1] + 2.0 * dem[prev_row + x] + dem[prev_row + x + 1]))
* scale;
let slope_rad = (dzdx * dzdx + dzdy * dzdy).sqrt().atan();
let mut aspect_rad = dzdy.atan2(-dzdx);
if aspect_rad < 0.0 {
aspect_rad += 2.0 * core::f32::consts::PI;
}
let shade = cos_zenith * slope_rad.cos()
+ sin_zenith * slope_rad.sin() * (azimuth_rad - aspect_rad).cos();
let shade_value = (shade.clamp(0.0, 1.0) * 255.0).round();
hillshade[curr_row + x] = shade_value;
}
}
for x in 0..width {
hillshade[x] = hillshade[width + x];
hillshade[(height - 1) * width + x] = hillshade[(height - 2) * width + x];
}
for y in 0..height {
hillshade[y * width] = hillshade[y * width + 1];
hillshade[y * width + width - 1] = hillshade[y * width + width - 2];
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_terrain_slope_flat() {
let dem = vec![100.0_f32; 100]; let mut slope = vec![0.0_f32; 100];
terrain_slope_simd(&dem, &mut slope, 10, 10, 30.0)
.expect("terrain slope calculation should succeed");
for &val in &slope[11..89] {
assert_abs_diff_eq!(val, 0.0, epsilon = 0.1);
}
}
#[test]
fn test_terrain_aspect() {
let mut dem = vec![0.0_f32; 100];
for y in 0..10 {
for x in 0..10 {
dem[y * 10 + x] = x as f32 * 10.0;
}
}
let mut aspect = vec![0.0_f32; 100];
terrain_aspect_simd(&dem, &mut aspect, 10, 10, 1.0)
.expect("terrain aspect calculation should succeed");
for &val in &aspect[11..89] {
assert!((0.0..360.0).contains(&val));
}
}
#[test]
fn test_terrain_tpi() {
let mut dem = vec![100.0_f32; 100];
dem[55] = 150.0;
let mut tpi = vec![0.0_f32; 100];
terrain_tpi_simd(&dem, &mut tpi, 10, 10, 1)
.expect("terrain TPI calculation should succeed");
assert!(tpi[55] > 0.0);
}
#[test]
fn test_terrain_tri() {
let dem = vec![100.0_f32; 100]; let mut tri = vec![0.0_f32; 100];
terrain_tri_simd(&dem, &mut tri, 10, 10).expect("terrain TRI calculation should succeed");
for &val in &tri[11..89] {
assert_abs_diff_eq!(val, 0.0, epsilon = 0.01);
}
}
#[test]
fn test_terrain_roughness() {
let dem = vec![100.0_f32; 100]; let mut roughness = vec![0.0_f32; 100];
terrain_roughness_simd(&dem, &mut roughness, 10, 10, 1)
.expect("terrain roughness calculation should succeed");
for &val in &roughness {
assert_abs_diff_eq!(val, 0.0, epsilon = 0.01);
}
}
#[test]
fn test_invalid_dimensions() {
let dem = vec![100.0_f32; 4]; let mut slope = vec![0.0_f32; 4];
let result = terrain_slope_simd(&dem, &mut slope, 2, 2, 30.0);
assert!(result.is_err());
}
#[test]
fn test_invalid_cell_size() {
let dem = vec![100.0_f32; 100];
let mut slope = vec![0.0_f32; 100];
let result = terrain_slope_simd(&dem, &mut slope, 10, 10, 0.0);
assert!(result.is_err());
let result = terrain_slope_simd(&dem, &mut slope, 10, 10, -1.0);
assert!(result.is_err());
}
#[test]
fn test_terrain_curvature_flat() {
let dem = vec![100.0_f32; 100]; let mut curvature = vec![0.0_f32; 100];
terrain_curvature_simd(&dem, &mut curvature, 10, 10, 1.0, CurvatureTypeSIMD::Total)
.expect("terrain curvature calculation should succeed");
for &val in &curvature[11..89] {
assert_abs_diff_eq!(val, 0.0, epsilon = 0.1);
}
}
#[test]
fn test_terrain_vrm_flat() {
let dem = vec![100.0_f32; 100]; let mut vrm = vec![0.0_f32; 100];
terrain_vrm_simd(&dem, &mut vrm, 10, 10, 1.0, 1)
.expect("terrain VRM calculation should succeed");
for &val in &vrm {
assert!((0.0..=1.0).contains(&val));
}
}
#[test]
fn test_terrain_slope_aspect_combined() {
let dem = vec![100.0_f32; 100]; let mut slope = vec![0.0_f32; 100];
let mut aspect = vec![0.0_f32; 100];
terrain_slope_aspect_combined_simd(&dem, &mut slope, &mut aspect, 10, 10, 30.0)
.expect("combined slope/aspect calculation should succeed");
for &val in &slope[11..89] {
assert_abs_diff_eq!(val, 0.0, epsilon = 0.1);
}
}
#[test]
fn test_terrain_hillshade() {
let dem = vec![100.0_f32; 100]; let mut hillshade = vec![0.0_f32; 100];
terrain_hillshade_simd(&dem, &mut hillshade, 10, 10, 30.0, 315.0, 45.0)
.expect("terrain hillshade calculation should succeed");
for &val in &hillshade[11..89] {
assert!((0.0..=255.0).contains(&val));
}
}
}