voxelize/world/generators/
terrain.rs

1use kdtree::{distance::squared_euclidean, KdTree};
2use log::info;
3use serde::Serialize;
4use splines::interpolate::Interpolator;
5
6use crate::WorldConfig;
7
8use super::{
9    noise::{NoiseOptions, SeededNoise},
10    spline::SplineMap,
11};
12
13#[derive(PartialEq, Clone)]
14pub struct Biome {
15    pub name: String,
16    pub test_block: String,
17}
18
19impl Biome {
20    pub fn new(name: &str, test_block: &str) -> Self {
21        Self {
22            name: name.to_owned(),
23            test_block: test_block.to_owned(),
24        }
25    }
26}
27
28/// A seeded layered terrain for Voxelize world generation.
29#[derive(Clone)]
30pub struct Terrain {
31    config: WorldConfig,
32    noise: SeededNoise,
33    biome_tree: KdTree<f64, Biome, Vec<f64>>,
34    pub layers: Vec<(TerrainLayer, f64)>,
35    pub noise_layers: Vec<(TerrainLayer, f64)>,
36}
37
38impl Terrain {
39    /// Create a new instance of the seeded terrain.
40    pub fn new(config: &WorldConfig) -> Self {
41        Self {
42            config: config.to_owned(),
43            noise: SeededNoise::new(config.seed, &config.terrain),
44            biome_tree: KdTree::new(2),
45            layers: vec![],
46            noise_layers: vec![],
47        }
48    }
49
50    /// Add a terrain layer to the voxelize terrain.
51    pub fn add_layer(&mut self, layer: &TerrainLayer, weight: f64) -> &mut Self {
52        // Map the last layer's bias and offset to -1 to 1.
53        // if !self.layers.is_empty() {
54        //     let (mut last_layer, last_weight) = self.layers.pop().unwrap();
55        //     last_layer.normalize();
56        //     self.layers.push((last_layer, last_weight));
57        // }
58
59        if self.biome_tree.size() > 0 {
60            panic!("Terrain layers must be added before biomes.");
61        }
62
63        let mut layer = layer.to_owned();
64        layer.set_seed(self.config.seed);
65        // layer.normalize();
66        self.layers.push((layer, weight));
67
68        self.biome_tree = KdTree::new(self.layers.len());
69
70        self
71    }
72
73    /// Add a noise layer to the voxelize terrain.
74    pub fn add_noise_layer(&mut self, layer: &TerrainLayer, weight: f64) -> &mut Self {
75        let mut layer = layer.to_owned();
76        layer.set_seed(self.config.seed);
77        self.noise_layers.push((layer, weight));
78
79        self
80    }
81
82    pub fn add_biome(&mut self, point: &[f64], biome: Biome) -> &mut Self {
83        let point_vec = point.to_vec();
84        let point_vec = point_vec[..self.layers.len()]
85            .into_iter()
86            .enumerate()
87            .map(|(idx, val)| self.layers[idx].1 * val)
88            .collect::<Vec<f64>>()
89            .to_owned();
90
91        self.biome_tree.add(point_vec, biome).unwrap();
92        self
93    }
94
95    /// Get the voxel density at a voxel coordinate, which does the following:
96    /// 1. Calculate the height bias and height offset of each terrain layer.
97    /// 2. Obtain the average height bias and height offset at this specific voxel column.
98    /// 3. Get the noise value at this specific voxel coordinate, and add the average bias and height to it.
99    pub fn get_density_from_bias_offset(&self, bias: f64, offset: f64, vy: i32) -> f64 {
100        let max_height = self.config.max_height as f64;
101
102        // self.noise.get3d(vx, vy, vz).powi(2)
103        -if self.layers.is_empty() {
104            0.0
105        } else {
106            bias * (vy as f64 - offset * max_height) / (offset * max_height)
107        }
108    }
109
110    /// Get the height bias and height offset values at a voxel column. What it does is that it samples the bias and offset
111    /// of all noise layers and take the average of them all.
112    pub fn get_bias_offset(&self, vx: i32, vy: i32, vz: i32) -> (f64, f64) {
113        let mut bias = 0.0;
114        let mut offset = 0.0;
115        let mut total_weight = 0.0;
116
117        self.layers.iter().for_each(|(layer, weight)| {
118            let value = if layer.options.dimension == 2 {
119                layer.noise.get2d(vx, vz)
120            } else {
121                layer.noise.get3d(vx, vy, vz)
122            };
123            bias += layer.sample_bias(value) * weight;
124            offset += layer.sample_offset(value) * weight;
125            total_weight += weight;
126        });
127
128        self.noise_layers.iter().for_each(|(layer, weight)| {
129            let value = if layer.options.dimension == 2 {
130                layer.noise.get2d(vx, vz)
131            } else {
132                layer.noise.get3d(vx, vy, vz)
133            };
134            bias += layer.sample_bias(value) * weight;
135            offset += layer.sample_offset(value) * weight;
136            total_weight += weight;
137        });
138
139        (bias / total_weight, offset / total_weight)
140    }
141
142    pub fn get_biome_at(&self, vx: i32, vy: i32, vz: i32) -> &Biome {
143        let values = self
144            .layers
145            .iter()
146            .map(|(layer, weight)| {
147                (if layer.options.dimension == 2 {
148                    layer.noise.get2d(vx, vz)
149                } else {
150                    layer.noise.get3d(vx, vy, vz)
151                }) * weight
152            })
153            .collect::<Vec<f64>>();
154
155        let result = self
156            .biome_tree
157            .nearest(&values, 1, &squared_euclidean)
158            .unwrap()[0];
159
160        result.1
161    }
162}
163
164/// A layer to the terrain. Consists of two spline graphs: height bias and height offset graphs.
165/// Height bias is how much terrain should be compressed as y-coordinate increases, and height offset is
166/// by how much should the entire terrain shift up and down.
167#[derive(Clone, Serialize, Debug)]
168#[serde(rename_all = "camelCase")]
169pub struct TerrainLayer {
170    pub name: String,
171    #[serde(skip_serializing)]
172    pub noise: SeededNoise,
173    pub options: NoiseOptions,
174    pub height_bias_spline: SplineMap,
175    pub height_offset_spline: SplineMap,
176}
177
178impl TerrainLayer {
179    /// Create a new terrain layer from a specific noise configuration. The noise options are used for this layer
180    /// to be mapped to the height bias and height offset spline graphs.
181    pub fn new(name: &str, options: &NoiseOptions) -> Self {
182        TerrainLayer {
183            name: name.to_owned(),
184            noise: SeededNoise::new(0, options),
185            options: options.to_owned(),
186            height_bias_spline: SplineMap::default(),
187            height_offset_spline: SplineMap::default(),
188        }
189    }
190
191    /// Add a point to the bias spline graph.
192    pub fn add_bias_point(mut self, point: [f64; 2]) -> Self {
193        self.height_bias_spline.add(point[0], point[1]);
194        self
195    }
196
197    /// Add a set of points to the bias spline graph.
198    pub fn add_bias_points(mut self, points: &[[f64; 2]]) -> Self {
199        points.into_iter().for_each(|point| {
200            self.height_bias_spline.add(point[0], point[1]);
201        });
202        self
203    }
204
205    /// Add a point to the height offset spline graph.
206    pub fn add_offset_point(mut self, point: [f64; 2]) -> Self {
207        self.height_offset_spline.add(point[0], point[1]);
208        self
209    }
210
211    /// Add a set of points to the height offset spline graph.
212    pub fn add_offset_points(mut self, points: &[[f64; 2]]) -> Self {
213        points.into_iter().for_each(|point| {
214            self.height_offset_spline.add(point[0], point[1]);
215        });
216        self
217    }
218
219    /// Sample the bias at a certain x-value.
220    pub fn sample_bias(&self, x: f64) -> f64 {
221        self.height_bias_spline.sample(x)
222    }
223
224    /// Sample the offset at a certain x-value.
225    pub fn sample_offset(&self, x: f64) -> f64 {
226        self.height_offset_spline.sample(x)
227    }
228
229    /// Set the seed of the noise generator.
230    pub fn set_seed(&mut self, seed: u32) {
231        self.noise.set_seed(seed);
232    }
233
234    /// Normalize the spline graphs.
235    pub fn normalize(&mut self) {
236        self.height_bias_spline.rescale_values(-1.0, 1.0);
237        self.height_offset_spline.rescale_values(-1.0, 1.0);
238    }
239}