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 math::{clamp, interpolate};
use noise_fns::NoiseFn;
use std;

/// Noise function that maps the output value from the source function onto a
/// terrace-forming curve.
///
/// This noise function maps the output value from the source function onto a
/// terrace-forming curve. The start of the curve has a slode of zero; it's
/// slope then smoothly increases. This curve also contains _control points_
/// which resets the slope to zero at that point, producing a "terracing"
/// effect.
///
/// To add control points to the curve, use the `add_control_point` method.
///
/// An application must add a minimum of two control points to the curve. If
/// there are less than two control points, the get() method panics. The
/// control points can have any value, although no two control points can
/// have the same value. There is no limit to the number of control points
/// that can be added to the curve.
///
/// The noise function clamps the output value from the source function if that
/// value is less than the value of the lowest control point or greater than
/// the value of the highest control point.
///
/// This noise function is often used to generate terrain features such as the
/// stereotypical desert canyon.
pub struct Terrace<'a, T: 'a> {
    /// Outputs a value.
    pub source: &'a dyn NoiseFn<T>,

    /// Determines if the terrace-forming curve between all control points is
    /// inverted.
    pub invert_terraces: bool,

    /// Vec that stores the control points.
    control_points: Vec<f64>,
}

impl<'a, T> Terrace<'a, T> {
    pub fn new(source: &'a dyn NoiseFn<T>) -> Self {
        Terrace {
            source,
            invert_terraces: false,
            control_points: Vec::with_capacity(2),
        }
    }

    /// Adds a control point to the terrace-forming curve.
    ///
    /// Two or more control points define the terrace-forming curve. The start
    /// of this curve has a slope of zero; its slope then smoothly increases.
    /// At the control points, its slope resets to zero.
    ///
    /// It does not matter which order these points are added in.
    pub fn add_control_point(mut self, control_point: f64) -> Self {
        // check to see if the vector already contains the input point.
        if !self
            .control_points
            .iter()
            .any(|&x| (x - control_point).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 >= control_point)
                .unwrap_or_else(|| self.control_points.len());

            // add the new control point at the correct position.
            self.control_points.insert(insertion_point, control_point);
        }

        // create new Terrace with updated control_points vector
        Terrace { ..self }
    }

    /// Enables or disables the inversion of the terrain-forming curve between
    /// the control points.
    pub fn invert_terraces(self, invert_terraces: bool) -> Self {
        Terrace {
            invert_terraces,
            ..self
        }
    }
}

impl<'a, T> NoiseFn<T> for Terrace<'a, T> {
    fn get(&self, point: T) -> f64 {
        // confirm that there's at least 2 control points in the vector.
        assert!(self.control_points.len() >= 2);

        // 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 >= source_value)
            .unwrap_or_else(|| self.control_points.len());

        // Find the two nearest control points so that we can map their values
        // onto a quadratic curve.
        let index0 = clamp_index(index_pos as isize - 1, 0, self.control_points.len() - 1);
        let index1 = clamp_index(index_pos as isize, 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 index0 == index1 {
            return self.control_points[index1];
        }

        // Compute the alpha value used for cubic interpolation
        let mut input0 = self.control_points[index0];
        let mut input1 = self.control_points[index1];
        let mut alpha = (source_value - input0) / (input1 - input0);

        if self.invert_terraces {
            alpha = 1.0 - alpha;
            std::mem::swap(&mut input0, &mut input1);
        }

        // Squaring the alpha produces the terrace effect.
        alpha *= alpha;

        // Now perform the cubic interpolation and return.
        interpolate::linear(input0, input1, alpha)
    }
}

fn clamp_index(index: isize, min: usize, max: usize) -> usize {
    clamp(index, min as isize, max as isize) as usize
}