From 3460d889aa2e7af1a60d218a1a8589027b4e4e63 Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 9 Jul 2026 23:27:55 +0200 Subject: init --- src/renderer.rs | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 src/renderer.rs (limited to 'src/renderer.rs') diff --git a/src/renderer.rs b/src/renderer.rs new file mode 100644 index 0000000..0792537 --- /dev/null +++ b/src/renderer.rs @@ -0,0 +1,196 @@ +use crate::game::Game; +use crate::texture::TEX_SIZE; + +pub struct Renderer { + screen_w: usize, + screen_h: usize, + /// Precomputed: floor_dist_lut[row] = perpendicular distance to floor at that row. + floor_dist_lut: Vec, + /// Precomputed: floor_bright_lut[row] = brightness × 256 for that row (fixed-point). + floor_bright_lut: Vec, +} + +impl Renderer { + pub fn new(screen_w: usize, screen_h: usize) -> Self { + let half_h = screen_h as f64 / 2.0; + let mut floor_dist_lut = vec![0.0f64; screen_h]; + let mut floor_bright_lut = vec![0u32; screen_h]; + + // Only rows below the horizon have meaningful floor values. + for py in (screen_h / 2 + 1)..screen_h { + let dy = py as f64 - half_h; + let dist = 0.5 * screen_h as f64 / dy; + floor_dist_lut[py] = dist; + let bright = (1.0 / (1.0 + dist * 0.15)).max(0.05); + floor_bright_lut[py] = (bright * 256.0) as u32; + } + + Self { + screen_w, + screen_h, + floor_dist_lut, + floor_bright_lut, + } + } + + /// Produce one frame into `buffer` (packed `0xAARRGGBB`). + pub fn render(&self, game: &Game, buffer: &mut [u32]) { + buffer.fill(0xFF00_0000); // pure-black ceiling + + let half_h = self.screen_h as f64 / 2.0; + let fov = std::f64::consts::PI / 3.0; // 60° + + for sx in 0..self.screen_w { + let cam_x = 2.0 * sx as f64 / self.screen_w as f64 - 1.0; + let ray_angle = game.player.angle + (fov / 2.0) * cam_x; + let rdx = ray_angle.cos(); + let rdy = ray_angle.sin(); + + // ---- DDA setup ------------------------------------------------- + let mut map_x = game.player.x as isize; + let mut map_y = game.player.y as isize; + + let ddx = if rdx == 0.0 { + f64::MAX + } else { + (1.0 / rdx).abs() + }; + let ddy = if rdy == 0.0 { + f64::MAX + } else { + (1.0 / rdy).abs() + }; + + let (step_x, mut side_x) = if rdx < 0.0 { + (-1isize, (game.player.x - map_x as f64) * ddx) + } else { + (1isize, (map_x as f64 + 1.0 - game.player.x) * ddx) + }; + + let (step_y, mut side_y) = if rdy < 0.0 { + (-1isize, (game.player.y - map_y as f64) * ddy) + } else { + (1isize, (map_y as f64 + 1.0 - game.player.y) * ddy) + }; + + let mut hit_side: bool; + let euclidean: f64; + + // ---- DDA loop -------------------------------------------------- + loop { + if side_x < side_y { + side_x += ddx; + map_x += step_x; + hit_side = false; + } else { + side_y += ddy; + map_y += step_y; + hit_side = true; + } + + if map_y < 0 + || map_y >= game.maze.height() as isize + || map_x < 0 + || map_x >= game.maze.width() as isize + { + euclidean = 1.0; + break; + } + + if game.maze.is_wall(map_y as usize, map_x as usize) { + euclidean = if !hit_side { + (map_x as f64 - game.player.x + (1.0 - step_x as f64) / 2.0) / rdx + } else { + (map_y as f64 - game.player.y + (1.0 - step_y as f64) / 2.0) / rdy + }; + break; + } + } + + // Fisheye correction + let perp_dist = (euclidean * (ray_angle - game.player.angle).cos().abs()).max(0.001); + + // ---- wall slice ------------------------------------------------ + let wall_h = (self.screen_h as f64 / perp_dist) as isize; + let draw_start = ((-wall_h / 2) + self.screen_h as isize / 2).max(0) as usize; + let draw_end = ((wall_h / 2) + self.screen_h as isize / 2) + .min(self.screen_h as isize - 1) as usize; + + // Texture x-coordinate (uses Euclidean, not perp) + let wall_x = if !hit_side { + game.player.y + euclidean * rdy + } else { + game.player.x + euclidean * rdx + }; + let wall_x = wall_x - wall_x.floor(); + let tex_x = ((wall_x * TEX_SIZE as f64) as usize).min(TEX_SIZE - 1); + + // Hoist: brightness is identical for every pixel in this column + let wall_bright_fx = { + let b = (1.0 / (1.0 + perp_dist * 0.12)).max(0.08); + (b * 256.0) as u32 + }; + + for py in draw_start..=draw_end { + let tex_y = ((py as f64 - half_h + wall_h as f64 / 2.0) * TEX_SIZE as f64 + / wall_h as f64) as usize; + let tex_y = tex_y.min(TEX_SIZE - 1); + + let color = game.wall_texture[tex_y * TEX_SIZE + tex_x]; + buffer[py * self.screen_w + sx] = darken_fx(color, wall_bright_fx); + } + + // ---- floor below the wall -------------------------------------- + let floor_start = draw_end + 1; + if floor_start < self.screen_h { + for py in floor_start..self.screen_h { + let floor_dist = self.floor_dist_lut[py]; + + if floor_dist > 15.0 { + // Far floor → just a dark pixel, skip texture & world calc. + let gray = darken_fx(0xFF80_8080, self.floor_bright_lut[py]); + buffer[py * self.screen_w + sx] = gray; + } else { + let fx = game.player.x + floor_dist * rdx; + let fy = game.player.y + floor_dist * rdy; + + let ftx = ((fx.fract().abs() * TEX_SIZE as f64) as usize).min(TEX_SIZE - 1); + let fty = ((fy.fract().abs() * TEX_SIZE as f64) as usize).min(TEX_SIZE - 1); + + let color = game.floor_texture[fty * TEX_SIZE + ftx]; + buffer[py * self.screen_w + sx] = + darken_fx(color, self.floor_bright_lut[py]); + } + } + } + } + + // ---- crosshair ----------------------------------------------------- + let cx = self.screen_w / 2; + let cy = self.screen_h / 2; + let cross_color = 0xFF00_FF00; + for i in 0..=4isize { + if cy as isize - i >= 0 { + buffer[(cy - i as usize) * self.screen_w + cx] = cross_color; + } + if (cy + i as usize) < self.screen_h { + buffer[(cy + i as usize) * self.screen_w + cx] = cross_color; + } + if cx as isize - i >= 0 { + buffer[cy * self.screen_w + (cx - i as usize)] = cross_color; + } + if (cx + i as usize) < self.screen_w { + buffer[cy * self.screen_w + (cx + i as usize)] = cross_color; + } + } + } +} + +/// Integer darken: `bright_fx` is brightness × 256 in fixed-point. +#[inline(always)] +fn darken_fx(color: u32, bright_fx: u32) -> u32 { + let r = (((color >> 16) & 0xFF) * bright_fx) >> 8; + let g = (((color >> 8) & 0xFF) * bright_fx) >> 8; + let b = ((color & 0xFF) * bright_fx) >> 8; + 0xFF00_0000 | (r << 16) | (g << 8) | b +} -- cgit v1.2.3