use crate::base::{Rect, Rgba};
use crate::render::cell::Cell;
use crate::render::layer::CellShader;
pub(super) const STABLE: Option<Rect> = Some(Rect::ZERO);
fn wave(phase: f32) -> f32 {
let frac = phase - phase.floor();
(frac - 0.5).abs() * 4.0 - 1.0
}
pub(super) fn cell_hash(x: i32, y: i32, seed: u32) -> f32 {
let mut h = (x as u32).wrapping_mul(0x9E37_79B9)
^ (y as u32).wrapping_mul(0x85EB_CA6B)
^ seed.wrapping_mul(0xC2B2_AE35);
h ^= h >> 16;
h = h.wrapping_mul(0x7FEB_352D);
h ^= h >> 15;
h = h.wrapping_mul(0x846C_A68B);
h ^= h >> 16;
(h >> 8) as f32 / 16_777_216.0
}
fn scale_channels(c: Rgba, f: f32) -> Rgba {
if c.is_transparent() {
return c;
}
let s = |v: u8| ((v as f32 * f).round() as i64).clamp(0, 255) as u8;
Rgba::new(s(c.r), s(c.g), s(c.b), c.a)
}
pub(super) const TRANSPARENT_CELL: Cell = Cell::EMPTY;
#[derive(Copy, Clone, Debug)]
pub struct Shimmer {
pub speed: f32,
pub amplitude: f32,
pub wavelength: f32,
}
impl Default for Shimmer {
fn default() -> Self {
Shimmer {
speed: 0.8,
amplitude: 0.18,
wavelength: 14.0,
}
}
}
impl CellShader for Shimmer {
fn shade(&self, x: i32, y: i32, t: f32, cell: Cell) -> Cell {
let wavelength = if self.wavelength.abs() < 1e-3 {
1.0
} else {
self.wavelength
};
let phase = t * self.speed - (x + y) as f32 / wavelength;
let f = 1.0 + self.amplitude * wave(phase);
let mut c = cell;
c.fg = scale_channels(c.fg, f);
c.ul = scale_channels(c.ul, f);
c
}
fn changed_region(&self, t0: f32, t1: f32, _bounds: Rect) -> Option<Rect> {
if self.amplitude == 0.0 || t0 * self.speed == t1 * self.speed {
STABLE
} else {
None
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct HueDrift {
pub speed: f32,
pub strength: f32,
}
impl Default for HueDrift {
fn default() -> Self {
HueDrift {
speed: 0.5,
strength: 0.35,
}
}
}
impl CellShader for HueDrift {
fn shade(&self, _x: i32, _y: i32, t: f32, cell: Cell) -> Cell {
let pulse = (wave(t * self.speed) + 1.0) * 0.5; let k = (self.strength.clamp(0.0, 1.0)) * pulse;
let mut c = cell;
c.fg = drift(c.fg, k);
c.ul = drift(c.ul, k);
c
}
fn changed_region(&self, t0: f32, t1: f32, _bounds: Rect) -> Option<Rect> {
if self.strength <= 0.0 || wave(t0 * self.speed) == wave(t1 * self.speed) {
STABLE
} else {
None
}
}
}
fn drift(c: Rgba, k: f32) -> Rgba {
if c.is_transparent() {
return c;
}
let rotated = Rgba::new(c.g, c.b, c.r, c.a);
c.lerp(rotated, k)
}
#[derive(Copy, Clone, Debug)]
pub struct Pulse {
pub speed: f32,
pub amplitude: f32,
}
impl Default for Pulse {
fn default() -> Self {
Pulse {
speed: 1.2,
amplitude: 0.3,
}
}
}
impl CellShader for Pulse {
fn shade(&self, _x: i32, _y: i32, t: f32, cell: Cell) -> Cell {
let k = (wave(t * self.speed + 0.5) + 1.0) * 0.5;
let f = 1.0 + self.amplitude * k;
let mut c = cell;
c.fg = scale_channels(c.fg, f);
c.ul = scale_channels(c.ul, f);
c
}
fn changed_region(&self, t0: f32, t1: f32, _bounds: Rect) -> Option<Rect> {
if self.amplitude == 0.0 || wave(t0 * self.speed + 0.5) == wave(t1 * self.speed + 0.5) {
STABLE
} else {
None
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Sweep {
pub period: f32,
pub band: f32,
pub boost: f32,
pub travel: f32,
}
impl Default for Sweep {
fn default() -> Self {
Sweep {
period: 1.6,
band: 6.0,
boost: 0.5,
travel: 90.0,
}
}
}
impl CellShader for Sweep {
fn shade(&self, x: i32, y: i32, t: f32, cell: Cell) -> Cell {
if self.period <= 0.0 || self.band <= 0.0 {
return cell;
}
let phase = t / self.period;
let center = (phase - phase.floor()) * self.travel;
let d = ((x + y) as f32 - center).abs();
let half = self.band * 0.5;
if d >= half {
return cell;
}
let k = 1.0 - d / half; let f = 1.0 + self.boost * k;
let mut c = cell;
c.fg = scale_channels(c.fg, f);
c.bg = scale_channels(c.bg, f);
c.ul = scale_channels(c.ul, f);
c
}
fn changed_region(&self, t0: f32, t1: f32, bounds: Rect) -> Option<Rect> {
if self.period <= 0.0 || self.band <= 0.0 {
return STABLE; }
let center = |t: f32| {
let phase = t / self.period;
(phase - phase.floor()) * self.travel
};
let (c0, c1) = (center(t0), center(t1));
if c0 == c1 {
return STABLE;
}
let half = self.band * 0.5;
let lo = c0.min(c1) - half;
let hi = c0.max(c1) + half;
let y1 = bounds.bottom() - 1;
let x_lo = (lo - y1 as f32).floor() as i32;
let x_hi = (hi - bounds.y as f32).ceil() as i32;
Some(Rect::new(
x_lo,
bounds.y,
(x_hi - x_lo + 1).max(0),
bounds.h,
))
}
}
#[derive(Copy, Clone, Debug)]
pub struct Rainbow {
pub speed: f32,
pub wavelength: f32,
pub strength: f32,
}
impl Default for Rainbow {
fn default() -> Self {
Rainbow {
speed: 0.4,
wavelength: 24.0,
strength: 1.0,
}
}
}
impl CellShader for Rainbow {
fn shade(&self, x: i32, y: i32, t: f32, cell: Cell) -> Cell {
let wavelength = if self.wavelength.abs() < 1e-3 {
1.0
} else {
self.wavelength
};
let phase = t * self.speed + (x + y) as f32 / wavelength;
let hue = phase - phase.floor();
let mut c = cell;
c.fg = toward_wheel(c.fg, hue, self.strength);
c.ul = toward_wheel(c.ul, hue, self.strength);
c
}
fn changed_region(&self, t0: f32, t1: f32, _bounds: Rect) -> Option<Rect> {
if self.strength <= 0.0 || t0 * self.speed == t1 * self.speed {
STABLE
} else {
None
}
}
}
fn hue_wheel(h: f32) -> (f32, f32, f32) {
let h6 = (h - h.floor()) * 6.0;
let seg = h6 as i32 % 6;
let f = h6 - h6.floor();
match seg {
0 => (1.0, f, 0.0),
1 => (1.0 - f, 1.0, 0.0),
2 => (0.0, 1.0, f),
3 => (0.0, 1.0 - f, 1.0),
4 => (f, 0.0, 1.0),
_ => (1.0, 0.0, 1.0 - f),
}
}
fn toward_wheel(c: Rgba, hue: f32, strength: f32) -> Rgba {
if c.is_transparent() {
return c;
}
let level = (c.r as f32 + c.g as f32 + c.b as f32) / 3.0;
let (r, g, b) = hue_wheel(hue);
let wheel = Rgba::new(
(r * level).round() as u8,
(g * level).round() as u8,
(b * level).round() as u8,
c.a,
);
c.lerp(wheel, strength.clamp(0.0, 1.0))
}
#[derive(Copy, Clone, Debug)]
pub struct Vignette {
pub size: (i32, i32),
pub inner: f32,
pub strength: f32,
}
impl CellShader for Vignette {
fn changed_region(&self, _t0: f32, _t1: f32, _bounds: Rect) -> Option<Rect> {
STABLE
}
fn shade(&self, x: i32, y: i32, _t: f32, cell: Cell) -> Cell {
let (w, h) = (self.size.0.max(1) as f32, self.size.1.max(1) as f32 * 2.0);
let px = x as f32 + 0.5;
let py = (y as f32 + 0.5) * 2.0;
let (cx, cy) = (w * 0.5, h * 0.5);
let far = (cx * cx + cy * cy).sqrt().max(1e-6);
let d = (((px - cx).powi(2) + (py - cy).powi(2)).sqrt() / far).clamp(0.0, 1.0);
let inner = self.inner.clamp(0.0, 0.99);
if d <= inner {
return cell;
}
let k = ((d - inner) / (1.0 - inner)).clamp(0.0, 1.0);
let k = k * k * (3.0 - 2.0 * k);
let f = 1.0 - self.strength.clamp(0.0, 1.0) * k;
let mut c = cell;
c.fg = scale_channels(c.fg, f);
c.bg = scale_channels(c.bg, f);
c.ul = scale_channels(c.ul, f);
c
}
}
#[path = "shaders_reveal.rs"]
mod shaders_reveal;
pub use shaders_reveal::{Dissolve, GradientReveal, ScanlineFade};
#[cfg(test)]
#[path = "shaders_tests.rs"]
mod tests;