summaryrefslogtreecommitdiffstats
path: root/src/renderer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderer.rs')
-rw-r--r--src/renderer.rs196
1 files changed, 196 insertions, 0 deletions
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 @@
1use crate::game::Game;
2use crate::texture::TEX_SIZE;
3
4pub struct Renderer {
5 screen_w: usize,
6 screen_h: usize,
7 /// Precomputed: floor_dist_lut[row] = perpendicular distance to floor at that row.
8 floor_dist_lut: Vec<f64>,
9 /// Precomputed: floor_bright_lut[row] = brightness × 256 for that row (fixed-point).
10 floor_bright_lut: Vec<u32>,
11}
12
13impl Renderer {
14 pub fn new(screen_w: usize, screen_h: usize) -> Self {
15 let half_h = screen_h as f64 / 2.0;
16 let mut floor_dist_lut = vec![0.0f64; screen_h];
17 let mut floor_bright_lut = vec![0u32; screen_h];
18
19 // Only rows below the horizon have meaningful floor values.
20 for py in (screen_h / 2 + 1)..screen_h {
21 let dy = py as f64 - half_h;
22 let dist = 0.5 * screen_h as f64 / dy;
23 floor_dist_lut[py] = dist;
24 let bright = (1.0 / (1.0 + dist * 0.15)).max(0.05);
25 floor_bright_lut[py] = (bright * 256.0) as u32;
26 }
27
28 Self {
29 screen_w,
30 screen_h,
31 floor_dist_lut,
32 floor_bright_lut,
33 }
34 }
35
36 /// Produce one frame into `buffer` (packed `0xAARRGGBB`).
37 pub fn render(&self, game: &Game, buffer: &mut [u32]) {
38 buffer.fill(0xFF00_0000); // pure-black ceiling
39
40 let half_h = self.screen_h as f64 / 2.0;
41 let fov = std::f64::consts::PI / 3.0; // 60°
42
43 for sx in 0..self.screen_w {
44 let cam_x = 2.0 * sx as f64 / self.screen_w as f64 - 1.0;
45 let ray_angle = game.player.angle + (fov / 2.0) * cam_x;
46 let rdx = ray_angle.cos();
47 let rdy = ray_angle.sin();
48
49 // ---- DDA setup -------------------------------------------------
50 let mut map_x = game.player.x as isize;
51 let mut map_y = game.player.y as isize;
52
53 let ddx = if rdx == 0.0 {
54 f64::MAX
55 } else {
56 (1.0 / rdx).abs()
57 };
58 let ddy = if rdy == 0.0 {
59 f64::MAX
60 } else {
61 (1.0 / rdy).abs()
62 };
63
64 let (step_x, mut side_x) = if rdx < 0.0 {
65 (-1isize, (game.player.x - map_x as f64) * ddx)
66 } else {
67 (1isize, (map_x as f64 + 1.0 - game.player.x) * ddx)
68 };
69
70 let (step_y, mut side_y) = if rdy < 0.0 {
71 (-1isize, (game.player.y - map_y as f64) * ddy)
72 } else {
73 (1isize, (map_y as f64 + 1.0 - game.player.y) * ddy)
74 };
75
76 let mut hit_side: bool;
77 let euclidean: f64;
78
79 // ---- DDA loop --------------------------------------------------
80 loop {
81 if side_x < side_y {
82 side_x += ddx;
83 map_x += step_x;
84 hit_side = false;
85 } else {
86 side_y += ddy;
87 map_y += step_y;
88 hit_side = true;
89 }
90
91 if map_y < 0
92 || map_y >= game.maze.height() as isize
93 || map_x < 0
94 || map_x >= game.maze.width() as isize
95 {
96 euclidean = 1.0;
97 break;
98 }
99
100 if game.maze.is_wall(map_y as usize, map_x as usize) {
101 euclidean = if !hit_side {
102 (map_x as f64 - game.player.x + (1.0 - step_x as f64) / 2.0) / rdx
103 } else {
104 (map_y as f64 - game.player.y + (1.0 - step_y as f64) / 2.0) / rdy
105 };
106 break;
107 }
108 }
109
110 // Fisheye correction
111 let perp_dist = (euclidean * (ray_angle - game.player.angle).cos().abs()).max(0.001);
112
113 // ---- wall slice ------------------------------------------------
114 let wall_h = (self.screen_h as f64 / perp_dist) as isize;
115 let draw_start = ((-wall_h / 2) + self.screen_h as isize / 2).max(0) as usize;
116 let draw_end = ((wall_h / 2) + self.screen_h as isize / 2)
117 .min(self.screen_h as isize - 1) as usize;
118
119 // Texture x-coordinate (uses Euclidean, not perp)
120 let wall_x = if !hit_side {
121 game.player.y + euclidean * rdy
122 } else {
123 game.player.x + euclidean * rdx
124 };
125 let wall_x = wall_x - wall_x.floor();
126 let tex_x = ((wall_x * TEX_SIZE as f64) as usize).min(TEX_SIZE - 1);
127
128 // Hoist: brightness is identical for every pixel in this column
129 let wall_bright_fx = {
130 let b = (1.0 / (1.0 + perp_dist * 0.12)).max(0.08);
131 (b * 256.0) as u32
132 };
133
134 for py in draw_start..=draw_end {
135 let tex_y = ((py as f64 - half_h + wall_h as f64 / 2.0) * TEX_SIZE as f64
136 / wall_h as f64) as usize;
137 let tex_y = tex_y.min(TEX_SIZE - 1);
138
139 let color = game.wall_texture[tex_y * TEX_SIZE + tex_x];
140 buffer[py * self.screen_w + sx] = darken_fx(color, wall_bright_fx);
141 }
142
143 // ---- floor below the wall --------------------------------------
144 let floor_start = draw_end + 1;
145 if floor_start < self.screen_h {
146 for py in floor_start..self.screen_h {
147 let floor_dist = self.floor_dist_lut[py];
148
149 if floor_dist > 15.0 {
150 // Far floor → just a dark pixel, skip texture & world calc.
151 let gray = darken_fx(0xFF80_8080, self.floor_bright_lut[py]);
152 buffer[py * self.screen_w + sx] = gray;
153 } else {
154 let fx = game.player.x + floor_dist * rdx;
155 let fy = game.player.y + floor_dist * rdy;
156
157 let ftx = ((fx.fract().abs() * TEX_SIZE as f64) as usize).min(TEX_SIZE - 1);
158 let fty = ((fy.fract().abs() * TEX_SIZE as f64) as usize).min(TEX_SIZE - 1);
159
160 let color = game.floor_texture[fty * TEX_SIZE + ftx];
161 buffer[py * self.screen_w + sx] =
162 darken_fx(color, self.floor_bright_lut[py]);
163 }
164 }
165 }
166 }
167
168 // ---- crosshair -----------------------------------------------------
169 let cx = self.screen_w / 2;
170 let cy = self.screen_h / 2;
171 let cross_color = 0xFF00_FF00;
172 for i in 0..=4isize {
173 if cy as isize - i >= 0 {
174 buffer[(cy - i as usize) * self.screen_w + cx] = cross_color;
175 }
176 if (cy + i as usize) < self.screen_h {
177 buffer[(cy + i as usize) * self.screen_w + cx] = cross_color;
178 }
179 if cx as isize - i >= 0 {
180 buffer[cy * self.screen_w + (cx - i as usize)] = cross_color;
181 }
182 if (cx + i as usize) < self.screen_w {
183 buffer[cy * self.screen_w + (cx + i as usize)] = cross_color;
184 }
185 }
186 }
187}
188
189/// Integer darken: `bright_fx` is brightness × 256 in fixed-point.
190#[inline(always)]
191fn darken_fx(color: u32, bright_fx: u32) -> u32 {
192 let r = (((color >> 16) & 0xFF) * bright_fx) >> 8;
193 let g = (((color >> 8) & 0xFF) * bright_fx) >> 8;
194 let b = ((color & 0xFF) * bright_fx) >> 8;
195 0xFF00_0000 | (r << 16) | (g << 8) | b
196}