ds_heightmap/
lib.rs

1#![doc = include_str!("../README.md")]
2
3#[cfg(target_arch = "wasm32")]
4use js_sys::{Array, Object, Reflect};
5use rand::Rng;
6use rand_distr::{Beta, Distribution};
7#[cfg(target_arch = "wasm32")]
8use wasm_bindgen::prelude::{wasm_bindgen, JsValue};
9
10#[cfg(target_arch = "wasm32")]
11#[global_allocator]
12static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
13
14/// The output of the algorithm.
15pub struct Output {
16    /// The height data of every pixels.
17    pub data: Vec<Vec<f32>>,
18    /// The maximum height.
19    pub max: f32,
20    /// The minimum height.
21    pub min: f32,
22}
23
24/// A runner to run the algorithm.
25#[cfg(not(target_arch = "wasm32"))]
26pub struct Runner {
27    data: Vec<Vec<f32>>,
28    max: f32,
29    min: f32,
30    width: usize,
31    height: usize,
32    depth: f32,
33    rough: f32,
34    side: usize,
35}
36
37#[cfg(target_arch = "wasm32")]
38#[wasm_bindgen]
39pub struct Runner {
40    data: Vec<Vec<f32>>,
41    max: f32,
42    min: f32,
43    width: usize,
44    height: usize,
45    depth: f32,
46    rough: f32,
47    side: usize,
48}
49
50static DEFAULT_WIDTH: usize = 129;
51static DEFAULT_HEIGHT: usize = 129;
52static DEFAULT_DEPTH: f32 = 2000.0;
53static DEFAULT_ROUGH: f32 = 1.0;
54
55#[cfg(target_arch = "wasm32")]
56#[wasm_bindgen]
57impl Runner {
58    #[wasm_bindgen(constructor)]
59    pub fn wasm_new(width: usize, height: usize, depth: Option<f32>, rough: Option<f32>) -> Self {
60        let mut runner = Self::new();
61        runner.set_width(width);
62        runner.set_height(height);
63        if depth.is_some() {
64            runner.set_depth(depth.unwrap());
65        }
66        if rough.is_some() {
67            runner.set_rough(rough.unwrap());
68        }
69        runner
70    }
71
72    #[wasm_bindgen(js_name = ds)]
73    pub fn wasm_ds(&mut self) -> Result<Object, JsValue> {
74        let output = self.ds();
75
76        let data = Array::from(&JsValue::from(
77            output
78                .data
79                .into_iter()
80                .map(|x| {
81                    x.into_iter()
82                        .map(|v| JsValue::from_f64(v as f64))
83                        .collect::<Array>()
84                })
85                .collect::<Array>(),
86        ));
87
88        let obj = Object::new();
89        Reflect::set(&obj, &"data".into(), &data.into())?;
90        Reflect::set(&obj, &"max".into(), &output.max.into())?;
91        Reflect::set(&obj, &"min".into(), &output.min.into())?;
92        Ok(obj)
93    }
94}
95
96impl Runner {
97    /// Constuct a new Runner.
98    pub fn new() -> Self {
99        let mut runner = Self {
100            data: Vec::new(),
101            max: f32::MIN,
102            min: f32::MAX,
103            width: 0,
104            height: 0,
105            depth: DEFAULT_DEPTH,
106            rough: DEFAULT_ROUGH,
107            side: 0,
108        };
109        runner.set_width(DEFAULT_WIDTH);
110        runner.set_height(DEFAULT_HEIGHT);
111        runner
112    }
113
114    /// Run the Diamond-square algorithm with a default rng.
115    /// Use [rand::rngs::ThreadRng](https://rust-random.github.io/rand/rand/rngs/struct.ThreadRng.html) internally.
116    pub fn ds(&mut self) -> Output {
117        self.ds_with_rng(&mut rand::thread_rng())
118    }
119
120    /// Run the Diamond-square algorithm with given rng.
121    pub fn ds_with_rng(&mut self, rng: &mut impl Rng) -> Output {
122        let beta = Beta::new(3.0, 3.0).unwrap();
123        let p = self.side - 1;
124        self.data[0][0] = beta.sample(rng) * self.depth;
125        self.data[0][p] = beta.sample(rng) * self.depth;
126        self.data[p][0] = beta.sample(rng) * self.depth;
127        self.data[p][p] = beta.sample(rng) * self.depth;
128
129        self.shape(self.side as f32, self.side as f32, rng);
130
131        if self.data.len() != self.width {
132            self.data.truncate(self.width);
133        }
134        if self.data[0].len() != self.height {
135            for i in 0..self.width {
136                self.data[i].truncate(self.height);
137            }
138        }
139
140        Output {
141            data: self.data.clone(),
142            max: self.max,
143            min: self.min,
144        }
145    }
146
147    /// Set the depth.
148    /// The value of each pixel will be within 0~depth.
149    /// Default: 2000.0.
150    pub fn set_depth(&mut self, depth: f32) {
151        if depth >= 0.0 && depth != self.depth {
152            self.depth = depth;
153        }
154    }
155
156    /// Set the height of the map.
157    /// Must be larger than 1.
158    /// Default: 129.
159    pub fn set_height(&mut self, height: usize) {
160        if height >= 2 && height != self.height {
161            self.height = height;
162            if self.height > self.width {
163                self.set_side(self.height);
164            }
165        }
166    }
167
168    /// Set the roughness.
169    /// Default: 1.0.
170    pub fn set_rough(&mut self, rough: f32) {
171        if rough != self.rough {
172            self.rough = rough;
173        }
174    }
175
176    /// Set the width of the map.
177    /// Must be larger than 1.
178    /// Default: 129.
179    pub fn set_width(&mut self, width: usize) {
180        if width >= 2 && width != self.width {
181            self.width = width;
182            if self.width > self.height {
183                self.set_side(self.width);
184            }
185        }
186    }
187
188    fn diamond(&mut self, x: f32, y: f32, half_w: f32, half_h: f32, rng: &mut impl Rng) {
189        let mut corners = vec![];
190        if x - half_w > 0.0 {
191            corners.push(self.data[(x - half_w) as usize][y as usize]);
192        }
193        if y - half_h > 0.0 {
194            corners.push(self.data[x as usize][(y - half_h) as usize]);
195        }
196        if x + half_w < self.side as f32 {
197            corners.push(self.data[(x + half_w) as usize][y as usize]);
198        }
199        if y + half_h < self.side as f32 {
200            corners.push(self.data[x as usize][(y + half_h) as usize]);
201        }
202
203        let mut base = 0.0;
204        for v in &corners {
205            base += v;
206        }
207        let n = self.randomize(base / corners.len() as f32, half_w + half_h, rng);
208
209        if (x as usize) < self.width && (y as usize) < self.height {
210            if n < self.min {
211                self.min = n;
212            }
213            if n > self.max {
214                self.max = n;
215            }
216        }
217
218        self.data[x as usize][y as usize] = n;
219    }
220
221    fn is_corner(&self, x: f32, y: f32) -> bool {
222        let p = (self.side - 1) as f32;
223        (x == 0.0 && y == 0.0) || (x == 0.0 && y == p) || (x == p && y == 0.0) || (x == p && y == p)
224    }
225
226    fn randomize(&mut self, base: f32, range: f32, rng: &mut impl Rng) -> f32 {
227        let r: f32 = rng.gen();
228
229        base + (r - base / self.depth) * range * self.rough
230    }
231
232    fn set_side(&mut self, max_side: usize) {
233        let side = if ((max_side - 1) as f32).log2() % 1.0 == 0.0 {
234            max_side
235        } else {
236            let n = (max_side as f32).log2();
237            (2 as usize).pow((n + if n % 1.0 == 0.0 { 0.0 } else { 1.0 }).floor() as u32) + 1
238        };
239        self.side = side;
240        self.data = vec![vec![0.0; side]; side];
241    }
242
243    fn shape(&mut self, size_w: f32, size_h: f32, rng: &mut impl Rng) {
244        if size_w <= 2.0 || size_h <= 2.0 {
245            return;
246        }
247
248        let half_w = (size_w / 2.0).floor();
249        let half_h = (size_h / 2.0).floor();
250        let side = self.side as f32;
251        let mut y = half_h;
252        while y < side {
253            let mut x = half_w;
254            while x < side {
255                if self.is_corner(x, y) {
256                    continue;
257                }
258                self.square(x, y, half_w, half_h, rng);
259                x += size_w - 1.0;
260            }
261            y += size_h - 1.0;
262        }
263
264        y = 0.0;
265        while y < side {
266            let mut x = if (y / half_h) % 2.0 == 0.0 {
267                half_w
268            } else {
269                0.0
270            };
271            while x < side {
272                if self.is_corner(x, y) {
273                    continue;
274                }
275                self.diamond(x, y, half_w, half_h, rng);
276                x += size_w - 1.0;
277            }
278            y += half_h;
279        }
280
281        self.shape((size_w / 2.0).ceil(), (size_h / 2.0).ceil(), rng);
282    }
283
284    fn square(&mut self, x: f32, y: f32, half_w: f32, half_h: f32, rng: &mut impl Rng) {
285        let base = (self.data[(x - half_w) as usize][(y - half_h) as usize]
286            + self.data[(x + half_w) as usize][(y - half_h) as usize]
287            + self.data[(x + half_w) as usize][(y + half_h) as usize]
288            + self.data[(x - half_w) as usize][(y + half_h) as usize])
289            / 4.0;
290        let n = self.randomize(base, half_w + half_h, rng);
291
292        if (x as usize) < self.width && (y as usize) < self.height {
293            if n < self.min {
294                self.min = n;
295            }
296            if n > self.max {
297                self.max = n;
298            }
299        }
300
301        self.data[x as usize][y as usize] = n;
302    }
303}