Heightmap with coarser grid
Description
Previously, when we had chosen a random height for each X-Z coordinate, the terrain fluctuated wildly. Adjacent blocks had no influence on each other so the terrain was very jagged.
Instead of choosing a random height for each X-Z coordinate, we can use fewer random values that are further apart, and interpolate the height of coordinates between these values.
In this example, we're choosing a random height every 16 blocks in the X and Z directions. This creates a grid pattern with cells that are 16x16 and a random height at the corner of each cell.
Coordinates that are not at the corners will derive their height from the values at the four corners of the cell.
Screenshot
Commands
git clone git@github.com:atsheehan/iridium
cd iridium
git checkout bcc4a30ea42e59cca9282fc2294db4a1e02fe960
cargo run --release
Code Changes
Modified src/main.rsGitHub
@@ -24,7 +24,7 @@2424 let event_loop = EventLoop::new().unwrap();
2525 let mut renderer = Renderer::new(&event_loop, options.windowed);
2626
27- let mut world = World::new(100, 25, 100);
27+ let mut world = World::new(256, 32, 256);
2828 renderer.update_block_cache(world.block_positions());
2929
3030 let mut last_instant = Instant::now();
@@ -24,7 +24,7 @@24 let event_loop = EventLoop::new().unwrap();
25 let mut renderer = Renderer::new(&event_loop, options.windowed);
26
27- let mut world = World::new(100, 25, 100);
28 renderer.update_block_cache(world.block_positions());
29
30 let mut last_instant = Instant::now();
@@ -24,7 +24,7 @@24 let event_loop = EventLoop::new().unwrap();
25 let mut renderer = Renderer::new(&event_loop, options.windowed);
26
27+ let mut world = World::new(256, 32, 256);
28 renderer.update_block_cache(world.block_positions());
29
30 let mut last_instant = Instant::now();
Modified src/math.rsGitHub
@@ -1,8 +1,16 @@1- use std::ops::Add;
1+ use std::ops::{Add, Div};
22
33 #[derive(Debug, Copy, Clone)]
44 pub(crate) struct Vec2(pub(crate) f32, pub(crate) f32);
55
6+ impl Div<f32> for Vec2 {
7+ type Output = Vec2;
8+
9+ fn div(self, denominator: f32) -> Self::Output {
10+ Self(self.0 / denominator, self.1 / denominator)
11+ }
12+ }
13+
614 #[derive(Debug, Copy, Clone)]
715 pub(crate) struct Vec3(pub(crate) f32, pub(crate) f32, pub(crate) f32);
816
@@ -77,6 +85,12 @@7785 let range = max - min;
7886 min + self.gen_u32() % range
7987 }
88+
89+ // Generates an f32 value between [0.0, 1.0).
90+ pub(crate) fn gen_f32(&mut self) -> f32 {
91+ let value = self.gen_u32();
92+ value as f32 / u32::MAX as f32
93+ }
8094 }
8195
8296 #[cfg(test)]
@@ -117,3 +131,7 @@117131 );
118132 }
119133 }
134+
135+ pub(crate) fn interpolate(value_a: f32, value_b: f32, t: f32) -> f32 {
136+ (1.0 - t) * value_a + t * value_b
137+ }
@@ -1,8 +1,16 @@1- use std::ops::Add;
2
3 #[derive(Debug, Copy, Clone)]
4 pub(crate) struct Vec2(pub(crate) f32, pub(crate) f32);
5
6 #[derive(Debug, Copy, Clone)]
7 pub(crate) struct Vec3(pub(crate) f32, pub(crate) f32, pub(crate) f32);
8
@@ -77,6 +85,12 @@77 let range = max - min;
78 min + self.gen_u32() % range
79 }
80 }
81
82 #[cfg(test)]
@@ -117,3 +131,7 @@117 );
118 }
119 }
@@ -1,8 +1,16 @@1+ use std::ops::{Add, Div};
2
3 #[derive(Debug, Copy, Clone)]
4 pub(crate) struct Vec2(pub(crate) f32, pub(crate) f32);
5
6+ impl Div<f32> for Vec2 {
7+ type Output = Vec2;
8+
9+ fn div(self, denominator: f32) -> Self::Output {
10+ Self(self.0 / denominator, self.1 / denominator)
11+ }
12+ }
13+
14 #[derive(Debug, Copy, Clone)]
15 pub(crate) struct Vec3(pub(crate) f32, pub(crate) f32, pub(crate) f32);
16
@@ -77,6 +85,12 @@85 let range = max - min;
86 min + self.gen_u32() % range
87 }
88+
89+ // Generates an f32 value between [0.0, 1.0).
90+ pub(crate) fn gen_f32(&mut self) -> f32 {
91+ let value = self.gen_u32();
92+ value as f32 / u32::MAX as f32
93+ }
94 }
95
96 #[cfg(test)]
@@ -117,3 +131,7 @@131 );
132 }
133 }
134+
135+ pub(crate) fn interpolate(value_a: f32, value_b: f32, t: f32) -> f32 {
136+ (1.0 - t) * value_a + t * value_b
137+ }
Modified src/world.rsGitHub
@@ -1,4 +1,4 @@1- use crate::math::{Vec2, Vec3};
1+ use crate::math::{self, RandomNumberGenerator, Vec2, Vec3};
22
33 const MOUSE_SENSITIVITY: f32 = 0.01;
44 const MOVE_SPEED: f32 = 0.5;
@@ -23,7 +23,7 @@2323
2424 let xz_area = (x_width * z_depth) as usize;
2525
26- let heightmap = Heightmap::new(Vec2((x_width / 2) as f32, (z_depth / 2) as f32), 25.0);
26+ let heightmap = Heightmap::new(16.0, 16, 16);
2727 let min_height = 1;
2828 let height_range = y_height - min_height;
2929
@@ -33,7 +33,7 @@3333 let coordinate = Coordinates(x, 0, z);
3434 let xz_position = coordinate.center().xz();
3535
36- let height = heightmap.height_at(xz_position.0, xz_position.1);
36+ let height = heightmap.height_at(&xz_position);
3737 let scaled_height = (height * height_range as f32) as u32 + min_height;
3838
3939 heights.push(scaled_height);
@@ -149,27 +149,101 @@149149
150150 /// Describes how elevation varies across the x-z plane.
151151 struct Heightmap {
152- center: Vec2,
153- spread: f32,
152+ cell_size: f32,
153+ num_x_cells: u32,
154+ num_z_cells: u32,
155+ heights: Vec<f32>,
154156 }
155157
156158 impl Heightmap {
157- fn new(center: Vec2, spread: f32) -> Self {
158- Self { center, spread }
159+ fn new(cell_size: f32, num_x_cells: u32, num_z_cells: u32) -> Self {
160+ let mut rng = RandomNumberGenerator::with_seed(32131);
161+
162+ let num_values = ((num_z_cells + 1) * (num_x_cells + 1)) as usize;
163+ let mut heights = Vec::with_capacity(num_values);
164+
165+ for _ in 0..num_values {
166+ heights.push(rng.gen_f32());
167+ }
168+
169+ Self {
170+ cell_size,
171+ num_x_cells,
172+ num_z_cells,
173+ heights,
174+ }
175+ }
176+
177+ fn height_at(&self, xz_position: &Vec2) -> f32 {
178+ if self.is_out_of_range(xz_position) {
179+ return 0.0;
180+ }
181+
182+ let normalized_position = self.normalize_position(xz_position);
183+
184+ let x0z0_height = self.height_at_x0z0(&normalized_position);
185+ let x0z1_height = self.height_at_x0z1(&normalized_position);
186+ let x1z0_height = self.height_at_x1z0(&normalized_position);
187+ let x1z1_height = self.height_at_x1z1(&normalized_position);
188+
189+ let x_frac = normalized_position.0.fract();
190+ let z_frac = normalized_position.1.fract();
191+
192+ let x0_height = math::interpolate(x0z0_height, x0z1_height, z_frac);
193+ let x1_height = math::interpolate(x1z0_height, x1z1_height, z_frac);
194+
195+ math::interpolate(x0_height, x1_height, x_frac)
196+ }
197+
198+ fn is_out_of_range(&self, xz_position: &Vec2) -> bool {
199+ let Vec2(x, z) = *xz_position;
200+
201+ x < self.min_x() || x >= self.max_x() || z < self.min_z() || z >= self.max_z()
159202 }
160203
161- fn height_at(&self, x: f32, z: f32) -> f32 {
162- let Vec2(x_center, z_center) = self.center;
163- let spread = self.spread * self.spread * 2.0;
204+ fn normalize_position(&self, xz_position: &Vec2) -> Vec2 {
205+ *xz_position / self.cell_size
206+ }
164207
165- let dx = x_center - x;
166- let dz = z_center - z;
208+ fn min_x(&self) -> f32 {
209+ 0.0
210+ }
167211
168- let x_term = (dx * dx) / spread;
169- let z_term = (dz * dz) / spread;
212+ fn max_x(&self) -> f32 {
213+ self.cell_size * self.num_x_cells as f32
214+ }
170215
171- let sum = -(x_term + z_term);
172- sum.exp()
216+ fn min_z(&self) -> f32 {
217+ 0.0
218+ }
219+
220+ fn max_z(&self) -> f32 {
221+ self.cell_size * self.num_z_cells as f32
222+ }
223+
224+ fn height_at_x0z0(&self, normalized_position: &Vec2) -> f32 {
225+ let Vec2(x, z) = *normalized_position;
226+ self.height_at_index(x as usize, z as usize)
227+ }
228+
229+ fn height_at_x1z0(&self, normalized_position: &Vec2) -> f32 {
230+ let Vec2(x, z) = *normalized_position;
231+ self.height_at_index((x as usize) + 1, z as usize)
232+ }
233+
234+ fn height_at_x0z1(&self, normalized_position: &Vec2) -> f32 {
235+ let Vec2(x, z) = *normalized_position;
236+ self.height_at_index(x as usize, (z as usize) + 1)
237+ }
238+
239+ fn height_at_x1z1(&self, normalized_position: &Vec2) -> f32 {
240+ let Vec2(x, z) = *normalized_position;
241+ self.height_at_index(x as usize + 1, z as usize + 1)
242+ }
243+
244+ fn height_at_index(&self, xi: usize, zi: usize) -> f32 {
245+ let i = zi * self.num_x_cells as usize + xi;
246+ self.heights[i]
173247 }
174248 }
175249
@@ -1,4 +1,4 @@1- use crate::math::{Vec2, Vec3};
2
3 const MOUSE_SENSITIVITY: f32 = 0.01;
4 const MOVE_SPEED: f32 = 0.5;
@@ -23,7 +23,7 @@23
24 let xz_area = (x_width * z_depth) as usize;
25
26- let heightmap = Heightmap::new(Vec2((x_width / 2) as f32, (z_depth / 2) as f32), 25.0);
27 let min_height = 1;
28 let height_range = y_height - min_height;
29
@@ -33,7 +33,7 @@33 let coordinate = Coordinates(x, 0, z);
34 let xz_position = coordinate.center().xz();
35
36- let height = heightmap.height_at(xz_position.0, xz_position.1);
37 let scaled_height = (height * height_range as f32) as u32 + min_height;
38
39 heights.push(scaled_height);
@@ -149,27 +149,101 @@149
150 /// Describes how elevation varies across the x-z plane.
151 struct Heightmap {
152- center: Vec2,
153- spread: f32,
154 }
155
156 impl Heightmap {
157- fn new(center: Vec2, spread: f32) -> Self {
158- Self { center, spread }
159 }
160
161- fn height_at(&self, x: f32, z: f32) -> f32 {
162- let Vec2(x_center, z_center) = self.center;
163- let spread = self.spread * self.spread * 2.0;
164
165- let dx = x_center - x;
166- let dz = z_center - z;
167
168- let x_term = (dx * dx) / spread;
169- let z_term = (dz * dz) / spread;
170
171- let sum = -(x_term + z_term);
172- sum.exp()
173 }
174 }
175
@@ -1,4 +1,4 @@1+ use crate::math::{self, RandomNumberGenerator, Vec2, Vec3};
2
3 const MOUSE_SENSITIVITY: f32 = 0.01;
4 const MOVE_SPEED: f32 = 0.5;
@@ -23,7 +23,7 @@23
24 let xz_area = (x_width * z_depth) as usize;
25
26+ let heightmap = Heightmap::new(16.0, 16, 16);
27 let min_height = 1;
28 let height_range = y_height - min_height;
29
@@ -33,7 +33,7 @@33 let coordinate = Coordinates(x, 0, z);
34 let xz_position = coordinate.center().xz();
35
36+ let height = heightmap.height_at(&xz_position);
37 let scaled_height = (height * height_range as f32) as u32 + min_height;
38
39 heights.push(scaled_height);
@@ -149,27 +149,101 @@149
150 /// Describes how elevation varies across the x-z plane.
151 struct Heightmap {
152+ cell_size: f32,
153+ num_x_cells: u32,
154+ num_z_cells: u32,
155+ heights: Vec<f32>,
156 }
157
158 impl Heightmap {
159+ fn new(cell_size: f32, num_x_cells: u32, num_z_cells: u32) -> Self {
160+ let mut rng = RandomNumberGenerator::with_seed(32131);
161+
162+ let num_values = ((num_z_cells + 1) * (num_x_cells + 1)) as usize;
163+ let mut heights = Vec::with_capacity(num_values);
164+
165+ for _ in 0..num_values {
166+ heights.push(rng.gen_f32());
167+ }
168+
169+ Self {
170+ cell_size,
171+ num_x_cells,
172+ num_z_cells,
173+ heights,
174+ }
175+ }
176+
177+ fn height_at(&self, xz_position: &Vec2) -> f32 {
178+ if self.is_out_of_range(xz_position) {
179+ return 0.0;
180+ }
181+
182+ let normalized_position = self.normalize_position(xz_position);
183+
184+ let x0z0_height = self.height_at_x0z0(&normalized_position);
185+ let x0z1_height = self.height_at_x0z1(&normalized_position);
186+ let x1z0_height = self.height_at_x1z0(&normalized_position);
187+ let x1z1_height = self.height_at_x1z1(&normalized_position);
188+
189+ let x_frac = normalized_position.0.fract();
190+ let z_frac = normalized_position.1.fract();
191+
192+ let x0_height = math::interpolate(x0z0_height, x0z1_height, z_frac);
193+ let x1_height = math::interpolate(x1z0_height, x1z1_height, z_frac);
194+
195+ math::interpolate(x0_height, x1_height, x_frac)
196+ }
197+
198+ fn is_out_of_range(&self, xz_position: &Vec2) -> bool {
199+ let Vec2(x, z) = *xz_position;
200+
201+ x < self.min_x() || x >= self.max_x() || z < self.min_z() || z >= self.max_z()
202 }
203
204+ fn normalize_position(&self, xz_position: &Vec2) -> Vec2 {
205+ *xz_position / self.cell_size
206+ }
207
208+ fn min_x(&self) -> f32 {
209+ 0.0
210+ }
211
212+ fn max_x(&self) -> f32 {
213+ self.cell_size * self.num_x_cells as f32
214+ }
215
216+ fn min_z(&self) -> f32 {
217+ 0.0
218+ }
219+
220+ fn max_z(&self) -> f32 {
221+ self.cell_size * self.num_z_cells as f32
222+ }
223+
224+ fn height_at_x0z0(&self, normalized_position: &Vec2) -> f32 {
225+ let Vec2(x, z) = *normalized_position;
226+ self.height_at_index(x as usize, z as usize)
227+ }
228+
229+ fn height_at_x1z0(&self, normalized_position: &Vec2) -> f32 {
230+ let Vec2(x, z) = *normalized_position;
231+ self.height_at_index((x as usize) + 1, z as usize)
232+ }
233+
234+ fn height_at_x0z1(&self, normalized_position: &Vec2) -> f32 {
235+ let Vec2(x, z) = *normalized_position;
236+ self.height_at_index(x as usize, (z as usize) + 1)
237+ }
238+
239+ fn height_at_x1z1(&self, normalized_position: &Vec2) -> f32 {
240+ let Vec2(x, z) = *normalized_position;
241+ self.height_at_index(x as usize + 1, z as usize + 1)
242+ }
243+
244+ fn height_at_index(&self, xi: usize, zi: usize) -> f32 {
245+ let i = zi * self.num_x_cells as usize + xi;
246+ self.heights[i]
247 }
248 }
249