doryen_extra/
heightmap.rs

1/* BSD 3-Clause License
2 *
3 * Copyright © 2019, Alexander Krivács Schrøder <alexschrod@gmail.com>.
4 * Copyright © 2008-2019, Jice and the libtcod contributors.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright notice,
11 *    this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright notice,
14 *    this list of conditions and the following disclaimer in the documentation
15 *    and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the copyright holder nor the names of its
18 *    contributors may be used to endorse or promote products derived from
19 *    this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 */
33
34//! # Height map generation.
35//!
36//! This module provides a way to create a 2D grid of float values using various algorithms.
37
38use crate::noise::algorithms::Algorithm as NoiseAlgorithm;
39use crate::noise::Noise;
40use crate::random::algorithms::Algorithm as RandomAlgorithm;
41use crate::random::{Random, Rng};
42use crate::{FPosition, Position, UPosition};
43use ilyvion_util::non_nan::NonNan;
44use impl_ops::*;
45use std::ops::{self, AddAssign, MulAssign};
46
47/// A struct representing a height map.
48#[derive(Clone, Debug)]
49#[cfg_attr(
50    feature = "serialization",
51    derive(serde_derive::Serialize, serde_derive::Deserialize)
52)]
53pub struct HeightMap {
54    width: usize,
55    height: usize,
56    values: Vec<f32>,
57}
58
59impl HeightMap {
60    /// Returns a new height map with the given width and height. Initially, all the values of the
61    /// height map are `0.0`.
62    ///
63    /// # Panics
64    ///
65    /// If the `width` or the `height` is 0.
66    pub fn new(width: usize, height: usize) -> Self {
67        assert!(width > 0 && height > 0);
68
69        Self {
70            width,
71            height,
72            values: vec![0.0; width * height],
73        }
74    }
75
76    /// Returns a new height map with the given width and height, and a set of values.
77    ///
78    /// # Panics
79    ///
80    /// * If the `width` or the `height` is 0.
81    /// * If the length of `values` is not `width * height`.
82    pub fn new_with_values(width: usize, height: usize, values: &[f32]) -> Self {
83        assert!(width > 0 && height > 0);
84        assert_eq!(values.len(), width * height);
85
86        Self {
87            width,
88            height,
89            values: values.to_vec(),
90        }
91    }
92
93    /// Returns the width of the height map.
94    pub fn width(&self) -> usize {
95        self.width
96    }
97
98    /// Returns the height of the height map.
99    pub fn height(&self) -> usize {
100        self.height
101    }
102
103    /// Returns the values of the height map.
104    pub fn values(&self) -> &[f32] {
105        &self.values
106    }
107
108    /// Returns the values of the height map.
109    pub fn values_mut(&mut self) -> &mut [f32] {
110        &mut self.values
111    }
112
113    /// Returns the value of the height map at the given position.
114    ///
115    /// # Panics
116    ///
117    /// If the position is outside the range of the height map.
118    pub fn value(&self, position: UPosition) -> f32 {
119        self.get_value(position.x as usize, position.y as usize)
120    }
121
122    /// Sets the value of the height map at the given position.
123    ///
124    /// # Panics
125    ///
126    /// If the position is outside the range of the height map.
127    pub fn set_value(&mut self, position: UPosition, value: f32) {
128        self.values[position.x as usize + position.y as usize * self.width] = value;
129    }
130
131    /// Interpolates the value of the height map at the given position.
132    ///
133    /// # Panics
134    ///
135    /// If the position is outside the range of the height map.
136    pub fn interpolated_value(&self, position: FPosition) -> f32 {
137        let i_position = position.trunc_u();
138        if i_position.x as usize >= self.width - 1 || i_position.y as usize >= self.height - 1 {
139            self.value(i_position)
140        } else {
141            let dx = position.x - i_position.x as f32;
142            let dy = position.y - i_position.y as f32;
143            let c1 = self.value(i_position);
144            let c2 = self.value(i_position + (1, 0));
145            let c3 = self.value(i_position + (0, 1));
146            let c4 = self.value(i_position + (1, 1));
147            let top = (1.0 - dx) * c1 + dx * c2;
148            let bottom = (1.0 - dx) * c3 + dx * c4;
149
150            (1.0 - dy) * top + dy * bottom
151        }
152    }
153
154    /// Calculates the slope at the given position.
155    ///
156    /// # Panics
157    ///
158    /// If the position is outside the range of the height map.
159    pub fn slope(&self, position: UPosition) -> f32 {
160        const DIX: [i32; 8] = [-1, 0, 1, -1, 1, -1, 0, 1];
161        const DIY: [i32; 8] = [-1, -1, -1, 0, 0, 1, 1, 1];
162
163        let mut min_dy = 0.0;
164        let mut max_dy = 0.0;
165        let v = self.value(position);
166        for (nx, ny) in Iterator::zip(DIX.iter(), DIY.iter())
167            .map(|(&dx, &dy)| (position.x as i32 + dx, position.y as i32 + dy))
168        {
169            if nx >= 0 && nx < self.width as i32 && ny >= 0 && ny <= self.height as i32 {
170                let n_slope = self.get_value(nx as usize, ny as usize) - v;
171                if n_slope > max_dy {
172                    max_dy = n_slope;
173                } else if n_slope < min_dy {
174                    min_dy = n_slope;
175                }
176            }
177        }
178
179        (max_dy + min_dy).atan2(1.0)
180    }
181
182    /// Calculates the normal at the given position.
183    ///
184    /// # Panics
185    ///
186    /// If the position is outside the range of the height map.
187    pub fn normal(&self, position: FPosition, water_level: f32) -> [f32; 3] {
188        let mut n = [0.0, 0.0, 1.0];
189
190        if position.x >= self.width as f32 - 1.0 || position.y >= self.height as f32 - 1.0 {
191            return n;
192        }
193
194        let mut h0 = self.interpolated_value(position);
195        if h0 < water_level {
196            h0 = water_level;
197        }
198
199        let mut hx = self.interpolated_value(position + (1.0, 0.0));
200        if hx < water_level {
201            hx = water_level;
202        }
203
204        let mut hy = self.interpolated_value(position + (0.0, 1.0));
205        if hy < water_level {
206            hy = water_level;
207        }
208
209        n[0] = 255.0 * (h0 - hx);
210        n[1] = 255.0 * (h0 - hy);
211        n[2] = 16.0;
212
213        // normalize
214        let inv_len = 1.0 / (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
215        n[0] *= inv_len;
216        n[1] *= inv_len;
217        n[2] *= inv_len;
218
219        n
220    }
221
222    /// Returns the number of cells that have a height between `min` and `max`, inclusive.
223    pub fn count_cells(&self, min: f32, max: f32) -> usize {
224        self.values
225            .iter()
226            .filter(|&&v| v >= min && v <= max)
227            .count()
228    }
229
230    /// Returns whether there is any land along the edge of the height map. A result of `false`
231    /// implies that the map is an island.
232    pub fn has_land_on_border(&self, water_level: f32) -> bool {
233        for x in 0..self.width {
234            if self.get_value(x, 0) > water_level
235                || self.get_value(x, self.height - 1) > water_level
236            {
237                return true;
238            }
239        }
240        for y in 0..self.height {
241            if self.get_value(0, y) > water_level || self.get_value(self.width - 1, y) > water_level
242            {
243                return true;
244            }
245        }
246
247        false
248    }
249
250    /// Returns the lowest and highest height value in the height map.
251    pub fn min_max(&self) -> MinMax {
252        self.values
253            .iter()
254            .fold((std::f32::MAX, std::f32::MIN), |(min, max), &v| {
255                (min.min(v), max.max(v))
256            })
257            .into()
258    }
259
260    /// Clamps the values in the height map to be between `min` and `max`, inclusive.
261    ///
262    /// # Panics
263    ///
264    /// If `max` > `min`.
265    pub fn clamp(&mut self, min: f32, max: f32) {
266        assert!(min <= max);
267
268        self.values
269            .iter_mut()
270            .for_each(|v| *v = v.max(min).min(max))
271    }
272
273    /// Normalizes the values in the height map by scaling them proportionally such that the map's
274    /// current smallest value will be set to `min`, and its largest value will be set to `max`, and
275    /// all values in-between will be the same as they were, relative to these new end points.
276    ///
277    /// # Panics
278    ///
279    /// If `max` > `min`.
280    ///
281    /// # Examples
282    /// ```
283    /// # use doryen_extra::heightmap::HeightMap;
284    /// let mut hm = HeightMap::new_with_values(2, 5,
285    ///     &[-25.0, -15.0, -10.0, -5.0, 0.0, 10.0, 20.0, 30.0, 40.0, 50.0]);
286    /// hm.normalize(-25.0, 20.0);
287    /// assert_eq!(hm.values(), [
288    ///     -25.0, -19.0, -16.0, -13.0, -10.0, -4.0, 2.0, 8.0, 14.0, 20.0,
289    /// ]);
290    /// ```
291    pub fn normalize(&mut self, min: f32, max: f32) {
292        assert!(min <= max);
293
294        let MinMax {
295            min: cur_min,
296            max: cur_max,
297        } = self.min_max();
298
299        let inv_max = if cur_max - cur_min == 0.0 {
300            0.0
301        } else {
302            (f64::from(max) - f64::from(min)) / (f64::from(cur_max) - f64::from(cur_min))
303        };
304
305        // normalize
306        self.values.iter_mut().for_each(|v| {
307            *v = (f64::from(min) + (f64::from(*v) - f64::from(cur_min)) * inv_max) as f32
308        });
309    }
310
311    /// Resets all the values in the height map to `0.0`.
312    pub fn clear(&mut self) {
313        for v in &mut self.values {
314            *v = 0.0;
315        }
316    }
317
318    /// Linearly interpolate two height maps together.
319    pub fn lerp(&self, other: &Self, coefficient: f32) -> Self {
320        assert_eq!(self.width, other.width);
321        assert_eq!(self.height, other.height);
322        assert!(coefficient >= 0.0 && coefficient <= 1.0);
323
324        let mut result = Self::new(self.width, self.height);
325        for (v, (&sv, &ov)) in result
326            .values
327            .iter_mut()
328            .zip(Iterator::zip(self.values.iter(), other.values.iter()))
329        {
330            *v = sv + (ov - sv) * coefficient;
331        }
332
333        result
334    }
335
336    /// Adds a hill (a half spheroid) at the given position, with a `radius` and a `height`.
337    /// If `height == radius` or `-radius`, the hill will be a half-sphere.
338    pub fn add_hill(&mut self, position: FPosition, radius: f32, height: f32) {
339        let radius2 = radius * radius;
340        let coefficient = height / radius2;
341
342        let min_x = (position.x - radius).max(0.0) as usize;
343        let max_x = (position.x + radius).min(self.width as f32) as usize;
344        let min_y = (position.y - radius).max(0.0) as usize;
345        let max_y = (position.y + radius).min(self.height as f32) as usize;
346
347        for x in min_x..max_x {
348            let x_dist = (x as f32 - position.x) * (x as f32 - position.x);
349            for y in min_y..max_y {
350                let z = radius2 - x_dist - (y as f32 - position.y) * (y as f32 - position.y);
351                if z > 0.0 {
352                    *self.get_value_mut(x, y) += z * coefficient;
353                }
354            }
355        }
356    }
357
358    /// Takes the highest value (if `height > 0`) or the lowest (if `height < 0`) between the map
359    /// and the hill. Its main goal is to carve things into maps (like rivers) by digging hills
360    /// along a curve.
361    pub fn dig_hill(&mut self, position: FPosition, radius: f32, height: f32) {
362        let radius2 = radius * radius;
363        let coefficient = height / radius2;
364
365        let min_x = (position.x - radius).max(0.0) as usize;
366        let max_x = (position.x + radius).min(self.width as f32) as usize;
367        let min_y = (position.y - radius).max(0.0) as usize;
368        let max_y = (position.y + radius).min(self.height as f32) as usize;
369
370        for x in min_x..max_x {
371            let x_dist = (x as f32 - position.x) * (x as f32 - position.x);
372            for y in min_y..max_y {
373                let dist = x_dist + (y as f32 - position.y) * (y as f32 - position.y);
374                if dist < radius2 {
375                    let z = (radius2 - dist) * coefficient;
376                    let value = self.get_value_mut(x, y);
377                    if height > 0.0 {
378                        if *value < z {
379                            *value = z;
380                        }
381                    } else if *value > 0.0 {
382                        *value = z;
383                    }
384                }
385            }
386        }
387    }
388
389    /// Carves a path along a cubic Bezier curve using the `dig_hill` method. Could be used for
390    /// generating roads, rivers, etc. Both radius and depth can vary linearly along the path. The
391    /// four `positions` are the 4 Bezier control points.
392    pub fn dig_bezier(
393        &mut self,
394        positions: [UPosition; 4],
395        start_radius: f32,
396        start_depth: f32,
397        end_radius: f32,
398        end_depth: f32,
399    ) {
400        let mut x_from = positions[0].x as usize;
401        let mut y_from = positions[0].y as usize;
402
403        let mut t = 0.0_f32;
404        while t <= 1.0 {
405            let it = 1.0 - t;
406
407            let x_to = (positions[0].x as f32 * it * it * it
408                + 3.0 * positions[1].x as f32 * t * it * it
409                + 3.0 * positions[2].x as f32 * t * t * it
410                + positions[3].x as f32 * t * t * t) as usize;
411            let y_to = (positions[0].y as f32 * it * it * it
412                + 3.0 * positions[1].y as f32 * t * it * it
413                + 3.0 * positions[2].y as f32 * t * t * it
414                + positions[3].y as f32 * t * t * t) as usize;
415
416            if x_to != x_from || y_to != y_from {
417                let radius = start_radius + (end_radius - start_radius) * t;
418                let depth = start_depth + (end_depth - start_depth) * t;
419                self.dig_hill((x_to as f32, y_to as f32).into(), radius, depth);
420                x_from = x_to;
421                y_from = y_to;
422            }
423
424            t += 0.001;
425        }
426    }
427
428    /// Simulates the effect of rain drops on the terrain, resulting in erosion patterns.
429    ///
430    /// # Parameters
431    /// * `drops` - The number of rain drops to simulate. Should be at least `width * height`.
432    /// * `erosion_coefficient` - The amount of ground eroded on the drop's path.
433    /// * `aggregation_coefficient` - The amount of ground deposited when the drops stops to flow.
434    /// * `random` - The random number generator to use.
435    pub fn rain_erosion<A: RandomAlgorithm>(
436        &mut self,
437        mut drops: u32,
438        erosion_coefficient: f32,
439        aggregation_coefficient: f32,
440        random: &mut Random<A>,
441    ) {
442        const DX: [i32; 8] = [-1, 0, 1, -1, 1, -1, 0, 1];
443        const DY: [i32; 8] = [-1, -1, -1, 0, 0, 1, 1, 1];
444
445        while drops > 0 {
446            let mut cur_x = random.get_i32(0, (self.width - 1) as i32);
447            let mut cur_y = random.get_i32(0, (self.height - 1) as i32);
448            let mut slope;
449            let mut sediment = 0.0;
450
451            loop {
452                let mut next_x = 0;
453                let mut next_y = 0;
454                let v = self.get_value(cur_x as usize, cur_y as usize);
455                slope = 0.0;
456                for (nx, ny) in
457                    Iterator::zip(DX.iter(), DY.iter()).map(|(&dx, &dy)| (cur_x + dx, cur_y + dy))
458                {
459                    if nx >= 0 && nx < self.width as i32 && ny >= 0 && ny < self.height as i32 {
460                        let n_slope = v - self.get_value(nx as usize, ny as usize);
461                        if n_slope > slope {
462                            slope = n_slope;
463                            next_x = nx;
464                            next_y = ny;
465                        }
466                    }
467                }
468                if slope > 0.0 {
469                    *self.get_value_mut(cur_x as usize, cur_y as usize) -=
470                        erosion_coefficient * slope;
471                    cur_x = next_x;
472                    cur_y = next_y;
473                    sediment += slope;
474                } else {
475                    *self.get_value_mut(cur_x as usize, cur_y as usize) +=
476                        aggregation_coefficient * sediment;
477                }
478
479                if slope <= 0.0 {
480                    break;
481                }
482            }
483            drops -= 1;
484        }
485    }
486
487    /// Apply a generic transformation on the height map, so that each resulting cell value is the
488    /// weighted sum of several neighbour cells. This can be used to, e.g. smooth/sharpen the map.
489    ///
490    /// # Examples
491    /// Do simple horizontal smoothing with direct neighbor cells.
492    /// ```
493    /// # use doryen_extra::Position;
494    /// # use doryen_extra::heightmap::{HeightMap, NeighborCell};
495    /// let mut hm =
496    ///     HeightMap::new_with_values(3, 3, &[3.0, 6.0, 9.0, 12.0, 15.0, 18.0, 21.0, 24.0, 27.0]);
497    /// let cells = [
498    ///     NeighborCell { relative_position: Position::new(-1, 0), weight: 0.33 },
499    ///     NeighborCell { relative_position: Position::new(0, 0), weight: 0.33 },
500    ///     NeighborCell { relative_position: Position::new(1, 0), weight: 0.33 },
501    /// ];
502    /// hm.kernel_transform(&cells, 0.0, 100.0);
503    /// assert_eq!(hm.values(), &[4.5, 6.5, 7.75, 13.5, 15.5, 16.75, 22.5, 24.5, 25.75])
504    /// ```
505    pub fn kernel_transform(&mut self, cells: &[NeighborCell], min_level: f32, max_level: f32) {
506        for x in 0..self.width {
507            let mut offset = x;
508            for y in 0..self.height {
509                if self.values[offset] >= min_level && self.values[offset] <= max_level {
510                    let mut val = 0.0;
511                    let mut total_weight = 0.0;
512                    for cell in cells {
513                        let nx = x as i32 + cell.relative_position.x;
514                        let ny = y as i32 + cell.relative_position.y;
515                        if nx >= 0 && nx < self.width as i32 && ny >= 0 && ny < self.height as i32 {
516                            val += f64::from(cell.weight)
517                                * f64::from(self.get_value(nx as usize, ny as usize));
518                            total_weight += f64::from(cell.weight);
519                        }
520                    }
521                    self.values[offset] = (val / total_weight) as f32;
522                }
523                offset += self.width;
524            }
525        }
526    }
527
528    /// Adds values from a Voronoi diagram to the height map.
529    pub fn add_voronoi<A: RandomAlgorithm>(
530        &mut self,
531        sites: usize,
532        coefficients: &[f32],
533        random: &mut Random<A>,
534    ) {
535        struct Point {
536            x: i32,
537            y: i32,
538            dist: NonNan<f32>,
539        }
540
541        assert!(sites >= coefficients.len());
542
543        let mut points = Vec::with_capacity(sites);
544        for _ in 0..sites {
545            points.push(Point {
546                x: random.get_i32(0, (self.width - 1) as i32),
547                y: random.get_i32(0, (self.height - 1) as i32),
548                dist: 0.0.into(),
549            });
550        }
551        for x in 0..self.width {
552            let mut offset = x;
553            for y in 0..self.height {
554                // calculate distance to voronoi points
555                for point in &mut points {
556                    point.dist = ((point.x - x as i32) as f32 * (point.x - x as i32) as f32
557                        + (point.y - y as i32) as f32 * (point.y - y as i32) as f32)
558                        .into();
559                }
560                for coefficient in coefficients {
561                    let min_dist_point = points.iter_mut().min_by_key(|p| p.dist).unwrap();
562                    self.values[offset] += coefficient * *min_dist_point.dist;
563                    min_dist_point.dist = std::f32::MAX.into();
564                }
565                offset += self.width;
566            }
567        }
568    }
569
570    /// Generates a height map with mid-point displacement.
571    ///
572    /// The mid-point displacement algorithm generates a realistic fractal height map using the
573    /// diamond-square (aka random midpoint displacement) algorithm.
574    ///
575    /// The roughness range should be comprised between `0.4` and `0.6`.
576    ///
577    /// # Panics
578    ///
579    /// If the `width` or the `height` is 0.
580    pub fn mid_point_displacement<A: RandomAlgorithm>(
581        &mut self,
582        random: &mut Random<A>,
583        roughness: f32,
584    ) {
585        let mut step = 1;
586        let mut offset = 1.0;
587        let init_sz = self.width.min(self.height);
588        let mut sz = init_sz;
589        self.values[0] = random.get_f32(0.0, 1.0);
590        self.values[sz - 1] = random.get_f32(0.0, 1.0);
591        self.values[(sz - 1) * sz] = random.get_f32(0.0, 1.0);
592        self.values[sz * sz - 1] = random.get_f32(0.0, 1.0);
593        while sz > 0 {
594            // diamond step
595            for x in 0..step {
596                for y in 0..step {
597                    let diamond_x = sz / 2 + x * sz;
598                    let diamond_y = sz / 2 + y * sz;
599
600                    let mut z = self.get_value(x * sz, y * sz);
601                    z += self.get_value((x + 1) * sz, y * sz);
602                    z += self.get_value((x + 1) * sz, (y + 1) * sz);
603                    z += self.get_value(x * sz, (y + 1) * sz);
604                    z *= 0.25;
605
606                    self.set_mpd_height(random, diamond_x, diamond_y, z, offset);
607                }
608            }
609            offset *= roughness;
610
611            // square step
612            for x in 0..step {
613                for y in 0..step {
614                    let diamond_x = sz / 2 + x * sz;
615                    let diamond_y = sz / 2 + y * sz;
616
617                    // north
618                    self.set_mdp_height_square(
619                        random,
620                        diamond_x,
621                        diamond_y - sz / 2,
622                        init_sz,
623                        sz / 2,
624                        offset,
625                    );
626                    // south
627                    self.set_mdp_height_square(
628                        random,
629                        diamond_x,
630                        diamond_y + sz / 2,
631                        init_sz,
632                        sz / 2,
633                        offset,
634                    );
635                    // west
636                    self.set_mdp_height_square(
637                        random,
638                        diamond_x - sz / 2,
639                        diamond_y,
640                        init_sz,
641                        sz / 2,
642                        offset,
643                    );
644                    // east
645                    self.set_mdp_height_square(
646                        random,
647                        diamond_x + sz / 2,
648                        diamond_y,
649                        init_sz,
650                        sz / 2,
651                        offset,
652                    );
653                }
654            }
655            sz /= 2;
656            step *= 2;
657        }
658    }
659
660    /// Add an FBM to the height map.
661    ///
662    /// The noise value for map cell `(x, y)` is `(x + add_x) * mul_x / width` and
663    /// `(y + add_y) * mul_y / height`, respectively. Those values allow you to scale and translate
664    /// the noise function over the height map.
665    ///
666    /// # Panics
667    ///
668    /// If the `noise` provided isn't 2D.
669    pub fn add_fbm<A: NoiseAlgorithm>(
670        &mut self,
671        noise: &mut Noise<A>,
672        octaves: f32,
673        coordinates: FbmCoordinateParameters,
674        delta: f32,
675        scale: f32,
676    ) {
677        assert_eq!(
678            noise.dimensions, 2,
679            "add_fbm requires a 2D noise generator."
680        );
681
682        let x_coefficient = coordinates.mul_x / self.width as f32;
683        let y_coefficient = coordinates.mul_y / self.height as f32;
684
685        for x in 0..self.width {
686            let mut f = [0.0; 2];
687            let mut offset = x;
688            f[0] = (x as f32 + coordinates.add_x) * x_coefficient;
689            for y in 0..self.height {
690                f[1] = (y as f32 + coordinates.add_y) * y_coefficient;
691                let value = delta + noise.fbm(&f, octaves) * scale;
692                self.values[offset] += value;
693                offset += self.width;
694            }
695        }
696    }
697
698    /// Scale the map by an FBM.
699    ///
700    /// The noise coordinate for map cell `(x, y)` is `(x + add_x) * mul_x / width` and
701    /// `(y + add_y) * mul_y / height`, respectively. Those values allow you to scale and translate
702    /// the noise function over the height map.
703    ///
704    /// The value multiplied with the height map is `delta + noise * scale`.
705    ///
706    /// # Panics
707    ///
708    /// If the `noise` generator provided isn't 2D.
709    pub fn scale_fbm<A: NoiseAlgorithm>(
710        &mut self,
711        noise: &mut Noise<A>,
712        coordinates: FbmCoordinateParameters,
713        octaves: f32,
714        delta: f32,
715        scale: f32,
716    ) {
717        assert_eq!(
718            noise.dimensions, 2,
719            "scale_fbm requires a 2D noise generator."
720        );
721
722        let x_coefficient = coordinates.mul_x / self.width as f32;
723        let y_coefficient = coordinates.mul_y / self.height as f32;
724
725        for x in 0..self.width {
726            let mut f = [0.0; 2];
727            let mut offset = x;
728            f[0] = (x as f32 + coordinates.add_x) * x_coefficient;
729            for y in 0..self.height {
730                f[1] = (y as f32 + coordinates.add_y) * y_coefficient;
731                let value = delta + noise.fbm(&f, octaves) * scale;
732                self.values[offset] *= value;
733                offset += self.width;
734            }
735        }
736    }
737
738    #[inline]
739    fn get_value(&self, x: usize, y: usize) -> f32 {
740        assert!(x < self.width);
741        assert!(y < self.height);
742
743        self.values[x + y * self.width]
744    }
745
746    #[inline]
747    fn get_value_mut(&mut self, x: usize, y: usize) -> &mut f32 {
748        assert!(x < self.width);
749        assert!(y < self.height);
750
751        &mut self.values[x + y * self.width]
752    }
753
754    fn set_mdp_height_square<A: RandomAlgorithm>(
755        &mut self,
756        random: &mut Random<A>,
757        x: usize,
758        y: usize,
759        init_sz: usize,
760        sz: usize,
761        offset: f32,
762    ) {
763        let mut z = 0.0;
764        let mut count = 0;
765        if y >= sz {
766            z += self.get_value(x, y - sz);
767            count += 1;
768        }
769        if x >= sz {
770            z += self.get_value(x - sz, y);
771            count += 1;
772        }
773        if y + sz < init_sz {
774            z += self.get_value(x, y + sz);
775            count += 1;
776        }
777        if x + sz < init_sz {
778            z += self.get_value(x + sz, y);
779            count += 1;
780        }
781        z /= count as f32;
782        self.set_mpd_height(random, x, y, z, offset);
783    }
784
785    fn set_mpd_height<A: RandomAlgorithm>(
786        &mut self,
787        random: &mut Random<A>,
788        x: usize,
789        y: usize,
790        mut z: f32,
791        offset: f32,
792    ) {
793        z += random.get_f32(-offset, offset);
794        *self.get_value_mut(x, y) = z;
795    }
796}
797
798impl_op_ex!(+ |a: &HeightMap, b: &HeightMap| -> HeightMap {
799    assert_eq!(a.width, b.width);
800    assert_eq!(a.height, b.height);
801
802    let mut result = a.clone();
803    for (r, &o) in result.values.iter_mut().zip(b.values.iter()) {
804        *r += o
805    }
806
807    result
808});
809
810impl AddAssign<f32> for HeightMap {
811    fn add_assign(&mut self, rhs: f32) {
812        self.values.iter_mut().for_each(|v| *v += rhs);
813    }
814}
815
816impl_op_ex!(*|a: &HeightMap, b: &HeightMap| -> HeightMap {
817    assert_eq!(a.width, b.width);
818    assert_eq!(a.height, b.height);
819
820    let mut result = a.clone();
821    for (r, &o) in result.values.iter_mut().zip(b.values.iter()) {
822        *r *= o
823    }
824
825    result
826});
827
828impl MulAssign<f32> for HeightMap {
829    fn mul_assign(&mut self, rhs: f32) {
830        self.values.iter_mut().for_each(|v| *v *= rhs);
831    }
832}
833
834/// Represents a result of minimum and maximum values in a height map.
835#[derive(Copy, Clone, Debug)]
836pub struct MinMax {
837    /// The minimum value.
838    pub min: f32,
839    /// The maximum value.
840    pub max: f32,
841}
842
843impl From<(f32, f32)> for MinMax {
844    fn from((min, max): (f32, f32)) -> Self {
845        Self { min, max }
846    }
847}
848
849/// Represents a neighbor cell in the kernel transformation method.
850#[derive(Copy, Clone, Debug)]
851pub struct NeighborCell {
852    /// Which map cell this transform takes its value from, relative to the current cell. That is to
853    /// say, if the transform is currently working on cell (5, 2) and this field is (-1, 1), it will
854    /// get its value from (4, 3).
855    pub relative_position: Position,
856
857    /// What to scale this neighbor cell's value by when calculating the new value of the
858    /// current cell.
859    pub weight: f32,
860}
861
862/// Represents the coordinates used in the `*_fbm` methods.
863#[derive(Copy, Clone, Debug)]
864pub struct FbmCoordinateParameters {
865    /// See the `*_fbm` methods for details on how this parameter is used.
866    pub mul_x: f32,
867    /// See the `*_fbm` methods for details on how this parameter is used.
868    pub mul_y: f32,
869    /// See the `*_fbm` methods for details on how this parameter is used.
870    pub add_x: f32,
871    /// See the `*_fbm` methods for details on how this parameter is used.
872    pub add_y: f32,
873}