Files
cyberpunk/crates/city/src/render/mod.rs
Jeffrey Wilcke 741231c25b feat(city): digital net overlay
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-12 17:42:11 +02:00

118 lines
3.3 KiB
Rust

//! Rendering: colours, cells, the render context and the tileset.
pub mod lighting;
pub mod minimap;
pub mod overlay;
pub mod style;
pub mod tileset;
pub mod traffic;
pub mod viewport;
pub mod weather;
use citygen::{BuildingKind, ConnectorKind, Pos, StratumKind, Theme};
use ratatui::buffer::Buffer;
use ratatui::style::{Color, Modifier, Style};
use serde::{Deserialize, Serialize};
use style::StylePack;
/// A truecolor value.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Rgb(pub u8, pub u8, pub u8);
impl Rgb {
pub fn to_color(self) -> Color {
Color::Rgb(self.0, self.1, self.2)
}
/// Multiplies every channel, saturating at 255.
pub fn scale(self, f: f32) -> Rgb {
let s = |c: u8| ((c as f32) * f).round().clamp(0.0, 255.0) as u8;
Rgb(s(self.0), s(self.1), s(self.2))
}
/// Linear blend toward `other` by `t` in 0..=1.
pub fn mix(self, other: Rgb, t: f32) -> Rgb {
let t = t.clamp(0.0, 1.0);
let m = |a: u8, b: u8| ((a as f32) + ((b as f32) - (a as f32)) * t).round() as u8;
Rgb(m(self.0, other.0), m(self.1, other.1), m(self.2, other.2))
}
/// Adds a signed offset per channel, clamped.
pub fn tint(self, dr: i16, dg: i16, db: i16) -> Rgb {
let t = |c: u8, d: i16| (c as i16 + d).clamp(0, 255) as u8;
Rgb(t(self.0, dr), t(self.1, dg), t(self.2, db))
}
}
/// One rendered terminal cell.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cell {
pub ch: char,
pub fg: Rgb,
pub bg: Rgb,
pub bold: bool,
pub dim: bool,
}
impl Cell {
pub fn write(&self, buf: &mut Buffer, x: u16, y: u16) {
if x < buf.area.x || y < buf.area.y || x >= buf.area.right() || y >= buf.area.bottom() {
return;
}
let mut style = Style::default()
.fg(self.fg.to_color())
.bg(self.bg.to_color());
if self.bold {
style = style.add_modifier(Modifier::BOLD);
}
if self.dim {
style = style.add_modifier(Modifier::DIM);
}
buf[(x, y)].set_char(self.ch).set_style(style);
}
}
/// What the walker knows about a tile.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Visibility {
Unseen,
Remembered,
Visible,
}
/// A connector under the tile and which way it leads from the current stratum.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ConnDir {
pub kind: ConnectorKind,
pub up: bool,
pub down: bool,
}
/// Everything `tileset::cell` needs besides the tile itself.
pub struct RenderCtx<'a> {
pub style: &'a StylePack,
pub theme: Option<Theme>,
pub stratum: StratumKind,
pub light: u8,
pub tick: u64,
pub pos: Pos,
pub vis: Visibility,
pub connector: Option<ConnDir>,
pub kind: Option<BuildingKind>,
/// Bits N=1, E=2, S=4, W=8: neighbours that connect to this tile (see `tileset::connects`).
pub wall_mask: u8,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rgb_math() {
assert_eq!(Rgb(100, 200, 50).scale(0.5), Rgb(50, 100, 25));
assert_eq!(Rgb(100, 100, 100).scale(10.0), Rgb(255, 255, 255));
assert_eq!(Rgb(0, 0, 0).mix(Rgb(100, 200, 50), 0.5), Rgb(50, 100, 25));
assert_eq!(Rgb(10, 10, 10).tint(-20, 5, 300), Rgb(0, 15, 255));
}
}