use crate::{math::interpolate, noise_fns::NoiseFn};
use alloc::vec::Vec;
use core::marker::PhantomData;
pub struct Curve<T, Source, const DIM: usize>
where
Source: NoiseFn<T, DIM>,
{
pub source: Source,
control_points: Vec<ControlPoint<f64>>,
phantom: PhantomData<T>,
}
struct ControlPoint<T> {
input: T,
output: T,
}
impl<T, Source, const DIM: usize> Curve<T, Source, DIM>
where
Source: NoiseFn<T, DIM>,
{
pub fn new(source: Source) -> Self {
Self {
source,
control_points: Vec::with_capacity(4),
phantom: PhantomData,
}
}
pub fn add_control_point(mut self, input_value: f64, output_value: f64) -> Self {
if !self
.control_points
.iter()
.any(|x| (x.input - input_value).abs() < f64::EPSILON)
{
let insertion_point = self
.control_points
.iter()
.position(|x| x.input >= input_value)
.unwrap_or(self.control_points.len());
self.control_points.insert(
insertion_point,
ControlPoint {
input: input_value,
output: output_value,
},
);
}
self
}
}
impl<T, Source, const DIM: usize> NoiseFn<T, DIM> for Curve<T, Source, DIM>
where
Source: NoiseFn<T, DIM>,
{
fn get(&self, point: [T; DIM]) -> f64 {
assert!(self.control_points.len() >= 4);
let source_value = self.source.get(point);
let index_pos = self
.control_points
.iter()
.position(|x| x.input > source_value)
.unwrap_or(self.control_points.len());
let index_pos = index_pos.clamp(2, self.control_points.len());
let index0 = (index_pos - 2).clamp(0, self.control_points.len() - 1);
let index1 = (index_pos - 1).clamp(0, self.control_points.len() - 1);
let index2 = index_pos.clamp(0, self.control_points.len() - 1);
let index3 = (index_pos + 1).clamp(0, self.control_points.len() - 1);
if index1 == index2 {
return self.control_points[index1].output;
}
let input0 = self.control_points[index1].input;
let input1 = self.control_points[index2].input;
let alpha = (source_value - input0) / (input1 - input0);
interpolate::cubic(
self.control_points[index0].output,
self.control_points[index1].output,
self.control_points[index2].output,
self.control_points[index3].output,
alpha,
)
}
}