Create terrain from heightmap

Description

Using random heights for each block was too noisy.

We can define a function that defines a heightmap. The function can approximate curves and hills.

Screenshot

Commands

git clone git@github.com:atsheehan/iridium
cd iridium
git checkout ada7c1e86522e0542538cb72556ed9a769c0cdb0
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, 100, 100);
27+ let mut world = World::new(100, 25, 100);
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, 100, 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(100, 25, 100);
28 renderer.update_block_cache(world.block_positions());
29
30 let mut last_instant = Instant::now();

Modified src/math.rsGitHub

@@ -1,6 +1,9 @@
11 use std::ops::Add;
22
33 #[derive(Debug, Copy, Clone)]
4+ pub(crate) struct Vec2(pub(crate) f32, pub(crate) f32);
5+
6+ #[derive(Debug, Copy, Clone)]
47 pub(crate) struct Vec3(pub(crate) f32, pub(crate) f32, pub(crate) f32);
58
69 impl Vec3 {
@@ -40,6 +43,10 @@
4043 pub(crate) fn z(&self) -> f32 {
4144 self.2
4245 }
46+
47+ pub(crate) fn xz(&self) -> Vec2 {
48+ Vec2(self.0, self.2)
49+ }
4350 }
4451
4552 impl Add<Vec3> for Vec3 {
@@ -1,6 +1,9 @@
1 use std::ops::Add;
2
3 #[derive(Debug, Copy, Clone)]
 
 
 
4 pub(crate) struct Vec3(pub(crate) f32, pub(crate) f32, pub(crate) f32);
5
6 impl Vec3 {
@@ -40,6 +43,10 @@
40 pub(crate) fn z(&self) -> f32 {
41 self.2
42 }
 
 
 
 
43 }
44
45 impl Add<Vec3> for Vec3 {
@@ -1,6 +1,9 @@
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
9 impl Vec3 {
@@ -40,6 +43,10 @@
43 pub(crate) fn z(&self) -> f32 {
44 self.2
45 }
46+
47+ pub(crate) fn xz(&self) -> Vec2 {
48+ Vec2(self.0, self.2)
49+ }
50 }
51
52 impl Add<Vec3> for Vec3 {

Modified src/world.rsGitHub

@@ -1,6 +1,6 @@
1- use crate::math::{RandomNumberGenerator, Vec3};
1+ use crate::math::{Vec2, Vec3};
22
3- const MOUSE_SENSITIVITY: f32 = 0.001;
3+ const MOUSE_SENSITIVITY: f32 = 0.01;
44 const MOVE_SPEED: f32 = 0.5;
55
66 pub(crate) struct World {
@@ -21,13 +21,23 @@
2121 pitch: 0.0,
2222 };
2323
24- let mut rng = RandomNumberGenerator::with_seed(42);
2524 let xz_area = (x_width * z_depth) as usize;
2625
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+
2730 let mut heights = Vec::with_capacity(xz_area);
31+ for x in 0..x_width {
32+ for z in 0..z_depth {
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;
2838
29- for _ in 0..xz_area {
30- heights.push(rng.gen_range(1, y_height));
39+ heights.push(scaled_height);
40+ }
3141 }
3242
3343 Self {
@@ -136,3 +146,38 @@
136146 self.pitch
137147 }
138148 }
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+
176+ struct Coordinates(u32, u32, u32);
177+
178+ impl Coordinates {
179+ fn center(&self) -> Vec3 {
180+ let Self(x, y, z) = *self;
181+ Vec3(x as f32 + 0.5, y as f32 + 0.5, z as f32 + 0.5)
182+ }
183+ }
@@ -1,6 +1,6 @@
1- use crate::math::{RandomNumberGenerator, Vec3};
2
3- const MOUSE_SENSITIVITY: f32 = 0.001;
4 const MOVE_SPEED: f32 = 0.5;
5
6 pub(crate) struct World {
@@ -21,13 +21,23 @@
21 pitch: 0.0,
22 };
23
24- let mut rng = RandomNumberGenerator::with_seed(42);
25 let xz_area = (x_width * z_depth) as usize;
26
 
 
 
 
27 let mut heights = Vec::with_capacity(xz_area);
 
 
 
 
 
 
 
28
29- for _ in 0..xz_area {
30- heights.push(rng.gen_range(1, y_height));
31 }
32
33 Self {
@@ -136,3 +146,38 @@
136 self.pitch
137 }
138 }
@@ -1,6 +1,6 @@
1+ use crate::math::{Vec2, Vec3};
2
3+ const MOUSE_SENSITIVITY: f32 = 0.01;
4 const MOVE_SPEED: f32 = 0.5;
5
6 pub(crate) struct World {
@@ -21,13 +21,23 @@
21 pitch: 0.0,
22 };
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+
30 let mut heights = Vec::with_capacity(xz_area);
31+ for x in 0..x_width {
32+ for z in 0..z_depth {
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);
40+ }
41 }
42
43 Self {
@@ -136,3 +146,38 @@
146 self.pitch
147 }
148 }
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+
176+ struct Coordinates(u32, u32, u32);
177+
178+ impl Coordinates {
179+ fn center(&self) -> Vec3 {
180+ let Self(x, y, z) = *self;
181+ Vec3(x as f32 + 0.5, y as f32 + 0.5, z as f32 + 0.5)
182+ }
183+ }