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}