fn multilinear_interpolation_weights<const N: usize>(
point: &[f32; N],
starts: &[f32; N],
ends: &[f32; N],
) -> [f32; N]
{
let mut weights = [0.0; N];
for i in 0..N
{
weights[i] = (point[i] - starts[i]) / (ends[i] - starts[i]);
}
weights
}
fn grid_coordinates<const N: usize>(
point: &[f32; N],
corner: &[f32; N],
extents: &[f32; N],
step_size: f32,
) -> [usize; N]
{
let mut coords = [0; N];
for i in 0..N
{
coords[i] = ((point[i] - corner[i]) / (step_size)) as usize;
coords[i] = coords[i].max(0).min((extents[i] / step_size) as usize - 1);
}
coords
}
pub fn sample_multilinear<const N: usize>(
point: &[f32; N],
corner: &[f32; N],
extents: &[f32; N],
step_size: f32,
data: &[f32],
) -> f32
{
let mut resolutions = [0; N];
for i in 0..N
{
resolutions[i] = (extents[i] / step_size) as usize;
}
assert_eq!(resolutions.iter().copied().product::<usize>(), data.len());
sample_multilinear_with_resolutions(point, corner, extents, &resolutions, data)
}
pub fn sample_multilinear_with_resolutions<const N: usize>(
point: &[f32; N],
corner: &[f32; N],
extents: &[f32; N],
resolutions: &[usize; N],
data: &[f32],
) -> f32
{
let mut max_extent = extents[0];
for f in extents.iter().skip(1)
{
max_extent = max_extent.max(*f);
}
let largest_index = extents.iter().position(|f| *f == max_extent).unwrap();
let step_size = extents[largest_index] / resolutions[largest_index] as f32;
let coords = grid_coordinates(point, corner, extents, step_size);
let mut starts = [0.0; N];
for i in 0..N
{
starts[i] = coords[i] as f32 * step_size + corner[i];
}
let mut ends = [0.0; N];
for i in 0..N
{
ends[i] = (coords[i] + 1) as f32 * step_size + corner[i];
}
let weights = multilinear_interpolation_weights(point, &starts, &ends);
let corner_total = 1 << N;
let mut result = 0.0;
for corner in 0..corner_total
{
let mut coord = coords;
let mut polynomial = 1.0;
for bit_index in 0..N
{
let bit = ((corner >> (N - 1 - bit_index)) & 1) as i32;
coord[bit_index] += bit as usize;
coord[bit_index] = coord[bit_index].clamp(0, resolutions[bit_index] - 1);
polynomial *= if bit == 1
{
weights[bit_index]
}
else
{
1.0 - weights[bit_index]
};
}
let mut index = 0;
for i in (0..N).rev()
{
index *= resolutions[i];
index += coord[i];
}
result += data[index] * polynomial;
}
result
}
#[cfg(test)]
mod tests
{
use image::{ImageBuffer, Rgba, RgbaImage};
use misc_iterators::IterDimensions;
use noise::perlin;
use super::sample_multilinear;
#[test]
fn test_2d_sampling()
{
let resolution = 64;
let function_sampling = |x: f32, y: f32| {
let value = perlin(&[x, y], 0);
value
};
let mut img: RgbaImage = ImageBuffer::new(resolution as u32, resolution as u32);
let mut image_data = Vec::with_capacity(resolution * resolution);
for [i, j] in IterDimensions::new([resolution, resolution])
{
let value = function_sampling(
i as f32 * 4.0 / resolution as f32,
j as f32 * 4.0 / resolution as f32,
);
let pixel = (value) * 255.0;
image_data.push(pixel);
let pixel = pixel as u8;
img.put_pixel(i as u32, j as u32, Rgba::<u8>([pixel, pixel, pixel, 255]));
}
img.save("input_test.png").unwrap();
let out_res = 512;
let mut img: RgbaImage = ImageBuffer::new(out_res as u32, out_res as u32);
for [i, j] in IterDimensions::new([out_res, out_res])
{
let x = i as f32 / out_res as f32;
let y = j as f32 / out_res as f32;
let value = sample_multilinear(
&[x, y],
&[0.0, 0.0],
&[1.0, 1.0],
1.0 / resolution as f32,
&image_data,
);
let pixel = value as u8;
img.put_pixel(i as u32, j as u32, Rgba::<u8>([pixel, pixel, pixel, 255]));
}
img.save("output_test.png").unwrap();
}
}