|
| 1 | +use std::time::{Duration, Instant}; |
| 2 | + |
| 3 | +use rand::rngs::SmallRng; |
| 4 | +use rand::{Rng, SeedableRng}; |
| 5 | +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; |
| 6 | +use softbuffer::Buffer; |
| 7 | + |
| 8 | +use crate::camera::Camera; |
| 9 | +use crate::vec3::{Color, Point3, Vec3}; |
| 10 | +use crate::world::World; |
| 11 | + |
| 12 | +pub struct Game { |
| 13 | + world: World, |
| 14 | + up: Vec3, |
| 15 | + camera_position: Point3, |
| 16 | + pub camera_yaw: f32, |
| 17 | + pub camera_pitch: f32, |
| 18 | + /// x = right, y = up, z = forwards |
| 19 | + pub camera_velocity: Vec3, |
| 20 | + elapsed_time: Instant, |
| 21 | +} |
| 22 | + |
| 23 | +const SAMPLES_PER_PIXEL: i32 = 3; |
| 24 | +const MAX_DEPTH: i32 = 5; |
| 25 | +pub const MOVEMENT_SPEED: f32 = 10.0; |
| 26 | +pub const MOUSE_SENSITIVITY: f32 = 0.005; |
| 27 | +const DURATION_BETWEEN_TICKS: Duration = Duration::from_millis(10); |
| 28 | + |
| 29 | +impl Game { |
| 30 | + pub fn new() -> Self { |
| 31 | + let mut rng = SmallRng::from_os_rng(); |
| 32 | + let position = Point3::new(13.0, 2.0, 3.0); |
| 33 | + let looking_at = Point3::new(0.0, 0.0, 0.0); |
| 34 | + let camera_direction = (looking_at - position).normalize(); |
| 35 | + let up = Vec3::new(0.0, 1.0, 0.0); |
| 36 | + Self { |
| 37 | + world: World::random_scene(&mut rng), |
| 38 | + up, |
| 39 | + camera_position: position, |
| 40 | + camera_yaw: camera_direction.x.atan2(camera_direction.z), |
| 41 | + camera_pitch: camera_direction.y.clamp(-1.0, 1.0).asin(), |
| 42 | + camera_velocity: Vec3::new(0.0, 0.0, 0.0), |
| 43 | + elapsed_time: Instant::now(), |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + pub fn draw( |
| 48 | + &self, |
| 49 | + buffer: &mut Buffer<'_, impl HasDisplayHandle, impl HasWindowHandle>, |
| 50 | + scale_factor: f32, |
| 51 | + ) { |
| 52 | + self.draw_scene(buffer, scale_factor); |
| 53 | + self.draw_ui(buffer, scale_factor); |
| 54 | + } |
| 55 | + |
| 56 | + /// Draw the 3D scene. |
| 57 | + fn draw_scene( |
| 58 | + &self, |
| 59 | + buffer: &mut Buffer<'_, impl HasDisplayHandle, impl HasWindowHandle>, |
| 60 | + scale_factor: f32, |
| 61 | + ) { |
| 62 | + // Raytracing is expensive, so we only do it once every 4x4 pixel. |
| 63 | + // |
| 64 | + // FIXME(madsmtm): Avoid the need for this once we can do hardware scaling. |
| 65 | + // https://github.com/rust-windowing/softbuffer/issues/177 |
| 66 | + let scale_factor = scale_factor * 4.0; |
| 67 | + |
| 68 | + let width = buffer.width().get() as f32 / scale_factor; |
| 69 | + let height = buffer.height().get() as f32 / scale_factor; |
| 70 | + |
| 71 | + let dist_to_focus = 10.0; |
| 72 | + let aperture = 0.1; |
| 73 | + let cam = Camera::new( |
| 74 | + self.camera_position, |
| 75 | + self.camera_direction(), |
| 76 | + self.up, |
| 77 | + 20.0, |
| 78 | + width / height, |
| 79 | + aperture, |
| 80 | + dist_to_focus, |
| 81 | + ); |
| 82 | + |
| 83 | + let mut pixels = vec![0; width as usize * height as usize]; |
| 84 | + |
| 85 | + let each_pixel = |rng: &mut SmallRng, i, pixel: &mut u32| { |
| 86 | + let y = i % (width as usize); |
| 87 | + let x = i / (width as usize); |
| 88 | + let mut pixel_color = Color::default(); |
| 89 | + for _ in 0..SAMPLES_PER_PIXEL { |
| 90 | + let s = (y as f32 + rng.random::<f32>()) / (width - 1.0); |
| 91 | + let t = 1.0 - (x as f32 + rng.random::<f32>()) / (height - 1.0); |
| 92 | + let r = cam.get_ray(s, t, rng); |
| 93 | + pixel_color += r.trace(&self.world, MAX_DEPTH, rng); |
| 94 | + } |
| 95 | + *pixel = color_to_pixel(pixel_color, SAMPLES_PER_PIXEL); |
| 96 | + }; |
| 97 | + |
| 98 | + // Render in parallel with rayon. |
| 99 | + #[cfg(not(target_family = "wasm"))] |
| 100 | + { |
| 101 | + use rayon::prelude::*; |
| 102 | + |
| 103 | + pixels |
| 104 | + .par_iter_mut() |
| 105 | + .enumerate() |
| 106 | + .for_each_init(SmallRng::from_os_rng, move |rng, (i, pixel)| { |
| 107 | + each_pixel(rng, i, pixel) |
| 108 | + }); |
| 109 | + }; |
| 110 | + #[cfg(target_family = "wasm")] |
| 111 | + { |
| 112 | + let mut rng = SmallRng::from_os_rng(); |
| 113 | + pixels |
| 114 | + .iter_mut() |
| 115 | + .enumerate() |
| 116 | + .for_each(|(i, pixel)| each_row(&mut rng, i, pixel)); |
| 117 | + } |
| 118 | + |
| 119 | + // Upscale by `scale_factor`. |
| 120 | + let width = buffer.width().get() as usize; |
| 121 | + buffer.iter_mut().enumerate().for_each(|(i, pixel)| { |
| 122 | + let y = i % width; |
| 123 | + let x = i / width; |
| 124 | + let y = (y as f32 / scale_factor) as usize; |
| 125 | + let x = (x as f32 / scale_factor) as usize; |
| 126 | + if let Some(x) = pixels.get(x * (width as f32 / scale_factor) as usize + y) { |
| 127 | + *pixel = *x; |
| 128 | + } |
| 129 | + }); |
| 130 | + } |
| 131 | + |
| 132 | + /// Draw a simple example UI on top of the scene. |
| 133 | + fn draw_ui( |
| 134 | + &self, |
| 135 | + buffer: &mut Buffer<'_, impl HasDisplayHandle, impl HasWindowHandle>, |
| 136 | + scale_factor: f32, |
| 137 | + ) { |
| 138 | + struct Rect { |
| 139 | + left: f32, |
| 140 | + right: f32, |
| 141 | + top: f32, |
| 142 | + bottom: f32, |
| 143 | + color: u32, |
| 144 | + } |
| 145 | + |
| 146 | + let width = buffer.width().get() as f32 / scale_factor; |
| 147 | + let height = buffer.height().get() as f32 / scale_factor; |
| 148 | + let rects = &[ |
| 149 | + Rect { |
| 150 | + left: 10.0, |
| 151 | + right: width - 10.0, |
| 152 | + top: height - 90.0, |
| 153 | + bottom: height - 10.0, |
| 154 | + color: 0x00eeaaaa, |
| 155 | + }, |
| 156 | + Rect { |
| 157 | + left: 30.0, |
| 158 | + right: 70.0, |
| 159 | + top: height - 70.0, |
| 160 | + bottom: height - 30.0, |
| 161 | + color: 0x00aaaaee, |
| 162 | + }, |
| 163 | + ]; |
| 164 | + |
| 165 | + let width = buffer.width().get(); |
| 166 | + for (y, row) in buffer.chunks_exact_mut(width as usize).enumerate() { |
| 167 | + for rect in rects { |
| 168 | + let rect_vertical = |
| 169 | + (rect.top * scale_factor) as usize..(rect.bottom * scale_factor) as usize; |
| 170 | + let rect_horizontal = |
| 171 | + (rect.left * scale_factor) as usize..(rect.right * scale_factor) as usize; |
| 172 | + if rect_vertical.contains(&y) { |
| 173 | + if let Some(row) = row.get_mut(rect_horizontal) { |
| 174 | + row.fill(rect.color); |
| 175 | + } |
| 176 | + } |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + fn tick(&mut self) { |
| 182 | + let forward = self.camera_direction().with_y(0.0); |
| 183 | + let right = forward.cross(self.up).normalize(); |
| 184 | + let up = right.cross(forward); |
| 185 | + let movement = forward * self.camera_velocity.z |
| 186 | + + up * self.camera_velocity.y |
| 187 | + + right * self.camera_velocity.x; |
| 188 | + self.camera_position += movement * DURATION_BETWEEN_TICKS.as_secs_f32(); |
| 189 | + } |
| 190 | + |
| 191 | + pub fn update(&mut self) { |
| 192 | + // Update game state. |
| 193 | + let now = Instant::now(); |
| 194 | + while let Some(_remainder) = now |
| 195 | + .duration_since(self.elapsed_time) |
| 196 | + .checked_sub(DURATION_BETWEEN_TICKS) |
| 197 | + { |
| 198 | + self.elapsed_time += DURATION_BETWEEN_TICKS; |
| 199 | + self.tick(); |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + fn camera_direction(&self) -> Vec3 { |
| 204 | + Vec3::new( |
| 205 | + self.camera_pitch.cos() * self.camera_yaw.sin(), |
| 206 | + self.camera_pitch.sin(), |
| 207 | + self.camera_pitch.cos() * self.camera_yaw.cos(), |
| 208 | + ) |
| 209 | + } |
| 210 | +} |
| 211 | + |
| 212 | +fn color_to_pixel(pixel_color: Color, samples_per_pixel: i32) -> u32 { |
| 213 | + let mut r = pixel_color.x; |
| 214 | + let mut g = pixel_color.y; |
| 215 | + let mut b = pixel_color.z; |
| 216 | + |
| 217 | + let scale = 1.0 / samples_per_pixel as f32; |
| 218 | + r = f32::sqrt(scale * r); |
| 219 | + g = f32::sqrt(scale * g); |
| 220 | + b = f32::sqrt(scale * b); |
| 221 | + |
| 222 | + (256.0 * b.clamp(0.0, 0.999)) as u32 |
| 223 | + | (((256.0 * g.clamp(0.0, 0.999)) as u32) << 8) |
| 224 | + | (((256.0 * r.clamp(0.0, 0.999)) as u32) << 16) |
| 225 | +} |
0 commit comments