summaryrefslogtreecommitdiffstats
path: root/src/renderer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderer.rs')
-rw-r--r--src/renderer.rs71
1 files changed, 60 insertions, 11 deletions
diff --git a/src/renderer.rs b/src/renderer.rs
index 885e5de..86862f0 100644
--- a/src/renderer.rs
+++ b/src/renderer.rs
@@ -108,6 +108,8 @@ impl Renderer {
108 108
109 let half_h = self.screen_h as f64 / 2.0; 109 let half_h = self.screen_h as f64 / 2.0;
110 let fov = std::f64::consts::PI / 3.0; 110 let fov = std::f64::consts::PI / 3.0;
111 let drift = game.color_drift();
112 let breathing = game.wall_breathing();
111 113
112 // Per-column wall distances for entity occlusion. 114 // Per-column wall distances for entity occlusion.
113 // Store Euclidean (for occlusion) and perp (for rendering). 115 // Store Euclidean (for occlusion) and perp (for rendering).
@@ -173,7 +175,18 @@ impl Renderer {
173 break; 175 break;
174 } 176 }
175 177
176 if game.maze.is_wall(map_y as usize, map_x as usize) { 178 let ux = map_x as usize;
179 let uy = map_y as usize;
180
181 // Phantom passage: a wall that appears as a passage.
182 // Skip it — the ray passes through.
183 if game.maze.is_wall(uy, ux) && game.is_phantom_passage(ux, uy) {
184 continue;
185 }
186
187 // Phantom wall: a passage that appears as a wall.
188 // OR a real wall. Either way, stop the ray here.
189 if game.maze.is_wall(uy, ux) || game.is_phantom_wall(ux, uy) {
177 euclidean = if !hit_side { 190 euclidean = if !hit_side {
178 (map_x as f64 - game.player.x + (1.0 - step_x as f64) / 2.0) / rdx 191 (map_x as f64 - game.player.x + (1.0 - step_x as f64) / 2.0) / rdx
179 } else { 192 } else {
@@ -198,11 +211,13 @@ impl Renderer {
198 game.player.x + euclidean * rdx 211 game.player.x + euclidean * rdx
199 }; 212 };
200 let wall_x = wall_x - wall_x.floor(); 213 let wall_x = wall_x - wall_x.floor();
201 let tex_x = ((wall_x * TEX_SIZE as f64) as usize).min(TEX_SIZE - 1); 214 // Wall breathing: subtle horizontal texture wobble.
215 let tex_x = (((wall_x + breathing / TEX_SIZE as f64) * TEX_SIZE as f64) as isize)
216 .rem_euclid(TEX_SIZE as isize) as usize;
202 217
203 let wall_bright_fx = { 218 let wall_bright_fx = {
204 let w = game.weakness_factor(); 219 let d = game.darkness_factor();
205 let darken_scale = 1.0 + w * 12.0; 220 let darken_scale = 1.0 + d * 12.0;
206 let b = 1.0 / (1.0 + perp_dist * 0.25 * darken_scale); 221 let b = 1.0 / (1.0 + perp_dist * 0.25 * darken_scale);
207 (b * 256.0) as u32 222 (b * 256.0) as u32
208 }; 223 };
@@ -212,13 +227,14 @@ impl Renderer {
212 / wall_h as f64) as usize; 227 / wall_h as f64) as usize;
213 let tex_y = tex_y.min(TEX_SIZE - 1); 228 let tex_y = tex_y.min(TEX_SIZE - 1);
214 let color = game.wall_texture[tex_y * TEX_SIZE + tex_x]; 229 let color = game.wall_texture[tex_y * TEX_SIZE + tex_x];
215 buffer[py * self.screen_w + sx] = darken_fx(color, wall_bright_fx); 230 buffer[py * self.screen_w + sx] =
231 color_drift_fx(darken_fx(color, wall_bright_fx), drift);
216 } 232 }
217 233
218 let floor_start = draw_end + 1; 234 let floor_start = draw_end + 1;
219 if floor_start < self.screen_h { 235 if floor_start < self.screen_h {
220 let w = game.weakness_factor(); 236 let d = game.darkness_factor();
221 let darken_scale = 1.0 + w * 12.0; 237 let darken_scale = 1.0 + d * 12.0;
222 for py in floor_start..self.screen_h { 238 for py in floor_start..self.screen_h {
223 let floor_dist = self.floor_dist_lut[py]; 239 let floor_dist = self.floor_dist_lut[py];
224 let bright = 1.0 / (1.0 + floor_dist * 0.35 * darken_scale); 240 let bright = 1.0 / (1.0 + floor_dist * 0.35 * darken_scale);
@@ -226,7 +242,7 @@ impl Renderer {
226 242
227 if floor_dist > 15.0 { 243 if floor_dist > 15.0 {
228 let gray = darken_fx(0xFF80_8080, bright_fx); 244 let gray = darken_fx(0xFF80_8080, bright_fx);
229 buffer[py * self.screen_w + sx] = gray; 245 buffer[py * self.screen_w + sx] = color_drift_fx(gray, drift);
230 } else { 246 } else {
231 let fx = game.player.x + floor_dist * rdx_floor; 247 let fx = game.player.x + floor_dist * rdx_floor;
232 let fy = game.player.y + floor_dist * rdy_floor; 248 let fy = game.player.y + floor_dist * rdy_floor;
@@ -238,7 +254,8 @@ impl Renderer {
238 } else { 254 } else {
239 game.floor_texture[fty * TEX_SIZE + ftx] 255 game.floor_texture[fty * TEX_SIZE + ftx]
240 }; 256 };
241 buffer[py * self.screen_w + sx] = darken_fx(color, bright_fx); 257 buffer[py * self.screen_w + sx] =
258 color_drift_fx(darken_fx(color, bright_fx), drift);
242 } 259 }
243 } 260 }
244 } 261 }
@@ -352,7 +369,8 @@ impl Renderer {
352 s.scale, 369 s.scale,
353 s.color, 370 s.color,
354 s.cam_z, 371 s.cam_z,
355 game.weakness_factor(), 372 game.darkness_factor(),
373 game.color_drift(),
356 wall_euclidean, 374 wall_euclidean,
357 ); 375 );
358 } 376 }
@@ -418,6 +436,7 @@ impl Renderer {
418 color: u32, 436 color: u32,
419 cam_z: f64, 437 cam_z: f64,
420 weakness: f64, 438 weakness: f64,
439 drift: f64,
421 wall_euclidean: &[f64], 440 wall_euclidean: &[f64],
422 ) { 441 ) {
423 let glyph = match find_glyph(ch) { 442 let glyph = match find_glyph(ch) {
@@ -428,7 +447,7 @@ impl Renderer {
428 let darken_scale = 1.0 + weakness * 12.0; 447 let darken_scale = 1.0 + weakness * 12.0;
429 let bright = 1.0 / (1.0 + cam_z * 0.15 * darken_scale); 448 let bright = 1.0 / (1.0 + cam_z * 0.15 * darken_scale);
430 let bright_fx = (bright * 256.0) as u32; 449 let bright_fx = (bright * 256.0) as u32;
431 let final_color = darken_fx(color, bright_fx); 450 let final_color = color_drift_fx(darken_fx(color, bright_fx), drift);
432 451
433 let draw_w = (LETTER_W as f64 * scale) as isize; 452 let draw_w = (LETTER_W as f64 * scale) as isize;
434 let draw_h = (LETTER_H as f64 * scale) as isize; 453 let draw_h = (LETTER_H as f64 * scale) as isize;
@@ -707,3 +726,33 @@ fn darken_fx(color: u32, bright_fx: u32) -> u32 {
707 let b = ((color & 0xFF) * bright_fx) >> 8; 726 let b = ((color & 0xFF) * bright_fx) >> 8;
708 0xFF00_0000 | (r << 16) | (g << 8) | b 727 0xFF00_0000 | (r << 16) | (g << 8) | b
709} 728}
729
730/// Subtle hue rotation for sanity-based colour drift.
731/// `drift` is a small angle in radians; 0 means no change.
732fn color_drift_fx(color: u32, drift: f64) -> u32 {
733 if drift.abs() < 0.001 {
734 return color;
735 }
736 let r = ((color >> 16) & 0xFF) as f64 / 255.0;
737 let g = ((color >> 8) & 0xFF) as f64 / 255.0;
738 let b = (color & 0xFF) as f64 / 255.0;
739
740 // Cheap hue shift: rotate in RGB space around the gray axis.
741 let gray = (r + g + b) / 3.0;
742 let dr = r - gray;
743 let dg = g - gray;
744 let db = b - gray;
745
746 let cos_d = drift.cos();
747 let sin_d = drift.sin();
748
749 // Rotate the RG plane (simple 2D projection for subtle effect).
750 let nr = gray + dr * cos_d - dg * sin_d * 0.5 + db * sin_d * 0.3;
751 let ng = gray + dg * cos_d + dr * sin_d * 0.5 - db * sin_d * 0.3;
752 let nb = gray + db * cos_d - dr * sin_d * 0.3 + dg * sin_d * 0.3;
753
754 let r = (nr.clamp(0.0, 1.0) * 255.0) as u32;
755 let g = (ng.clamp(0.0, 1.0) * 255.0) as u32;
756 let b = (nb.clamp(0.0, 1.0) * 255.0) as u32;
757 0xFF00_0000 | (r << 16) | (g << 8) | b
758}