use crate::{math::interpolate, noise_fns::NoiseFn};
use alloc::vec::Vec;
use core::marker::PhantomData;
pub struct Terrace<T, Source, const DIM: usize>
where
Source: NoiseFn<T, DIM>,
{
pub source: Source,
pub invert_terraces: bool,
control_points: Vec<f64>,
phantom: PhantomData<T>,
}
impl<T, Source, const DIM: usize> Terrace<T, Source, DIM>
where
Source: NoiseFn<T, DIM>,
{
pub fn new(source: Source) -> Self {
Terrace {
source,
invert_terraces: false,
control_points: Vec::with_capacity(2),
phantom: PhantomData,
}
}
pub fn add_control_point(mut self, control_point: f64) -> Self {
if !self
.control_points
.iter()
.any(|&x| (x - control_point).abs() < f64::EPSILON)
{
let insertion_point = self
.control_points
.iter()
.position(|&x| x >= control_point)
.unwrap_or(self.control_points.len());
self.control_points.insert(insertion_point, control_point);
}
Terrace { ..self }
}
pub fn invert_terraces(self, invert_terraces: bool) -> Self {
Terrace {
invert_terraces,
..self
}
}
}
impl<T, Source, const DIM: usize> NoiseFn<T, DIM> for Terrace<T, Source, DIM>
where
Source: NoiseFn<T, DIM>,
{
fn get(&self, point: [T; DIM]) -> f64 {
assert!(self.control_points.len() >= 2);
let source_value = self.source.get(point);
let index_pos = self
.control_points
.iter()
.position(|&x| x >= source_value)
.unwrap_or(self.control_points.len());
let index0 = clamp_index(index_pos as isize - 1, 0, self.control_points.len() - 1);
let index1 = clamp_index(index_pos as isize, 0, self.control_points.len() - 1);
if index0 == index1 {
return self.control_points[index1];
}
let mut input0 = self.control_points[index0];
let mut input1 = self.control_points[index1];
let mut alpha = (source_value - input0) / (input1 - input0);
if self.invert_terraces {
alpha = 1.0 - alpha;
core::mem::swap(&mut input0, &mut input1);
}
alpha *= alpha;
interpolate::linear(input0, input1, alpha)
}
}
fn clamp_index(index: isize, min: usize, max: usize) -> usize {
index.clamp(min as isize, max as isize) as usize
}