diff options
| author | LLLL Colonq <llll@colonq> | 2025-08-14 22:28:39 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2025-08-14 22:28:39 -0400 |
| commit | e4ded2c09e6c378040f80e80886aa9c087fe14b4 (patch) | |
| tree | 74984adf49dde6a1fe7b3b22c0d9c3e2168df267 /crates/renderer/src/overlay/automata.rs | |
| parent | 4fb92d6fa3ce2d93c2ce720429f46aa104972674 (diff) | |
Automata rendering
Diffstat (limited to 'crates/renderer/src/overlay/automata.rs')
| -rw-r--r-- | crates/renderer/src/overlay/automata.rs | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/crates/renderer/src/overlay/automata.rs b/crates/renderer/src/overlay/automata.rs new file mode 100644 index 0000000..869fd88 --- /dev/null +++ b/crates/renderer/src/overlay/automata.rs @@ -0,0 +1,242 @@ +use teleia::*; + +use glow::HasContext; + +const SCALE: usize = 15; +const WIDTH: usize = 1920 / SCALE; +const HEIGHT: usize = 1080 / SCALE; + +struct Pattern { + w: usize, h: usize, + cells: Vec<bool>, +} +impl Pattern { + pub fn from_rle(inp: &str) -> Option<Self> { + let s = inp.replace(";", "\n"); + let mut data = String::new(); + let mut w = 0; + let mut h = 0; + for line in s.split("\n") { + if let Some('#') = line.trim().chars().nth(0) { + } else if let Some('x') = line.trim().chars().nth(0) { + for assign in line.trim().split(",") { + if let Some((svar, sval)) = assign.split_once(" = ") { + if svar.trim() == "x" { + w = sval.trim().parse().ok()?; + } else if svar.trim() == "y" { + h = sval.trim().parse().ok()?; + } + } + } + } else { + data.push_str(&line); + } + } + let mut ret = Self { + w, h, + cells: vec![false; w * h], + }; + log::info!("dims: {w} {h} data: {data}"); + ret.populate(&data); + Some(ret) + } + pub fn idx(&self, x: i32, y: i32) -> usize{ + let ux = x.rem_euclid(self.w as i32) as usize; + let uy = y.rem_euclid(self.h as i32) as usize; + uy * self.w + ux + } + pub fn get(&self, x: i32, y: i32) -> bool { + let idx = self.idx(x, y); + self.cells[idx] + } + pub fn set(&mut self, x: i32, y: i32) { + let idx = self.idx(x, y); + self.cells[idx] = true; + log::info!("pattern: {x} {y}"); + } + pub fn populate(&mut self, s: &str) { + let mut run = 0; + let mut x = 0; + let mut y = 0; + for c in s.chars() { + if let Some(d) = c.to_digit(10) { + run = run * 10 + d; + } else { + if run == 0 { run = 1 } + if c == '$' && x == 0 { run -= 1 } + while run > 0 { + match c { + 'b' => { + x = (x + 1) % (self.w as i32); + if x == 0 { y += 1; } + }, + 'o' => { + self.set(x, y); + x = (x + 1) % (self.w as i32); + if x == 0 { y += 1; } + }, + '$' => { + x = 0; + y += 1; + }, + _ => {}, + } + run -= 1; + } + } + } + } +} + +type Cell = u8; + +struct CellRule { + color: glam::Vec4, +} + +struct CellBuffer { + buf: [Cell; WIDTH * HEIGHT], +} +impl CellBuffer { + pub fn new() -> Self { + Self { + buf: [0; WIDTH * HEIGHT], + } + } + fn idx(x: i32, y: i32) -> usize { + let ux = x.rem_euclid(WIDTH as i32) as usize; + let uy = y.rem_euclid(HEIGHT as i32) as usize; + uy * WIDTH + ux + } + pub fn get(&self, x: i32, y: i32) -> Cell { + self.buf[Self::idx(x, y)] + } + pub fn is_nonzero(&self, x: i32, y: i32) -> bool { + self.get(x, y) > 0 + } + pub fn count_cell(&self, x: i32, y: i32) -> i32 { + self.get(x, y).min(1) as i32 + } + pub fn count_neighbors(&self, x: i32, y: i32) -> i32 { + self.count_cell(x-1, y-1) + self.count_cell(x, y-1) + self.count_cell(x+1, y-1) + + self.count_cell(x-1, y) + self.count_cell(x+1, y) + + self.count_cell(x-1, y+1) + self.count_cell(x, y+1) + self.count_cell(x+1, y+1) + } + pub fn set(&mut self, x: i32, y: i32, v: Cell) { + self.buf[Self::idx(x, y)] = v; + } +} + +pub struct Board { + shader: shader::Shader, + tex: texture::Texture, + active: bool, + buf0: CellBuffer, + buf1: CellBuffer, + rules: [CellRule; 256], +} +impl Board { + pub fn new(ctx: &context::Context) -> Self { + let rules = std::array::from_fn(|idx| match idx { + 0 => CellRule { color: glam::Vec4::new(0.0, 0.0, 0.0, 0.0) }, + _ => CellRule { color: glam::Vec4::new(1.0, 1.0, 1.0, 1.0) }, + }); + Self { + shader: shader::Shader::new( + ctx, + include_str!("../assets/shaders/automata/vert.glsl"), + include_str!("../assets/shaders/automata/frag.glsl"), + ), + tex: texture::Texture::new_empty(ctx), + active: false, + buf0: CellBuffer::new(), + buf1: CellBuffer::new(), + rules, + } + } + pub fn spawn(&mut self, x: i32, y: i32, c: Cell, pat: &Pattern) { + let cur = if self.active { &mut self.buf0 } else { &mut self.buf1 }; + for uxoff in 0..pat.w { + for uyoff in 0..pat.h { + let xoff = uxoff as i32; let yoff = uyoff as i32; + cur.set(x + xoff, y + yoff, if pat.get(xoff, yoff) { c } else { 0 }); + } + } + } + pub fn test_glider(&mut self) { + // let cur = if self.active { &mut self.buf0 } else { &mut self.buf1 }; + // cur.set(1, 0, 1); + // cur.set(2, 1, 1); + // cur.set(0, 2, 1); + // cur.set(1, 2, 1); + // cur.set(2, 2, 1); + if let Some(pat) = Pattern::from_rle(" +#N Tanner's p46 +#O Tanner Jacobi +#C A period 46 oscillator discovered by Tanner Jacobi in October 2017. +#C https://conwaylife.com/wiki/Tanner%27s_p46 +x = 13, y = 26, rule = B3/S23 +2b2o9b$2bo10b$3bo9b$2b2o9b$13b$9b2o2b$9bo3b$10bo2b$9b2o2b$b2o10b$b2o6b +2o2b$o7bobo2b$b2o6bo3b$b2o7b3o$12bo$13b$13b$13b$13b$13b$13b$b2o10b$b2o +2b2o6b$5bobo5b$7bo5b$7b2o4b! +") { + self.spawn(30, 10, 1, &pat); + } + } + pub fn step(&mut self) { + let (cur, next) = if self.active { + (&mut self.buf0, &mut self.buf1) + } else { + (&mut self.buf1, &mut self.buf0) + }; + for ux in 0..WIDTH { + for uy in 0..HEIGHT { + let x = ux as _; let y = uy as _; + let n = cur.count_neighbors(x, y); + if cur.is_nonzero(x, y) && n != 2 && n != 3{ + next.set(x, y, 0) + } else if n == 3 { + next.set(x, y, 1) + } else { + next.set(x, y, cur.get(x, y)) + } + } + } + self.active = !self.active; + } + pub fn upload(&self, ctx: &context::Context) { + let cur = if self.active { &self.buf0 } else { &self.buf1 }; + unsafe { + let err = ctx.gl.get_error(); + self.tex.bind(ctx); + ctx.gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::R8 as i32, + WIDTH as i32, + HEIGHT as i32, + 0, + glow::RED, + glow::UNSIGNED_BYTE, + Some(&cur.buf), + ); + ctx.gl.generate_mipmap(glow::TEXTURE_2D); + } + } + pub fn update(&mut self, ctx: &context::Context, st: &mut state::State) { + if st.tick % 10 == 0 { + self.step(); + self.upload(ctx); + } + } + pub fn render(&self, ctx: &context::Context, st: &mut state::State) { + st.bind_2d(ctx, &self.shader); + self.tex.bind(ctx); + self.shader.set_position_2d( + ctx, + &glam::Vec2::new(0.0, 0.0), + &glam::Vec2::new(1920.0, 1080.0) + ); + st.mesh_square.render(ctx); + } +} |
