use super::math::{Camera, Vec3};
use ratatui::style::Color;
#[derive(Clone, Copy, PartialEq, Default)]
pub enum RenderMode {
#[default]
Solid,
Wireframe,
}
impl RenderMode {
pub fn next(self) -> Self {
match self {
Self::Solid => Self::Wireframe,
Self::Wireframe => Self::Solid,
}
}
pub fn name(self) -> &'static str {
match self {
Self::Solid => "Solid",
Self::Wireframe => "Wire",
}
}
}
#[derive(Clone, Copy, PartialEq, Default)]
pub enum CharStyle {
#[default]
Ascii, Blocks, Braille, Dense, Minimal, Hatching, Dots, Stars, }
impl CharStyle {
pub fn next(self) -> Self {
match self {
Self::Ascii => Self::Blocks,
Self::Blocks => Self::Braille,
Self::Braille => Self::Dense,
Self::Dense => Self::Minimal,
Self::Minimal => Self::Hatching,
Self::Hatching => Self::Dots,
Self::Dots => Self::Stars,
Self::Stars => Self::Ascii,
}
}
pub fn name(self) -> &'static str {
match self {
Self::Ascii => "ASCII",
Self::Blocks => "Blocks",
Self::Braille => "Braille",
Self::Dense => "Dense",
Self::Minimal => "Minimal",
Self::Hatching => "Hatching",
Self::Dots => "Dots",
Self::Stars => "Stars",
}
}
pub fn chars(self) -> &'static [char] {
match self {
Self::Ascii => &[' ', '.', ',', '-', '~', ':', ';', '=', '!', '*', '#', '$', '@'],
Self::Blocks => &[' ', '░', '░', '▒', '▒', '▓', '▓', '█', '█'],
Self::Braille => &[' ', '⠁', '⠃', '⠇', '⠏', '⠟', '⠿', '⡿', '⣿'],
Self::Dense => &[' ', '`', '.', '-', '\'', ':', '_', ',', '^', '=', ';', '>', '<',
'+', '!', 'r', 'c', '*', '/', 'z', '?', 's', 'L', 'T', 'v',
')', 'J', '7', '(', '|', 'F', 'i', '{', 'C', '}', 'f', 'I',
'3', '1', 't', 'l', 'u', '[', 'n', 'e', 'o', 'Z', '5', 'Y',
'x', 'j', 'y', 'a', ']', '2', 'E', 'S', 'w', 'q', 'k', 'P',
'6', 'h', '9', 'd', '4', 'V', 'p', 'O', 'G', 'b', 'U', 'A',
'K', 'X', 'H', 'm', '8', 'R', 'D', '#', '$', 'B', 'g', '0',
'M', 'N', 'W', 'Q', '%', '&', '@'],
Self::Minimal => &[' ', '.', ':', '+', '*', '#', '@'],
Self::Hatching => &[' ', '·', '-', '/', '|', '\\', '+', 'x', 'X', '#', '▓', '█'],
Self::Dots => &[' ', '·', '∙', '•', '●', '◉', '◎', '○', '◌', '◍', '◉', '●', '⬤'],
Self::Stars => &[' ', '·', '˙', '*', '✦', '✧', '★', '☆', '✪', '✫', '✬', '✯', '✵'],
}
}
pub fn to_char(self, luminance: f32) -> char {
let chars = self.chars();
let idx = ((luminance.clamp(0.0, 1.0)) * (chars.len() - 1) as f32) as usize;
chars[idx.min(chars.len() - 1)]
}
}
#[derive(Clone, Copy, PartialEq, Default)]
pub enum ColorPalette {
#[default]
Cyan,
Fire,
Matrix,
Purple,
Grayscale,
Rainbow,
}
impl ColorPalette {
pub fn next(self) -> Self {
match self {
Self::Cyan => Self::Fire,
Self::Fire => Self::Matrix,
Self::Matrix => Self::Purple,
Self::Purple => Self::Grayscale,
Self::Grayscale => Self::Rainbow,
Self::Rainbow => Self::Cyan,
}
}
pub fn name(self) -> &'static str {
match self {
Self::Cyan => "Cyan",
Self::Fire => "Fire",
Self::Matrix => "Matrix",
Self::Purple => "Purple",
Self::Grayscale => "Grayscale",
Self::Rainbow => "Rainbow",
}
}
pub fn to_color(self, luminance: f32) -> Color {
let l = luminance.clamp(0.0, 1.0);
match self {
Self::Cyan => {
let r = (l * l * 255.0) as u8;
let g = (l * 200.0 + 55.0 * l * l) as u8;
let b = (100.0 + l * 155.0) as u8;
Color::Rgb(r, g, b)
}
Self::Fire => {
let r = (50.0 + l * 205.0) as u8;
let g = (l * l * 180.0) as u8;
let b = (l * l * l * 80.0) as u8;
Color::Rgb(r, g, b)
}
Self::Matrix => {
let r = (l * l * 50.0) as u8;
let g = (40.0 + l * 215.0) as u8;
let b = (l * l * 80.0) as u8;
Color::Rgb(r, g, b)
}
Self::Purple => {
let r = (80.0 + l * 175.0) as u8;
let g = (l * l * 100.0) as u8;
let b = (100.0 + l * 155.0) as u8;
Color::Rgb(r, g, b)
}
Self::Grayscale => {
let v = (l * 255.0) as u8;
Color::Rgb(v, v, v)
}
Self::Rainbow => {
let h = l * 300.0; let s = 0.8;
let v = 0.5 + l * 0.5;
let (r, g, b) = hsv_to_rgb(h, s, v);
Color::Rgb(r, g, b)
}
}
}
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
let c = v * s;
let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
let m = v - c;
let (r, g, b) = if h < 60.0 {
(c, x, 0.0)
} else if h < 120.0 {
(x, c, 0.0)
} else if h < 180.0 {
(0.0, c, x)
} else if h < 240.0 {
(0.0, x, c)
} else if h < 300.0 {
(x, 0.0, c)
} else {
(c, 0.0, x)
};
(((r + m) * 255.0) as u8, ((g + m) * 255.0) as u8, ((b + m) * 255.0) as u8)
}
#[derive(Clone, Copy)]
pub struct Fragment {
pub luminance: f32,
}
pub struct AsciiBuffer {
pub width: u16,
pub height: u16,
buffer: Vec<Option<Fragment>>,
z_buffer: Vec<f32>,
}
impl AsciiBuffer {
pub fn new(width: u16, height: u16) -> Self {
let size = (width as usize) * (height as usize);
Self {
width,
height,
buffer: vec![None; size],
z_buffer: vec![f32::NEG_INFINITY; size],
}
}
pub fn clear(&mut self) {
self.buffer.fill(None);
self.z_buffer.fill(f32::NEG_INFINITY);
}
pub fn plot(&mut self, x: u16, y: u16, depth: f32, luminance: f32) {
if x >= self.width || y >= self.height {
return;
}
let idx = (y as usize) * (self.width as usize) + (x as usize);
if depth > self.z_buffer[idx] {
self.z_buffer[idx] = depth;
self.buffer[idx] = Some(Fragment { luminance });
}
}
pub fn get(&self, x: u16, y: u16) -> Option<&Fragment> {
if x >= self.width || y >= self.height {
return None;
}
let idx = (y as usize) * (self.width as usize) + (x as usize);
self.buffer[idx].as_ref()
}
pub fn resize(&mut self, width: u16, height: u16) {
if self.width != width || self.height != height {
self.width = width;
self.height = height;
let size = (width as usize) * (height as usize);
self.buffer = vec![None; size];
self.z_buffer = vec![f32::NEG_INFINITY; size];
}
}
}
pub struct Renderer {
pub camera: Camera,
pub light_dir: Vec3,
}
impl Default for Renderer {
fn default() -> Self {
Self {
camera: Camera::default(),
light_dir: Vec3::new(0.0, 1.0, -1.0).normalize(),
}
}
}
impl Renderer {
pub fn render_point(
&self,
buffer: &mut AsciiBuffer,
position: Vec3,
normal: Vec3,
) {
if let Some((sx, sy, depth)) = self.camera.project(position, buffer.width, buffer.height) {
let luminance = normal.dot(self.light_dir).max(0.0);
buffer.plot(sx, sy, depth, luminance);
}
}
}