noice/noise_fns/modifiers/
terrace.rs

1use crate::{
2    math::{clamp, interpolate},
3    noise_fns::NoiseFn,
4};
5
6/// Noise function that maps the output value from the source function onto a
7/// terrace-forming curve.
8///
9/// This noise function maps the output value from the source function onto a
10/// terrace-forming curve. The start of the curve has a slode of zero; it's
11/// slope then smoothly increases. This curve also contains _control points_
12/// which resets the slope to zero at that point, producing a "terracing"
13/// effect.
14///
15/// To add control points to the curve, use the `add_control_point` method.
16///
17/// An application must add a minimum of two control points to the curve. If
18/// there are less than two control points, the get() method panics. The
19/// control points can have any value, although no two control points can
20/// have the same value. There is no limit to the number of control points
21/// that can be added to the curve.
22///
23/// The noise function clamps the output value from the source function if that
24/// value is less than the value of the lowest control point or greater than
25/// the value of the highest control point.
26///
27/// This noise function is often used to generate terrain features such as the
28/// stereotypical desert canyon.
29pub struct Terrace<'a, T> {
30    /// Outputs a value.
31    pub source: &'a dyn NoiseFn<T>,
32
33    /// Determines if the terrace-forming curve between all control points is
34    /// inverted.
35    pub invert_terraces: bool,
36
37    /// Vec that stores the control points.
38    control_points: Vec<f64>,
39}
40
41impl<'a, T> Terrace<'a, T> {
42    pub fn new(source: &'a dyn NoiseFn<T>) -> Self {
43        Terrace {
44            source,
45            invert_terraces: false,
46            control_points: Vec::with_capacity(2),
47        }
48    }
49
50    /// Adds a control point to the terrace-forming curve.
51    ///
52    /// Two or more control points define the terrace-forming curve. The start
53    /// of this curve has a slope of zero; its slope then smoothly increases.
54    /// At the control points, its slope resets to zero.
55    ///
56    /// It does not matter which order these points are added in.
57    pub fn add_control_point(mut self, control_point: f64) -> Self {
58        // check to see if the vector already contains the input point.
59        if !self
60            .control_points
61            .iter()
62            .any(|&x| (x - control_point).abs() < std::f64::EPSILON)
63        {
64            // it doesn't, so find the correct position to insert the new
65            // control point.
66            let insertion_point = self
67                .control_points
68                .iter()
69                .position(|&x| x >= control_point)
70                .unwrap_or_else(|| self.control_points.len());
71
72            // add the new control point at the correct position.
73            self.control_points.insert(insertion_point, control_point);
74        }
75
76        // create new Terrace with updated control_points vector
77        Terrace { ..self }
78    }
79
80    /// Enables or disables the inversion of the terrain-forming curve between
81    /// the control points.
82    pub fn invert_terraces(self, invert_terraces: bool) -> Self {
83        Terrace {
84            invert_terraces,
85            ..self
86        }
87    }
88}
89
90impl<'a, T> NoiseFn<T> for Terrace<'a, T> {
91    fn get(&self, point: T) -> f64 {
92        // confirm that there's at least 2 control points in the vector.
93        assert!(self.control_points.len() >= 2);
94
95        // get output value from the source function
96        let source_value = self.source.get(point);
97
98        // Find the first element in the control point array that has a input
99        // value larger than the output value from the source function
100        let index_pos = self
101            .control_points
102            .iter()
103            .position(|&x| x >= source_value)
104            .unwrap_or_else(|| self.control_points.len());
105
106        // Find the two nearest control points so that we can map their values
107        // onto a quadratic curve.
108        let index0 = clamp_index(index_pos as isize - 1, 0, self.control_points.len() - 1);
109        let index1 = clamp_index(index_pos as isize, 0, self.control_points.len() - 1);
110
111        // If some control points are missing (which occurs if the value from
112        // the source function is greater than the largest input value or less
113        // than the smallest input value of the control point array), get the
114        // corresponding output value of the nearest control point and exit.
115        if index0 == index1 {
116            return self.control_points[index1];
117        }
118
119        // Compute the alpha value used for cubic interpolation
120        let mut input0 = self.control_points[index0];
121        let mut input1 = self.control_points[index1];
122        let mut alpha = (source_value - input0) / (input1 - input0);
123
124        if self.invert_terraces {
125            alpha = 1.0 - alpha;
126            std::mem::swap(&mut input0, &mut input1);
127        }
128
129        // Squaring the alpha produces the terrace effect.
130        alpha *= alpha;
131
132        // Now perform the cubic interpolation and return.
133        interpolate::linear(input0, input1, alpha)
134    }
135}
136
137fn clamp_index(index: isize, min: usize, max: usize) -> usize {
138    clamp(index, min as isize, max as isize) as usize
139}