Move camera around
Description
As we start building out our world, we have limited ability to explore it. Our view is stuck at the origin, facing down the +Z axis.
To look around, we can introduce a camera that we can control. The camera is represented as a point in space (i.e. a 3D vector). To move it around, we listen for keyboard events and then increment the camera position in any of the six cardinal directions (up, down, left, right, forward, backward).
To handle rendering from the viewport of the camera, we introduce the camera coordinate space. This space puts the camera at the origin facing down the +Z axis which is how we've been rendering everything so far.
If the camera moves away from the origin, we need a way to transform the world in a way that puts the camera back to (0, 0, 0). This is the world-to-camera transform that will shift everything in the world relative to the camera's position. See this section on coordinate space transformations for more details.
Screenshot
Commands
git clone git@github.com:atsheehan/iridium
cd iridium
git checkout bfa8d481b566757537364a4c062d51c4ac43cb81
cargo run --release
Code Changes
Modified shaders/cube.vertGitHub
@@ -3,6 +3,7 @@33 const float NEAR = 0.1;
44 const float FAR = 10000.0;
55
6+ uniform vec3 camera_position;
67 uniform vec3 position;
78 uniform float aspect_ratio = 1.0;
89
@@ -82,7 +83,13 @@8283 texture_coordinates[5] = vec2(0.0, 0.0);
8384 vertex_tex_coord = texture_coordinates[gl_VertexID % 6];
8485
85- mat4 world_to_clip_transform =
86+ mat4 world_to_camera_transform =
87+ mat4(1.0, 0.0, 0.0, -camera_position.x,
88+ 0.0, 1.0, 0.0, -camera_position.y,
89+ 0.0, 0.0, 1.0, -camera_position.z,
90+ 0.0, 0.0, 0.0, 1.0);
91+
92+ mat4 camera_to_clip_transform =
8693 mat4(1.0, 0.0, 0.0, 0.0,
8794 0.0, aspect_ratio, 0.0, 0.0,
8895 0.0, 0.0, (-NEAR - FAR) / (NEAR - FAR), 2.0 * FAR * NEAR / (NEAR - FAR),
@@ -94,5 +101,8 @@94101 0.0, 0.0, 1.0, position.z,
95102 0.0, 0.0, 0.0, 1.0);
96103
97- gl_Position = vec4(vertices[gl_VertexID], 1.0) * model_to_world_transform * world_to_clip_transform;
104+ gl_Position = vec4(vertices[gl_VertexID], 1.0) *
105+ model_to_world_transform *
106+ world_to_camera_transform *
107+ camera_to_clip_transform;
98108 }
@@ -3,6 +3,7 @@3 const float NEAR = 0.1;
4 const float FAR = 10000.0;
5
6 uniform vec3 position;
7 uniform float aspect_ratio = 1.0;
8
@@ -82,7 +83,13 @@82 texture_coordinates[5] = vec2(0.0, 0.0);
83 vertex_tex_coord = texture_coordinates[gl_VertexID % 6];
84
85- mat4 world_to_clip_transform =
86 mat4(1.0, 0.0, 0.0, 0.0,
87 0.0, aspect_ratio, 0.0, 0.0,
88 0.0, 0.0, (-NEAR - FAR) / (NEAR - FAR), 2.0 * FAR * NEAR / (NEAR - FAR),
@@ -94,5 +101,8 @@94 0.0, 0.0, 1.0, position.z,
95 0.0, 0.0, 0.0, 1.0);
96
97- gl_Position = vec4(vertices[gl_VertexID], 1.0) * model_to_world_transform * world_to_clip_transform;
98 }
@@ -3,6 +3,7 @@3 const float NEAR = 0.1;
4 const float FAR = 10000.0;
5
6+ uniform vec3 camera_position;
7 uniform vec3 position;
8 uniform float aspect_ratio = 1.0;
9
@@ -82,7 +83,13 @@83 texture_coordinates[5] = vec2(0.0, 0.0);
84 vertex_tex_coord = texture_coordinates[gl_VertexID % 6];
85
86+ mat4 world_to_camera_transform =
87+ mat4(1.0, 0.0, 0.0, -camera_position.x,
88+ 0.0, 1.0, 0.0, -camera_position.y,
89+ 0.0, 0.0, 1.0, -camera_position.z,
90+ 0.0, 0.0, 0.0, 1.0);
91+
92+ mat4 camera_to_clip_transform =
93 mat4(1.0, 0.0, 0.0, 0.0,
94 0.0, aspect_ratio, 0.0, 0.0,
95 0.0, 0.0, (-NEAR - FAR) / (NEAR - FAR), 2.0 * FAR * NEAR / (NEAR - FAR),
@@ -94,5 +101,8 @@101 0.0, 0.0, 1.0, position.z,
102 0.0, 0.0, 0.0, 1.0);
103
104+ gl_Position = vec4(vertices[gl_VertexID], 1.0) *
105+ model_to_world_transform *
106+ world_to_camera_transform *
107+ camera_to_clip_transform;
108 }
Modified src/main.rsGitHub
@@ -16,7 +16,7 @@1616 let event_loop = EventLoop::new().unwrap();
1717 let mut renderer = Renderer::new(&event_loop, options.windowed);
1818
19- let world = World::new(20, 20);
19+ let mut world = World::new(20, 20);
2020
2121 event_loop
2222 .run(move |event, window_target| match event {
@@ -39,19 +39,27 @@3939 },
4040 window_id,
4141 } if window_id == renderer.window_id() => {
42- #[allow(clippy::single_match)]
4342 match (state, physical_key) {
43+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::KeyW)) => world.move_forward(),
44+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::KeyS)) => world.move_backward(),
45+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::KeyA)) => world.move_left(),
46+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::KeyD)) => world.move_right(),
47+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::Space)) => world.move_up(),
48+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::ControlLeft)) => world.move_down(),
4449 (ElementState::Pressed, PhysicalKey::Code(KeyCode::Escape)) => {
4550 window_target.exit();
4651 }
4752 _ => {}
4853 };
54+
55+ renderer.redraw();
4956 }
5057 Event::WindowEvent {
5158 event: WindowEvent::RedrawRequested,
5259 window_id,
5360 } if window_id == renderer.window_id() => {
5461 renderer.set_viewport();
62+ renderer.set_camera(world.camera());
5563 renderer.clear();
5664
5765 for position in world.block_positions() {
@@ -16,7 +16,7 @@16 let event_loop = EventLoop::new().unwrap();
17 let mut renderer = Renderer::new(&event_loop, options.windowed);
18
19- let world = World::new(20, 20);
20
21 event_loop
22 .run(move |event, window_target| match event {
@@ -39,19 +39,27 @@39 },
40 window_id,
41 } if window_id == renderer.window_id() => {
42- #[allow(clippy::single_match)]
43 match (state, physical_key) {
44 (ElementState::Pressed, PhysicalKey::Code(KeyCode::Escape)) => {
45 window_target.exit();
46 }
47 _ => {}
48 };
49 }
50 Event::WindowEvent {
51 event: WindowEvent::RedrawRequested,
52 window_id,
53 } if window_id == renderer.window_id() => {
54 renderer.set_viewport();
55 renderer.clear();
56
57 for position in world.block_positions() {
@@ -16,7 +16,7 @@16 let event_loop = EventLoop::new().unwrap();
17 let mut renderer = Renderer::new(&event_loop, options.windowed);
18
19+ let mut world = World::new(20, 20);
20
21 event_loop
22 .run(move |event, window_target| match event {
@@ -39,19 +39,27 @@39 },
40 window_id,
41 } if window_id == renderer.window_id() => {
42 match (state, physical_key) {
43+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::KeyW)) => world.move_forward(),
44+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::KeyS)) => world.move_backward(),
45+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::KeyA)) => world.move_left(),
46+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::KeyD)) => world.move_right(),
47+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::Space)) => world.move_up(),
48+ (ElementState::Pressed, PhysicalKey::Code(KeyCode::ControlLeft)) => world.move_down(),
49 (ElementState::Pressed, PhysicalKey::Code(KeyCode::Escape)) => {
50 window_target.exit();
51 }
52 _ => {}
53 };
54+
55+ renderer.redraw();
56 }
57 Event::WindowEvent {
58 event: WindowEvent::RedrawRequested,
59 window_id,
60 } if window_id == renderer.window_id() => {
61 renderer.set_viewport();
62+ renderer.set_camera(world.camera());
63 renderer.clear();
64
65 for position in world.block_positions() {
Modified src/math.rsGitHub
@@ -1,5 +1,16 @@1+ use std::ops::Add;
2+
3+ #[derive(Debug, Copy, Clone)]
14 pub(crate) struct Vec3(pub(crate) f32, pub(crate) f32, pub(crate) f32);
25
6+ impl Add<Vec3> for Vec3 {
7+ type Output = Vec3;
8+
9+ fn add(self, rhs: Vec3) -> Self::Output {
10+ Vec3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
11+ }
12+ }
13+
314 pub(crate) struct RandomNumberGenerator {
415 seed: u32,
516 }
@@ -1,5 +1,16 @@ 1 pub(crate) struct Vec3(pub(crate) f32, pub(crate) f32, pub(crate) f32);
2
3 pub(crate) struct RandomNumberGenerator {
4 seed: u32,
5 }
@@ -1,5 +1,16 @@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 Add<Vec3> for Vec3 {
7+ type Output = Vec3;
8+
9+ fn add(self, rhs: Vec3) -> Self::Output {
10+ Vec3(self.0 + rhs.0, self.1 + rhs.1, self.2 + rhs.2)
11+ }
12+ }
13+
14 pub(crate) struct RandomNumberGenerator {
15 seed: u32,
16 }
Modified src/render.rsGitHub
@@ -18,7 +18,10 @@1818 window::{Fullscreen, Window, WindowBuilder, WindowId},
1919 };
2020
21- use crate::math::{RandomNumberGenerator, Vec3};
21+ use crate::{
22+ math::{RandomNumberGenerator, Vec3},
23+ world::Camera,
24+ };
2225
2326 const CUBE_VERTEX_SHADER_SRC: &str = include_str!("../shaders/cube.vert");
2427 const CUBE_FRAGMENT_SHADER_SRC: &str = include_str!("../shaders/cube.frag");
@@ -179,6 +182,15 @@179182 gl::Viewport(0, 0, window_size.width as i32, window_size.height as i32);
180183 }
181184 }
185+
186+ pub(crate) fn set_camera(&mut self, camera: &Camera) {
187+ self.cube_program
188+ .set_uniform_vec3("camera_position", camera.position());
189+ }
190+
191+ pub(crate) fn redraw(&mut self) {
192+ self.window.request_redraw();
193+ }
182194 }
183195
184196 struct ProgramId(GLuint);
@@ -18,7 +18,10 @@18 window::{Fullscreen, Window, WindowBuilder, WindowId},
19 };
20
21- use crate::math::{RandomNumberGenerator, Vec3};
22
23 const CUBE_VERTEX_SHADER_SRC: &str = include_str!("../shaders/cube.vert");
24 const CUBE_FRAGMENT_SHADER_SRC: &str = include_str!("../shaders/cube.frag");
@@ -179,6 +182,15 @@179 gl::Viewport(0, 0, window_size.width as i32, window_size.height as i32);
180 }
181 }
182 }
183
184 struct ProgramId(GLuint);
@@ -18,7 +18,10 @@18 window::{Fullscreen, Window, WindowBuilder, WindowId},
19 };
20
21+ use crate::{
22+ math::{RandomNumberGenerator, Vec3},
23+ world::Camera,
24+ };
25
26 const CUBE_VERTEX_SHADER_SRC: &str = include_str!("../shaders/cube.vert");
27 const CUBE_FRAGMENT_SHADER_SRC: &str = include_str!("../shaders/cube.frag");
@@ -179,6 +182,15 @@182 gl::Viewport(0, 0, window_size.width as i32, window_size.height as i32);
183 }
184 }
185+
186+ pub(crate) fn set_camera(&mut self, camera: &Camera) {
187+ self.cube_program
188+ .set_uniform_vec3("camera_position", camera.position());
189+ }
190+
191+ pub(crate) fn redraw(&mut self) {
192+ self.window.request_redraw();
193+ }
194 }
195
196 struct ProgramId(GLuint);
Modified src/world.rsGitHub
@@ -1,13 +1,24 @@11 use crate::math::Vec3;
22
3+ const MOVE_DISTANCE: f32 = 0.5;
4+
35 pub(crate) struct World {
46 x_width: u32,
57 z_depth: u32,
8+ camera: Camera,
69 }
710
811 impl World {
912 pub(crate) fn new(x_width: u32, z_depth: u32) -> Self {
10- Self { x_width, z_depth }
13+ let camera = Camera {
14+ position: Vec3(0.0, 0.0, 0.0),
15+ };
16+
17+ Self {
18+ x_width,
19+ z_depth,
20+ camera,
21+ }
1122 }
1223
1324 pub(crate) fn block_positions(&self) -> impl Iterator<Item = Vec3> {
@@ -19,4 +30,42 @@1930 (x_start..x_end)
2031 .flat_map(move |x| (z_start..z_end).map(move |z| Vec3(x as f32, -2.0, z as f32)))
2132 }
33+
34+ pub(crate) fn move_forward(&mut self) {
35+ self.camera.position = self.camera.position + Vec3(0.0, 0.0, MOVE_DISTANCE);
36+ }
37+
38+ pub(crate) fn move_backward(&mut self) {
39+ self.camera.position = self.camera.position + Vec3(0.0, 0.0, -MOVE_DISTANCE);
40+ }
41+
42+ pub(crate) fn move_left(&mut self) {
43+ self.camera.position = self.camera.position + Vec3(-MOVE_DISTANCE, 0.0, 0.0);
44+ }
45+
46+ pub(crate) fn move_right(&mut self) {
47+ self.camera.position = self.camera.position + Vec3(MOVE_DISTANCE, 0.0, 0.0);
48+ }
49+
50+ pub(crate) fn move_up(&mut self) {
51+ self.camera.position = self.camera.position + Vec3(0.0, MOVE_DISTANCE, 0.0);
52+ }
53+
54+ pub(crate) fn move_down(&mut self) {
55+ self.camera.position = self.camera.position + Vec3(0.0, -MOVE_DISTANCE, 0.0);
56+ }
57+
58+ pub(crate) fn camera(&self) -> &Camera {
59+ &self.camera
60+ }
61+ }
62+
63+ pub(crate) struct Camera {
64+ position: Vec3,
65+ }
66+
67+ impl Camera {
68+ pub(crate) fn position(&self) -> &Vec3 {
69+ &self.position
70+ }
2271 }
@@ -1,13 +1,24 @@1 use crate::math::Vec3;
2
3 pub(crate) struct World {
4 x_width: u32,
5 z_depth: u32,
6 }
7
8 impl World {
9 pub(crate) fn new(x_width: u32, z_depth: u32) -> Self {
10- Self { x_width, z_depth }
11 }
12
13 pub(crate) fn block_positions(&self) -> impl Iterator<Item = Vec3> {
@@ -19,4 +30,42 @@19 (x_start..x_end)
20 .flat_map(move |x| (z_start..z_end).map(move |z| Vec3(x as f32, -2.0, z as f32)))
21 }
22 }
@@ -1,13 +1,24 @@1 use crate::math::Vec3;
2
3+ const MOVE_DISTANCE: f32 = 0.5;
4+
5 pub(crate) struct World {
6 x_width: u32,
7 z_depth: u32,
8+ camera: Camera,
9 }
10
11 impl World {
12 pub(crate) fn new(x_width: u32, z_depth: u32) -> Self {
13+ let camera = Camera {
14+ position: Vec3(0.0, 0.0, 0.0),
15+ };
16+
17+ Self {
18+ x_width,
19+ z_depth,
20+ camera,
21+ }
22 }
23
24 pub(crate) fn block_positions(&self) -> impl Iterator<Item = Vec3> {
@@ -19,4 +30,42 @@30 (x_start..x_end)
31 .flat_map(move |x| (z_start..z_end).map(move |z| Vec3(x as f32, -2.0, z as f32)))
32 }
33+
34+ pub(crate) fn move_forward(&mut self) {
35+ self.camera.position = self.camera.position + Vec3(0.0, 0.0, MOVE_DISTANCE);
36+ }
37+
38+ pub(crate) fn move_backward(&mut self) {
39+ self.camera.position = self.camera.position + Vec3(0.0, 0.0, -MOVE_DISTANCE);
40+ }
41+
42+ pub(crate) fn move_left(&mut self) {
43+ self.camera.position = self.camera.position + Vec3(-MOVE_DISTANCE, 0.0, 0.0);
44+ }
45+
46+ pub(crate) fn move_right(&mut self) {
47+ self.camera.position = self.camera.position + Vec3(MOVE_DISTANCE, 0.0, 0.0);
48+ }
49+
50+ pub(crate) fn move_up(&mut self) {
51+ self.camera.position = self.camera.position + Vec3(0.0, MOVE_DISTANCE, 0.0);
52+ }
53+
54+ pub(crate) fn move_down(&mut self) {
55+ self.camera.position = self.camera.position + Vec3(0.0, -MOVE_DISTANCE, 0.0);
56+ }
57+
58+ pub(crate) fn camera(&self) -> &Camera {
59+ &self.camera
60+ }
61+ }
62+
63+ pub(crate) struct Camera {
64+ position: Vec3,
65+ }
66+
67+ impl Camera {
68+ pub(crate) fn position(&self) -> &Vec3 {
69+ &self.position
70+ }
71 }