From a4626cb681fdb4939772018def2019c1d1c49d46 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 11 Jul 2026 16:05:15 +0200 Subject: add psychedelic horror effects: phantom walls, shifting maze, darkness, color drift, persistent hallucinations --- src/renderer.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 11 deletions(-) (limited to 'src/renderer.rs') 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 { let half_h = self.screen_h as f64 / 2.0; let fov = std::f64::consts::PI / 3.0; + let drift = game.color_drift(); + let breathing = game.wall_breathing(); // Per-column wall distances for entity occlusion. // Store Euclidean (for occlusion) and perp (for rendering). @@ -173,7 +175,18 @@ impl Renderer { break; } - if game.maze.is_wall(map_y as usize, map_x as usize) { + let ux = map_x as usize; + let uy = map_y as usize; + + // Phantom passage: a wall that appears as a passage. + // Skip it — the ray passes through. + if game.maze.is_wall(uy, ux) && game.is_phantom_passage(ux, uy) { + continue; + } + + // Phantom wall: a passage that appears as a wall. + // OR a real wall. Either way, stop the ray here. + if game.maze.is_wall(uy, ux) || game.is_phantom_wall(ux, uy) { euclidean = if !hit_side { (map_x as f64 - game.player.x + (1.0 - step_x as f64) / 2.0) / rdx } else { @@ -198,11 +211,13 @@ impl Renderer { 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); + // Wall breathing: subtle horizontal texture wobble. + let tex_x = (((wall_x + breathing / TEX_SIZE as f64) * TEX_SIZE as f64) as isize) + .rem_euclid(TEX_SIZE as isize) as usize; let wall_bright_fx = { - let w = game.weakness_factor(); - let darken_scale = 1.0 + w * 12.0; + let d = game.darkness_factor(); + let darken_scale = 1.0 + d * 12.0; let b = 1.0 / (1.0 + perp_dist * 0.25 * darken_scale); (b * 256.0) as u32 }; @@ -212,13 +227,14 @@ impl Renderer { / 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); + buffer[py * self.screen_w + sx] = + color_drift_fx(darken_fx(color, wall_bright_fx), drift); } let floor_start = draw_end + 1; if floor_start < self.screen_h { - let w = game.weakness_factor(); - let darken_scale = 1.0 + w * 12.0; + let d = game.darkness_factor(); + let darken_scale = 1.0 + d * 12.0; for py in floor_start..self.screen_h { let floor_dist = self.floor_dist_lut[py]; let bright = 1.0 / (1.0 + floor_dist * 0.35 * darken_scale); @@ -226,7 +242,7 @@ impl Renderer { if floor_dist > 15.0 { let gray = darken_fx(0xFF80_8080, bright_fx); - buffer[py * self.screen_w + sx] = gray; + buffer[py * self.screen_w + sx] = color_drift_fx(gray, drift); } else { let fx = game.player.x + floor_dist * rdx_floor; let fy = game.player.y + floor_dist * rdy_floor; @@ -238,7 +254,8 @@ impl Renderer { } else { game.floor_texture[fty * TEX_SIZE + ftx] }; - buffer[py * self.screen_w + sx] = darken_fx(color, bright_fx); + buffer[py * self.screen_w + sx] = + color_drift_fx(darken_fx(color, bright_fx), drift); } } } @@ -352,7 +369,8 @@ impl Renderer { s.scale, s.color, s.cam_z, - game.weakness_factor(), + game.darkness_factor(), + game.color_drift(), wall_euclidean, ); } @@ -418,6 +436,7 @@ impl Renderer { color: u32, cam_z: f64, weakness: f64, + drift: f64, wall_euclidean: &[f64], ) { let glyph = match find_glyph(ch) { @@ -428,7 +447,7 @@ impl Renderer { let darken_scale = 1.0 + weakness * 12.0; let bright = 1.0 / (1.0 + cam_z * 0.15 * darken_scale); let bright_fx = (bright * 256.0) as u32; - let final_color = darken_fx(color, bright_fx); + let final_color = color_drift_fx(darken_fx(color, bright_fx), drift); let draw_w = (LETTER_W as f64 * scale) as isize; let draw_h = (LETTER_H as f64 * scale) as isize; @@ -707,3 +726,33 @@ fn darken_fx(color: u32, bright_fx: u32) -> u32 { let b = ((color & 0xFF) * bright_fx) >> 8; 0xFF00_0000 | (r << 16) | (g << 8) | b } + +/// Subtle hue rotation for sanity-based colour drift. +/// `drift` is a small angle in radians; 0 means no change. +fn color_drift_fx(color: u32, drift: f64) -> u32 { + if drift.abs() < 0.001 { + return color; + } + let r = ((color >> 16) & 0xFF) as f64 / 255.0; + let g = ((color >> 8) & 0xFF) as f64 / 255.0; + let b = (color & 0xFF) as f64 / 255.0; + + // Cheap hue shift: rotate in RGB space around the gray axis. + let gray = (r + g + b) / 3.0; + let dr = r - gray; + let dg = g - gray; + let db = b - gray; + + let cos_d = drift.cos(); + let sin_d = drift.sin(); + + // Rotate the RG plane (simple 2D projection for subtle effect). + let nr = gray + dr * cos_d - dg * sin_d * 0.5 + db * sin_d * 0.3; + let ng = gray + dg * cos_d + dr * sin_d * 0.5 - db * sin_d * 0.3; + let nb = gray + db * cos_d - dr * sin_d * 0.3 + dg * sin_d * 0.3; + + let r = (nr.clamp(0.0, 1.0) * 255.0) as u32; + let g = (ng.clamp(0.0, 1.0) * 255.0) as u32; + let b = (nb.clamp(0.0, 1.0) * 255.0) as u32; + 0xFF00_0000 | (r << 16) | (g << 8) | b +} -- cgit v1.2.3