use teleia::*; use std::{collections::HashMap, f32::consts::PI}; use lexpr::sexp; use base64::prelude::*; use glow::HasContext; use crate::{assets, fig, toggle, input, background}; use super::{drawing, automata}; pub struct Chat { author: String, msg: String, time: f64, biblicality: f32, } impl Chat { pub fn new() -> Self { Self { author: format!(""), msg: format!(""), time: 0.0, biblicality: 0.0, } } } pub struct Overlay { assets: assets::Assets, model: scene::Scene, model_neck_base: glam::Mat4, fig: fig::SexpClient, fig_binary: fig::BinaryClient, tracking_eyes: (f32, f32), tracking_mouth: f32, tracking_neck: glam::Quat, throwshade: newton_throwshade::ThrowShade, emacs_cursor: (f32, f32), mouse_cursor: (f32, f32), emacs_heartrate: i32, muzak_author: Option, chat: Chat, toggles: toggle::Toggles, input: input::Input, backgrounds: background::Backgrounds, drawing: drawing::Drawing, automata: automata::Board, } impl Overlay { pub fn new(ctx: &context::Context) -> Self { let model = scene::Scene::from_gltf(ctx, include_bytes!("../assets/scenes/lcolonq.vrm")); let model_neck_base = model.nodes_by_name.get("J_Bip_C_Neck") .and_then(|i| model.nodes.get(*i)) .expect("failed to find neck joint") .transform; let throwshade = newton_throwshade::ThrowShade::new(); let mut automata = automata::Board::new(ctx); automata.test_glider(); Self { assets: assets::Assets::new(ctx), model, model_neck_base, fig: fig::SexpClient::new("shiro:32050", &[ sexp!((avatar toggle)), sexp!((avatar toggle set)), sexp!((avatar toggle unset)), sexp!((avatar text)), sexp!((avatar frame)), sexp!((avatar reset)), sexp!((avatar tracking)), sexp!((avatar overlay shader)), sexp!((avatar overlay muzak)), sexp!((avatar overlay muzak clear)), sexp!((avatar overlay chat)), sexp!((avatar overlay cursor)), sexp!((avatar overlay emacs)), ]), fig_binary: fig::BinaryClient::new("shiro:32051", false, &[ b"background frame" ]), tracking_eyes: (1.0, 1.0), tracking_mouth: 0.0, tracking_neck: glam::Quat::IDENTITY, emacs_cursor: (0.0, 0.0), mouse_cursor: (0.0, 0.0), emacs_heartrate: 0, throwshade, muzak_author: None, chat: Chat::new(), toggles: toggle::Toggles::new(), backgrounds: background::Backgrounds::new(ctx), input: input::Input::new(), drawing: drawing::Drawing::new(ctx), automata, } } pub fn handle_reset(&mut self, ctx: &context::Context) { // TODO also reset terminal if let Some(s) = &mut self.throwshade.shader { s.delete(ctx); } self.throwshade.shader = None; self.toggles.reset(); } pub fn handle_tracking(&mut self, msg: fig::SexpMessage) -> Option<()> { let eyes = msg.data.get(0)?; let eye_left = eyes.get(0)?.as_str()?.parse::().ok()?; let eye_right = eyes.get(1)?.as_str()?.parse::().ok()?; let mouth = msg.data.get(1)?.as_str()?.parse::().ok()?; let euler = msg.data.get(2)?; let euler_x = euler.get(0)?.as_str()?.parse::().ok()?.to_radians(); let euler_y = PI - euler.get(1)?.as_str()?.parse::().ok()?.to_radians(); let euler_z = euler.get(2)?.as_str()?.parse::().ok()?.to_radians() + PI/2.0; self.tracking_eyes = (eye_left, eye_right); self.tracking_mouth = mouth; self.tracking_neck = glam::Quat::from_euler(glam::EulerRot::XYZ, euler_x, euler_y, euler_z); Some(()) } pub fn handle_overlay_shader( &mut self, ctx: &context::Context, st: &state::State, msg: fig::SexpMessage ) -> Option<()> { let ba = BASE64_STANDARD.decode(msg.data.get(0)?.as_str()?).ok()?; let author = String::from_utf8_lossy(&ba); let bs = BASE64_STANDARD.decode(msg.data.get(1)?.as_str()?).ok()?; let s = String::from_utf8_lossy(&bs); self.throwshade.author = author.to_string(); if let Err(e) = self.throwshade.set(ctx, st, &s) { log::warn!("error compiling shader: {}", e); self.throwshade.shader = None; } Some(()) } pub fn handle_overlay_muzak(&mut self, msg: fig::SexpMessage) -> Option<()> { let ba = BASE64_STANDARD.decode(msg.data.get(0)?.as_str()?).ok()?; let author = String::from_utf8_lossy(&ba); self.muzak_author = Some(author.to_string()); Some(()) } pub fn handle_overlay_muzak_clear(&mut self) -> Option<()> { self.muzak_author = None; Some(()) } pub fn handle_overlay_chat(&mut self, msg: fig::SexpMessage) -> Option<()> { let ba = BASE64_STANDARD.decode(msg.data.get(0)?.as_str()?).ok()?; let a = String::from_utf8_lossy(&ba); let bs = BASE64_STANDARD.decode(msg.data.get(1)?.as_str()?).ok()?; let s = String::from_utf8_lossy(&bs); let time = msg.data.get(2)?.as_str()?.parse::().ok()?; let biblicality = msg.data.get(3)?.as_str()?.parse::().ok()?; self.chat.author = a.to_string(); self.chat.msg = s.to_string(); self.chat.time = time; self.chat.biblicality = biblicality; Some(()) } pub fn handle_overlay_cursor(&mut self, msg: fig::SexpMessage) -> Option<()> { let cursor_x = msg.data.get(0)?.as_i64()? as f32; let cursor_y = msg.data.get(1)?.as_i64()? as f32; self.emacs_cursor = (cursor_x, cursor_y); Some(()) } pub fn handle_overlay_emacs(&mut self, msg: fig::SexpMessage) -> Option<()> { self.emacs_heartrate = msg.data.get(0)?.as_i64()? as i32; Some(()) } } impl teleia::state::Game for Overlay { fn initialize_audio(&self, ctx: &context::Context, st: &state::State, actx: &audio::Context) -> HashMap { HashMap::new() } fn finish_title(&mut self, _st: &mut state::State) {} fn mouse_press(&mut self, _ctx: &context::Context, _st: &mut state::State) {} fn mouse_move(&mut self, _ctx: &context::Context, _st: &mut state::State, _x: i32, _y: i32) {} fn update(&mut self, ctx: &context::Context, st: &mut state::State) -> Erm<()> { st.move_camera( ctx, &glam::Vec3::new(0.0, 0.0, 1.0), &glam::Vec3::new(0.0, 0.0, -1.0), &glam::Vec3::new(0.0, 1.0, 0.0), ); 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.handle_reset(ctx); } 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 shader)) { if self.handle_overlay_shader(ctx, st, msg).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); } self.drawing.update(ctx, st, &mut self.input); self.automata.update(ctx, st); Ok(()) } fn render(&mut self, ctx: &context::Context, st: &mut state::State) -> Erm<()> { ctx.clear_color(glam::Vec4::new(0.0, 0.0, 0.0, 0.0)); ctx.clear(); if let Some(s) = &self.throwshade.shader { s.bind(ctx); s.set_f32( ctx, "opacity", if let Some(t@toggle::Toggle { val: true, .. }) = self.toggles.get(ctx, st, "shaderclarity") { ((st.tick - t.set_time) as f32 / 60.0).clamp(0.0, 1.0) * 0.5 + 0.5 } else if let Some(t@toggle::Toggle { val: false, .. }) = self.toggles.get(ctx, st, "shaderclarity") { (1.0 - ((st.tick - t.set_time) as f32 / 60.0).clamp(0.0, 1.0)) * 0.5 + 0.5 } else { 0.5 } ); s.set_vec2(ctx, "resolution", &glam::Vec2::new(ctx.render_width, ctx.render_height)); let elapsed = (st.tick - self.throwshade.tickset) as f32 / 60.0; s.set_f32(ctx, "time", elapsed); s.set_f32(ctx, "chat_time", (self.chat.time - self.throwshade.timeset) as f32); ctx.render_no_geometry(); s.set_f32(ctx, "tracking_mouth", self.tracking_mouth); // log::info!("eyes: {:?}", self.tracking_eyes); s.set_vec2(ctx, "tracking_eyes", &glam::Vec2::new(self.tracking_eyes.0, self.tracking_eyes.1)); s.set_mat4(ctx, "tracking_neck", &glam::Mat4::from_quat(self.tracking_neck)); s.set_vec2(ctx, "emacs_cursor", &glam::Vec2::new(self.emacs_cursor.0, self.emacs_cursor.1)); s.set_vec2(ctx, "mouse_cursor", &glam::Vec2::new(self.mouse_cursor.0, self.mouse_cursor.1)); s.set_i32(ctx, "heartrate", self.emacs_heartrate); } if let Some(t@toggle::Toggle { val: true, .. }) = self.toggles.get(ctx, st, "adblock") { st.bind_2d(ctx, &self.assets.shader_flat); self.assets.texture_adblock.bind(ctx); let tr = 1.0 - ((st.tick - t.set_time) as f32 / 60.0).clamp(0.0, 1.0); self.assets.shader_flat.set_f32(ctx, "transparency", tr); self.assets.shader_flat.set_position_2d( ctx, &glam::Vec2::new(1100.0, 300.0), &glam::Vec2::new(800.0, 600.0) ); st.mesh_square.render(ctx); } let mut authors = Vec::new(); if let Some(_) = &self.throwshade.shader { authors.push(format!("shader by {}", self.throwshade.author)); } if let Some(a) = &self.muzak_author { authors.push(format!("music by {}", a)); } let astr: String = authors.join(", "); self.assets.font.render_text(ctx, &glam::Vec2::new(0.0, 0.0), &astr); self.drawing.render(ctx, st, &self.backgrounds); self.automata.render(ctx, st); Ok(()) } }