summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2025-08-22 14:24:21 -0400
committerLLLL Colonq <llll@colonq>2025-08-22 14:24:21 -0400
commit4db7ffbaafda249ecafa720e52479d5c7b132edb (patch)
tree7ef5c7b60e181f7f442b5d0bde5d73d23e76f5cf
parenta196b0cd8669d50ca26f8865dcccb6f556b98cd9 (diff)
Update automata
-rw-r--r--Cargo.lock3
-rw-r--r--crates/renderer/src/assets/shaders/automata/frag.glsl5
-rw-r--r--crates/renderer/src/main.rs1
-rw-r--r--crates/renderer/src/overlay.rs80
-rw-r--r--crates/renderer/src/overlay/automata.rs109
5 files changed, 128 insertions, 70 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4f81706..0a85621 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2984,11 +2984,10 @@ dependencies = [
[[package]]
name = "teleia"
version = "0.1.0"
-source = "git+https://github.com/lcolonq/teleia#1da3eb369306ab5b88f580716201cf1fa4979936"
dependencies = [
"bimap",
"bincode",
- "bitflags 2.8.0",
+ "bitflags 1.3.2",
"byteorder",
"bytes",
"color-eyre",
diff --git a/crates/renderer/src/assets/shaders/automata/frag.glsl b/crates/renderer/src/assets/shaders/automata/frag.glsl
index 7eda644..29f5c71 100644
--- a/crates/renderer/src/assets/shaders/automata/frag.glsl
+++ b/crates/renderer/src/assets/shaders/automata/frag.glsl
@@ -5,8 +5,5 @@ void main()
{
vec2 tcfull = vec2(vertex_texcoord.x, 1.0 - vertex_texcoord.y);
vec4 texel = texture(texture_data, tcfull);
- if (texel.r == 0.0) {
- discard;
- }
- frag_color = vec4(1.0, 1.0, 1.0, 1.0);
+ frag_color = texel;
}
diff --git a/crates/renderer/src/main.rs b/crates/renderer/src/main.rs
index fa35852..854eae2 100644
--- a/crates/renderer/src/main.rs
+++ b/crates/renderer/src/main.rs
@@ -30,6 +30,7 @@ pub fn main() -> Erm<()> {
Box::new(overlay::automata::Overlay::new(ctx)),
Box::new(overlay::shader::Overlay::new(ctx)),
Box::new(overlay::drawing::Overlay::new(ctx)),
+ // Box::new(overlay::model::Overlay::new(ctx)),
])
})?;
},
diff --git a/crates/renderer/src/overlay.rs b/crates/renderer/src/overlay.rs
index fd59f82..accf3bb 100644
--- a/crates/renderer/src/overlay.rs
+++ b/crates/renderer/src/overlay.rs
@@ -70,6 +70,7 @@ impl State {
sexp!((avatar overlay chat)),
sexp!((avatar overlay cursor)),
sexp!((avatar overlay emacs)),
+ sexp!((avatar automata spawn)),
]),
fig_binary: fig::BinaryClient::new("shiro:32051", false, &[
b"background frame"
@@ -146,44 +147,6 @@ impl State {
);
let (x, y) = self.input.get_mouse();
self.mouse_cursor = (x as f32, y as f32);
- while let Some(msg) = self.fig.pump() {
- let malformed = format!("malformed {} data: {}", msg.event, msg.data);
- if msg.event == sexp!((avatar tracking)) {
- if self.handle_tracking(msg).is_none() { log::warn!("{}", malformed) }
- } else if msg.event == sexp!((avatar reset)) {
- self.reset(ctx, st);
- } else if msg.event == sexp!((avatar toggle)) {
- if self.toggles.handle(ctx, st, msg).is_none() { log::warn!("{}", malformed) }
- } else if msg.event == sexp!((avatar toggle set)) {
- if self.toggles.handle_set(ctx, st, msg, true).is_none() { log::warn!("{}", malformed) }
- } else if msg.event == sexp!((avatar toggle unset)) {
- if self.toggles.handle_set(ctx, st, msg, false).is_none() { log::warn!("{}", malformed) }
- } else if msg.event == sexp!((avatar overlay muzak)) {
- if self.handle_overlay_muzak(msg).is_none() { log::warn!("{}", malformed) }
- } else if msg.event == sexp!((avatar overlay muzak clear)) {
- if self.handle_overlay_muzak_clear().is_none() { log::warn!("{}", malformed) }
- } else if msg.event == sexp!((avatar overlay chat)) {
- if self.handle_overlay_chat(msg).is_none() { log::warn!("{}", malformed) }
- } else if msg.event == sexp!((avatar overlay cursor)) {
- if self.handle_overlay_cursor(msg).is_none() { log::warn!("{}", malformed) }
- } else {
- log::info!("received unhandled event {} with data: {}", msg.event, msg.data);
- }
- }
- while let Some(msg) = self.fig_binary.pump() {
- match &*msg.event {
- b"background frame" => {
- if let Some(f) = background::Frame::parse(&mut &*msg.data) {
- self.backgrounds.update(ctx, f);
- } else {
- log::warn!("failed to parse frame");
- }
- },
- ev => {
- log::info!("unhandled event: {:?}", ev);
- },
- }
- }
if let Some(n) = self.model.nodes_by_name.get("J_Bip_C_Neck").and_then(|i| self.model.nodes.get_mut(*i)) {
n.transform = self.model_neck_base * glam::Mat4::from_quat(self.tracking_neck);
}
@@ -233,7 +196,46 @@ impl teleia::state::Game for Overlays {
&glam::Vec3::new(0.0, 0.0, -1.0),
&glam::Vec3::new(0.0, 1.0, 0.0),
);
- self.state.reset(ctx, st);
+ while let Some(msg) = self.state.fig.pump() {
+ let malformed = format!("malformed {} data: {}", msg.event, msg.data);
+ for ov in self.overlays.iter_mut() {
+ ov.handle(ctx, st, &mut self.state, msg.clone())?;
+ }
+ if msg.event == sexp!((avatar tracking)) {
+ if self.state.handle_tracking(msg).is_none() { log::warn!("{}", malformed) }
+ } else if msg.event == sexp!((avatar reset)) {
+ self.reset(ctx, st)?;
+ } else if msg.event == sexp!((avatar toggle)) {
+ if self.state.toggles.handle(ctx, st, msg).is_none() { log::warn!("{}", malformed) }
+ } else if msg.event == sexp!((avatar toggle set)) {
+ if self.state.toggles.handle_set(ctx, st, msg, true).is_none() { log::warn!("{}", malformed) }
+ } else if msg.event == sexp!((avatar toggle unset)) {
+ if self.state.toggles.handle_set(ctx, st, msg, false).is_none() { log::warn!("{}", malformed) }
+ } else if msg.event == sexp!((avatar overlay muzak)) {
+ if self.state.handle_overlay_muzak(msg).is_none() { log::warn!("{}", malformed) }
+ } else if msg.event == sexp!((avatar overlay muzak clear)) {
+ if self.state.handle_overlay_muzak_clear().is_none() { log::warn!("{}", malformed) }
+ } else if msg.event == sexp!((avatar overlay chat)) {
+ if self.state.handle_overlay_chat(msg).is_none() { log::warn!("{}", malformed) }
+ } else if msg.event == sexp!((avatar overlay cursor)) {
+ if self.state.handle_overlay_cursor(msg).is_none() { log::warn!("{}", malformed) }
+ }
+ }
+ while let Some(msg) = self.state.fig_binary.pump() {
+ match &*msg.event {
+ b"background frame" => {
+ if let Some(f) = background::Frame::parse(&mut &*msg.data) {
+ self.state.backgrounds.update(ctx, f);
+ } else {
+ log::warn!("failed to parse frame");
+ }
+ },
+ ev => {
+ log::info!("unhandled event: {:?}", ev);
+ },
+ }
+ }
+ self.state.update(ctx, st)?;
for ov in self.overlays.iter_mut() {
ov.update(ctx, st, &mut self.state)?;
}
diff --git a/crates/renderer/src/overlay/automata.rs b/crates/renderer/src/overlay/automata.rs
index b5d749e..22f7884 100644
--- a/crates/renderer/src/overlay/automata.rs
+++ b/crates/renderer/src/overlay/automata.rs
@@ -1,6 +1,9 @@
use teleia::*;
use glow::HasContext;
+use lexpr::sexp;
+use base64::prelude::*;
+use rand::Rng;
use crate::overlay;
@@ -34,6 +37,7 @@ impl Pattern {
data.push_str(&line);
}
}
+ if w == 0 || h == 0 || w > WIDTH || h > HEIGHT { return None }
let mut ret = Self {
w, h,
cells: vec![false; w * h],
@@ -91,7 +95,7 @@ impl Pattern {
type Cell = u8;
struct CellRule {
- color: glam::Vec4,
+ color: [u8; 4],
}
struct CellBuffer {
@@ -111,6 +115,17 @@ impl CellBuffer {
pub fn get(&self, x: i32, y: i32) -> Cell {
self.buf[Self::idx(x, y)]
}
+ pub fn neighbors(&self, x: i32, y: i32) -> [Cell; 8] {
+ [ self.get(x-1, y-1),
+ self.get(x, y-1),
+ self.get(x+1, y-1),
+ self.get(x-1, y),
+ self.get(x+1, y),
+ self.get(x-1, y+1),
+ self.get(x, y+1),
+ self.get(x+1, y+1),
+ ]
+ }
pub fn is_nonzero(&self, x: i32, y: i32) -> bool {
self.get(x, y) > 0
}
@@ -118,9 +133,22 @@ impl CellBuffer {
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)
+ self.neighbors(x, y).into_iter().map(|c| c.min(1) as i32).sum()
+ }
+ pub fn most_common_neighbor(&self, x: i32, y: i32) -> u8 {
+ let mut ns = self.neighbors(x, y);
+ ns.sort_unstable();
+ let mut winner = 0;
+ let mut score = 0;
+ let mut cur = 0;
+ let mut curscore = 0;
+ for c in ns {
+ if c == 0 { continue; }
+ if c != cur { cur = c; curscore = 1; }
+ else { curscore += 1; }
+ if curscore >= score { winner = c; score = curscore; }
+ }
+ winner
}
pub fn set(&mut self, x: i32, y: i32, v: Cell) {
self.buf[Self::idx(x, y)] = v;
@@ -133,15 +161,16 @@ pub struct Overlay {
active: bool,
buf0: CellBuffer,
buf1: CellBuffer,
+ next_rule: usize,
rules: [CellRule; 256],
}
impl Overlay {
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) },
+ 0 => CellRule { color: [0, 0, 0, 0] },
+ _ => CellRule { color: [0xff, 0xff, 0xff, 0xff] },
});
- let mut ret = Self {
+ Self {
shader: shader::Shader::new(
ctx,
include_str!("../assets/shaders/automata/vert.glsl"),
@@ -151,21 +180,9 @@ impl Overlay {
active: false,
buf0: CellBuffer::new(),
buf1: CellBuffer::new(),
+ next_rule: 1,
rules,
- };
- 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!
-") {
- ret.spawn(30, 10, 1, &pat);
}
- ret
}
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 };
@@ -189,7 +206,7 @@ x = 13, y = 26, rule = B3/S23
if cur.is_nonzero(x, y) && n != 2 && n != 3{
next.set(x, y, 0)
} else if n == 3 {
- next.set(x, y, 1)
+ next.set(x, y, cur.most_common_neighbor(x, y))
} else {
next.set(x, y, cur.get(x, y))
}
@@ -199,25 +216,67 @@ x = 13, y = 26, rule = B3/S23
}
pub fn upload(&self, ctx: &context::Context) {
let cur = if self.active { &self.buf0 } else { &self.buf1 };
+ let mut buf = vec![0; WIDTH * HEIGHT * 4];
+ for (idx, c) in cur.buf.iter().enumerate() {
+ for off in 0..4 { buf[idx * 4 + off] = self.rules[*c as usize].color[off] }
+ }
unsafe {
- let err = ctx.gl.get_error();
self.tex.bind(ctx);
ctx.gl.tex_image_2d(
glow::TEXTURE_2D,
0,
- glow::R8 as i32,
+ glow::RGBA as i32,
WIDTH as i32,
HEIGHT as i32,
0,
- glow::RED,
+ glow::RGBA,
glow::UNSIGNED_BYTE,
- Some(&cur.buf),
+ Some(&buf),
);
ctx.gl.generate_mipmap(glow::TEXTURE_2D);
}
}
+ pub fn handle_spawn(&mut self, msg: fig::SexpMessage) -> Option<()> {
+ let bs = BASE64_STANDARD.decode(msg.data.get(0)?.as_str()?).ok()?;
+ let s = std::str::from_utf8(&bs).ok()?;
+ let bcol = BASE64_STANDARD.decode(msg.data.get(2)?.as_str()?).ok()?;
+ let scol = std::str::from_utf8(&bcol).ok()?.trim().strip_prefix("#")?;
+ let col = u32::from_str_radix(scol, 16).ok()?;
+ let r = (col >> 16 & 0xff) as u8;
+ let g = (col >> 8 & 0xff) as u8;
+ let b = (col & 0xff) as u8;
+ if let Some(pat) = Pattern::from_rle(s) {
+ let mut rng = rand::thread_rng();
+ let x = rng.gen_range(0..WIDTH);
+ let y = rng.gen_range(0..HEIGHT);
+ self.rules[self.next_rule] = CellRule { color: [r, g, b, 0xff] };
+ self.spawn(x as i32, y as i32, self.next_rule as u8, &pat);
+ self.next_rule = (self.next_rule + 1) % 256;
+ if self.next_rule == 0 { self.next_rule = 1; }
+ }
+ Some(())
+ }
}
impl overlay::Overlay for Overlay {
+ fn reset(&mut self, ctx: &context::Context, st: &mut state::State, _ost: &mut overlay::State) -> Erm<()> {
+ let cur = if self.active { &mut self.buf0 } else { &mut self.buf1 };
+ for ux in 0..WIDTH {
+ for uy in 0..HEIGHT {
+ cur.set(ux as i32, uy as i32, 0)
+ }
+ }
+ Ok(())
+ }
+ fn handle(
+ &mut self, ctx: &context::Context, st: &mut state::State, _ost: &mut overlay::State,
+ msg: fig::SexpMessage,
+ ) -> Erm<()> {
+ let malformed = format!("malformed {} data: {}", msg.event, msg.data);
+ if msg.event == sexp!((avatar automata spawn)) {
+ if self.handle_spawn(msg).is_none() { log::warn!("{}", malformed) }
+ }
+ Ok(())
+ }
fn update(&mut self, ctx: &context::Context, st: &mut state::State, _ost: &mut overlay::State) -> Erm<()> {
if st.tick % 10 == 0 {
self.step();