1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
use crate::{math::interpolate, noise_fns::NoiseFn};
use alloc::vec::Vec;
use core::marker::PhantomData;
/// Noise function that maps the output value from the source function onto an
/// arbitrary function curve.
///
/// This noise function maps the output value from the source function onto an
/// application-defined curve. The curve is defined by a number of _control
/// points_; each control point has an _input value_ that maps to an _output
/// value_.
///
/// To add control points to the curve, use the `add_control_point` method.
///
/// Since the curve is a cubic spline, an application must have a minimum of
/// four control points to the curve. If there is less than four control
/// points, the get() method panics. Each control point can have any input
/// and output value, although no two control points can have the same input.
#[derive(Clone)]
pub struct Curve<T, Source, const DIM: usize>
where
Source: NoiseFn<T, DIM>,
{
/// Outputs a value.
pub source: Source,
/// Vec that stores the control points.
control_points: Vec<ControlPoint<f64>>,
phantom: PhantomData<T>,
}
#[derive(Clone)]
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 {
// check to see if the vector already contains the input point.
if !self
.control_points
.iter()
.any(|x| (x.input - input_value).abs() < f64::EPSILON)
{
// it doesn't, so find the correct position to insert the new
// control point.
let insertion_point = self
.control_points
.iter()
.position(|x| x.input >= input_value)
.unwrap_or(self.control_points.len());
// add the new control point at the correct position.
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 {
// confirm that there's at least 4 control points in the vector.
assert!(self.control_points.len() >= 4);
// get output value from the source function
let source_value = self.source.get(point);
// Find the first element in the control point array that has a input
// value larger than the output value from the source function
let index_pos = self
.control_points
.iter()
.position(|x| x.input > source_value)
.unwrap_or(self.control_points.len());
// if index_pos < 2 {
// println!(
// "index_pos in curve was less than 2! source value was {}",
// source_value
// );
// }
// ensure that the index is at least 2 and less than control_points.len()
let index_pos = index_pos.clamp(2, self.control_points.len());
// Find the four nearest control points so that we can perform cubic
// interpolation.
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 some control points are missing (which occurs if the value from
// the source function is greater than the largest input value or less
// than the smallest input value of the control point array), get the
// corresponding output value of the nearest control point and exit.
if index1 == index2 {
return self.control_points[index1].output;
}
// Compute the alpha value used for cubic interpolation
let input0 = self.control_points[index1].input;
let input1 = self.control_points[index2].input;
let alpha = (source_value - input0) / (input1 - input0);
// Now perform the cubic interpolation and return.
interpolate::cubic(
self.control_points[index0].output,
self.control_points[index1].output,
self.control_points[index2].output,
self.control_points[index3].output,
alpha,
)
}
}