Move in direction of heading
Description
Even though we can look around with the mouse, when we move forward we're still traveling along the +Z axis.
Our camera heading is only used in our shader to transform our view of the world. We also need to transform our velocity before we update our position each tick.
It's possible to rotate a 3D vector by multiplying it with a rotational matrix. This change takes a shortcut by directly applying the matrix multiplication to the elements of the vector and returning a new vector that's been rotated.
This is also a good time to start adding unit tests to ensure we're
getting the results we expect. These tests can be run with cargo test
and add some initial converage.
We have to define a custom vec3 assertion because there are often small rounding errors after performing multiple operations on floating point values. The assert method will ignore these differences when checking if two vectors are equal.
Screenshot
Commands
git clone git@github.com:atsheehan/iridium
cd iridium
git checkout f1d0faab2b6df83ef0601644bc4c4c39264f344d
cargo run --release
Code Changes
Modified src/math.rsGitHub
@@ -15,6 +15,19 @@1515 pub(crate) fn set_z(&self, new_z: f32) -> Vec3 {
1616 Self(self.0, self.1, new_z)
1717 }
18+
19+ pub(crate) fn rotate_y(&self, angle: f32) -> Vec3 {
20+ let Self(x, y, z) = self;
21+
22+ let cos = angle.cos();
23+ let sin = angle.sin();
24+
25+ let new_x = x * cos + z * sin;
26+ let new_y = *y;
27+ let new_z = x * -sin + z * cos;
28+
29+ Self(new_x, new_y, new_z)
30+ }
1831 }
1932
2033 impl Add<Vec3> for Vec3 {
@@ -46,3 +59,42 @@4659 min + self.gen_u32() % range
4760 }
4861 }
62+
63+ #[cfg(test)]
64+ mod tests {
65+ use super::*;
66+ use std::f32::consts::*;
67+
68+ #[test]
69+ fn rotate_vector_around_y_axis() {
70+ let examples = [
71+ (Vec3(1.0, 0.0, 0.0), PI, Vec3(-1.0, 0.0, 0.0)),
72+ (Vec3(-1.0, 0.0, 0.0), PI, Vec3(1.0, 0.0, 0.0)),
73+ (Vec3(0.5, 0.0, 0.0), FRAC_PI_2, Vec3(0.0, 0.0, -0.5)),
74+ (Vec3(0.5, 0.0, 0.0), -FRAC_PI_2, Vec3(0.0, 0.0, 0.5)),
75+ (Vec3(0.0, 1.0, 0.0), FRAC_PI_2, Vec3(0.0, 1.0, 0.0)),
76+ (
77+ Vec3(0.0, 0.0, 1.0),
78+ FRAC_PI_4,
79+ Vec3(FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2),
80+ ),
81+ ];
82+
83+ for (input, angle, expected_output) in examples.into_iter() {
84+ let actual_output = input.rotate_y(angle);
85+ assert_vec3s_equal(&actual_output, &expected_output);
86+ }
87+ }
88+
89+ fn assert_vec3s_equal(a: &Vec3, b: &Vec3) {
90+ const TOLERANCE: f32 = 0.00001;
91+ let vecs_equal = (a.0 - b.0).abs() < TOLERANCE
92+ && (a.1 - b.1).abs() < TOLERANCE
93+ && (a.2 - b.2).abs() < TOLERANCE;
94+ assert!(
95+ vecs_equal,
96+ "vecs are not equal:\n left: {:?}\n right: {:?}\n",
97+ a, b
98+ );
99+ }
100+ }
@@ -15,6 +15,19 @@15 pub(crate) fn set_z(&self, new_z: f32) -> Vec3 {
16 Self(self.0, self.1, new_z)
17 }
18 }
19
20 impl Add<Vec3> for Vec3 {
@@ -46,3 +59,42 @@46 min + self.gen_u32() % range
47 }
48 }
@@ -15,6 +15,19 @@15 pub(crate) fn set_z(&self, new_z: f32) -> Vec3 {
16 Self(self.0, self.1, new_z)
17 }
18+
19+ pub(crate) fn rotate_y(&self, angle: f32) -> Vec3 {
20+ let Self(x, y, z) = self;
21+
22+ let cos = angle.cos();
23+ let sin = angle.sin();
24+
25+ let new_x = x * cos + z * sin;
26+ let new_y = *y;
27+ let new_z = x * -sin + z * cos;
28+
29+ Self(new_x, new_y, new_z)
30+ }
31 }
32
33 impl Add<Vec3> for Vec3 {
@@ -46,3 +59,42 @@59 min + self.gen_u32() % range
60 }
61 }
62+
63+ #[cfg(test)]
64+ mod tests {
65+ use super::*;
66+ use std::f32::consts::*;
67+
68+ #[test]
69+ fn rotate_vector_around_y_axis() {
70+ let examples = [
71+ (Vec3(1.0, 0.0, 0.0), PI, Vec3(-1.0, 0.0, 0.0)),
72+ (Vec3(-1.0, 0.0, 0.0), PI, Vec3(1.0, 0.0, 0.0)),
73+ (Vec3(0.5, 0.0, 0.0), FRAC_PI_2, Vec3(0.0, 0.0, -0.5)),
74+ (Vec3(0.5, 0.0, 0.0), -FRAC_PI_2, Vec3(0.0, 0.0, 0.5)),
75+ (Vec3(0.0, 1.0, 0.0), FRAC_PI_2, Vec3(0.0, 1.0, 0.0)),
76+ (
77+ Vec3(0.0, 0.0, 1.0),
78+ FRAC_PI_4,
79+ Vec3(FRAC_1_SQRT_2, 0.0, FRAC_1_SQRT_2),
80+ ),
81+ ];
82+
83+ for (input, angle, expected_output) in examples.into_iter() {
84+ let actual_output = input.rotate_y(angle);
85+ assert_vec3s_equal(&actual_output, &expected_output);
86+ }
87+ }
88+
89+ fn assert_vec3s_equal(a: &Vec3, b: &Vec3) {
90+ const TOLERANCE: f32 = 0.00001;
91+ let vecs_equal = (a.0 - b.0).abs() < TOLERANCE
92+ && (a.1 - b.1).abs() < TOLERANCE
93+ && (a.2 - b.2).abs() < TOLERANCE;
94+ assert!(
95+ vecs_equal,
96+ "vecs are not equal:\n left: {:?}\n right: {:?}\n",
97+ a, b
98+ );
99+ }
100+ }
Modified src/world.rsGitHub
@@ -25,7 +25,8 @@2525 }
2626
2727 pub(crate) fn update(&mut self) {
28- self.camera.position = self.camera.position + self.camera.velocity;
28+ let actual_velocity = self.camera.velocity.rotate_y(self.camera.heading);
29+ self.camera.position = self.camera.position + actual_velocity;
2930 }
3031
3132 pub(crate) fn block_positions(&self) -> impl Iterator<Item = Vec3> {
@@ -25,7 +25,8 @@25 }
26
27 pub(crate) fn update(&mut self) {
28- self.camera.position = self.camera.position + self.camera.velocity;
29 }
30
31 pub(crate) fn block_positions(&self) -> impl Iterator<Item = Vec3> {
@@ -25,7 +25,8 @@25 }
26
27 pub(crate) fn update(&mut self) {
28+ let actual_velocity = self.camera.velocity.rotate_y(self.camera.heading);
29+ self.camera.position = self.camera.position + actual_velocity;
30 }
31
32 pub(crate) fn block_positions(&self) -> impl Iterator<Item = Vec3> {