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
use crate::{
math::{self, interpolate},
noise_fns::NoiseFn,
};
/// 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.
pub struct Curve<'a, T> {
/// Outputs a value.
pub source: &'a dyn NoiseFn<T>,
/// Vec that stores the control points.
control_points: Vec<ControlPoint<f64>>,
}
struct ControlPoint<T> {
input: T,
output: T,
}
impl<'a, T> Curve<'a, T> {
pub fn new(source: &'a dyn NoiseFn<T>) -> Self {
Self {
source,
control_points: Vec::with_capacity(4),
}
}
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() < std::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_else(|| 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<'a, T> NoiseFn<T> for Curve<'a, T> {
fn get(&self, point: T) -> 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_else(|| 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 = math::clamp(index_pos, 2, self.control_points.len());
// Find the four nearest control points so that we can perform cubic
// interpolation.
let index0 = math::clamp(index_pos - 2, 0, self.control_points.len() - 1);
let index1 = math::clamp(index_pos - 1, 0, self.control_points.len() - 1);
let index2 = math::clamp(index_pos, 0, self.control_points.len() - 1);
let index3 = math::clamp(index_pos + 1, 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,
)
}
}