use std::{f32::consts::PI, path::{Path, PathBuf}}; use teleia::*; use rand::Rng; pub const PLANE_SIZE: f32 = 12.0; pub const FOOTSTEP_TIME: u64 = 90; #[derive(Debug)] pub struct Config { pub name: String, pub credits: String, pub links: String, pub size: f32, pub speed: f32, pub fov: f32, pub texture_repeat: f32, pub room_height: f32, pub layer_frames: f32, pub fog_color: glam::Vec3, pub fog_end: f32, pub mini_speed: f32, } impl Default for Config { fn default() -> Self { Self { name: "".to_owned(), credits: "".to_owned(), links: "".to_owned(), size: 1.0, speed: 1.0, fov: 55.0, texture_repeat: 3.0, room_height: 350.0, layer_frames: 1.0, fog_color: glam::Vec3::ZERO, fog_end: -1.0, mini_speed: 1.0, } } } impl Config { pub fn from_room_ini(bs: Vec) -> Erm { let mut ret = Self::default(); let contents = String::from_utf8(bs)?; let i = ini::Ini::load_from_str(&contents)?; let Some(sec) = i.section::<&str>(None) else { return utils::erm_msg("failed to get properties"); }; if let Some(x) = sec.get("name") { ret.name = x.to_owned(); } if let Some(x) = sec.get("credits") { ret.credits = x.to_owned(); } if let Some(x) = sec.get("links") { ret.links = x.to_owned(); } if let Some(x) = sec.get("size") { ret.size = x.parse()?; } if let Some(x) = sec.get("speed") { ret.speed = x.parse()?; } if let Some(x) = sec.get("FOV") { ret.fov = x.parse()?; } if let Some(x) = sec.get("Texture_Repeat") { ret.texture_repeat = x.parse()?; } if let Some(x) = sec.get("Room_Height") { ret.room_height = x.parse::()? / 350.0; } if let Some(x) = sec.get("Layer_Frames") { ret.layer_frames = x.parse()?; } if let Some(x) = sec.get("Fog_Color") { let num: u64 = x.parse()?; ret.fog_color = glam::Vec3::new( (num & 0xff) as f32 / 255.0, (num >> 8 & 0xff) as f32 / 255.0, (num >> 16 & 0xff) as f32 / 255.0, ); } if let Some(x) = sec.get("Fog_End") { ret.fog_end = x.parse::()? / 10.0; } if let Some(x) = sec.get("Mini_speed") { ret.mini_speed = x.parse::()? / 60.0; } Ok(ret) } } pub struct Room { pub config: Config, pub shader: shader::Shader, pub floor: texture::Texture, pub wall: texture::Texture, pub roof: texture::Texture, pub art: texture::Texture, pub credit: texture::Texture, pub mini: texture::Texture, pub has_mini: bool, pub mini_frame: u64, pub mini_pos: glam::Vec2, pub mini_dir: glam::Vec2, pub interact: texture::Texture, pub has_interact: bool, pub layer: texture::Texture, pub has_layer: bool, pub layer_frame: u64, pub interact_mode: ui::Mode, pub credit_mode: ui::Mode, pub music: Option<(audio::Audio, audio::AudioPlayingHandle)>, pub foot: Option, pub foot_countdown: u64, } impl Room { pub fn new(ctx: &context::Context, shader: shader::Shader) -> Self { Self { config: Config::default(), shader, floor: texture::Texture::new_empty(ctx), wall: texture::Texture::new_empty(ctx), roof: texture::Texture::new_empty(ctx), art: texture::Texture::new_empty(ctx), credit: texture::Texture::new_empty(ctx), mini: texture::Texture::new_empty(ctx), has_mini: false, mini_frame: 0, mini_pos: glam::Vec2::new(4.0, 4.0), mini_dir: glam::Vec2::new(1.0, 0.0), interact: texture::Texture::new_empty(ctx), has_interact: false, layer: texture::Texture::new_empty(ctx), has_layer: false, layer_frame: 0, interact_mode: ui::Mode::new(20), credit_mode: ui::Mode::new(10), music: None, foot: None, foot_countdown: 0, } } fn load_file(base: impl AsRef, needle: &str) -> Erm> { let base = base.as_ref(); let uneedle = needle.to_uppercase(); for mp in std::fs::read_dir(base)? { if let Ok(p) = mp && let Some(onm) = p.path().file_name() && let Some(nm) = onm.to_str() { if nm.to_uppercase().contains(&uneedle) { return Ok(std::fs::read(p.path())?); } } } utils::erm_msg(&format!("failed to find {} in {}", needle, base.display())) } pub fn load_from_bytes( &mut self, ctx: &context::Context, st: &mut state::State, config: Option<&[u8]>, floor: &[u8], wall: &[u8], roof: &[u8], art: &[u8], credit: &[u8], mini: Option<&[u8]>, interact: Option<&[u8]>, layer: Option<&[u8]>, music: Option<&[u8]>, foot: Option<&[u8]>, ) -> Erm<()> { self.config = Config::default(); if let Some(bs) = config { self.config = Config::from_room_ini(bs.to_owned())?; } self.floor.upload(ctx, floor); self.floor.set_repeat(ctx); self.wall.upload(ctx, wall); self.wall.set_repeat(ctx); self.roof.upload(ctx, roof); self.roof.set_repeat(ctx); self.art.upload(ctx, art); self.credit.upload(ctx, credit); if let Some(bs) = mini { self.mini.upload(ctx, &bs); self.has_mini = true; } else { self.has_mini = false; } if let Some(bs) = interact { self.interact.upload(ctx, &bs); self.has_interact = true; } else { self.has_interact = false; } if let Some(bs) = layer { self.layer.upload(ctx, &bs); self.has_layer = true; } else { self.has_layer = false; } if let Some(bs) = music && let Some(audio) = st.audio.as_mut() { let m = audio::Audio::new(&audio.ctx, &bs); if let Some(handle) = m.play(&mut audio.ctx, Some((None, None))) { self.music = Some((m, handle)) } } else { self.music = None; } if let Some(bs) = foot && let Some(audio) = st.audio.as_mut() { let m = audio::Audio::new(&audio.ctx, &bs); self.foot = Some(m); } else { self.foot = None; } Ok(()) } pub fn load(&mut self, ctx: &context::Context, st: &mut state::State, dir: &str) -> Erm<()> { let base = PathBuf::from(dir); self.load_from_bytes(ctx, st, Self::load_file(&base, "ROOM").ok().as_deref(), &Self::load_file(&base, "FLOOR")?, &Self::load_file(&base, "WALL")?, &Self::load_file(&base, "ROOF")?, &Self::load_file(&base, "ART")?, &Self::load_file(&base, "CREDIT")?, Self::load_file(&base, "MINI").ok().as_deref(), Self::load_file(&base, "INTERACT").ok().as_deref(), Self::load_file(&base, "LAYER").ok().as_deref(), Self::load_file(&base, "MUSIC").ok().as_deref(), Self::load_file(&base, "FOOT").ok().as_deref(), )?; Ok(()) } pub fn footstep(&mut self, _ctx: &context::Context, st: &mut state::State) -> Erm<()> { if let Some(f) = &self.foot && let Some(audio) = st.audio.as_mut() { if self.foot_countdown == 0 { f.play(&mut audio.ctx, None); self.foot_countdown = FOOTSTEP_TIME; } else { self.foot_countdown -= 1; } } Ok(()) } pub fn render(&mut self, ctx: &context::Context, st: &mut state::State, yaw: f32) -> Erm<()> { let scale = st.render_dims.x / 640.0; st.projection = glam::Mat4::perspective_lh( self.config.fov * PI / 180.0, st.render_dims.x / st.render_dims.y, 0.5, 50.0, ); st.bind_3d(ctx, &self.shader); self.shader.set_vec2(ctx, "texture_scale", &glam::Vec2::new(self.config.texture_repeat, self.config.texture_repeat) ); self.shader.set_f32(ctx, "fog_distance", self.config.fog_end); self.shader.set_vec3(ctx, "fog_color", &self.config.fog_color); self.floor.bind(ctx); self.shader.set_position_3d( ctx, st, &glam::Mat4::from_scale_rotation_translation( glam::Vec3::new(PLANE_SIZE, PLANE_SIZE, 1.0), glam::Quat::from_rotation_x(PI / 2.0), glam::Vec3::new(0.0, 0.0, 0.0), ), ); st.mesh_square.render(ctx); self.roof.bind(ctx); self.shader.set_position_3d( ctx, st, &glam::Mat4::from_scale_rotation_translation( glam::Vec3::new(PLANE_SIZE, PLANE_SIZE, 1.0), glam::Quat::from_rotation_x(3.0 * PI / 2.0), glam::Vec3::new(0.0, self.config.room_height * PLANE_SIZE * 2.0, 0.0), ), ); st.mesh_square.render(ctx); self.wall.bind(ctx); let wallpos = [ glam::Mat4::from_scale_rotation_translation( glam::Vec3::new(PLANE_SIZE, PLANE_SIZE, 1.0), glam::Quat::from_rotation_y(0.0), glam::Vec3::new(0.0, PLANE_SIZE, PLANE_SIZE), ), glam::Mat4::from_scale_rotation_translation( glam::Vec3::new(PLANE_SIZE, PLANE_SIZE, 1.0), glam::Quat::from_rotation_y(PI), glam::Vec3::new(0.0, PLANE_SIZE, -PLANE_SIZE), ), glam::Mat4::from_scale_rotation_translation( glam::Vec3::new(PLANE_SIZE, PLANE_SIZE, 1.0), glam::Quat::from_rotation_y(PI / 2.0), glam::Vec3::new(PLANE_SIZE, PLANE_SIZE, 0.0), ), glam::Mat4::from_scale_rotation_translation( glam::Vec3::new(PLANE_SIZE, PLANE_SIZE, 1.0), glam::Quat::from_rotation_y(3.0 * PI / 2.0), glam::Vec3::new(-PLANE_SIZE, PLANE_SIZE, 0.0), ), ]; for wp in wallpos { self.shader.set_position_3d(ctx, st, &wp); st.mesh_square.render(ctx); } self.shader.set_vec2(ctx, "texture_scale", &glam::Vec2::new(1.0, 1.0)); self.art.bind(ctx); self.shader.set_position_3d( ctx, st, &glam::Mat4::from_scale_rotation_translation( glam::Vec3::new(1.0, 1.0, 1.0), glam::Quat::from_rotation_y(yaw), glam::Vec3::new(0.0, 1.0, 0.0), ), ); st.mesh_square.render(ctx); if self.has_mini { self.mini.bind(ctx); let xscale = 1.0 / 4.0; let xoffset = xscale * self.mini_frame as f32; if st.tick % 10 == 0 { self.mini_frame += 1; self.mini_frame %= 4; } self.shader.set_vec2(ctx, "texture_scale", &glam::Vec2::new(xscale, 1.0)); self.shader.set_vec2(ctx, "texture_offset", &glam::Vec2::new(xoffset, 0.0)); self.shader.set_position_3d( ctx, st, &glam::Mat4::from_scale_rotation_translation( glam::Vec3::new(0.25, 0.25, 1.0), glam::Quat::from_rotation_y(yaw), glam::Vec3::new(self.mini_pos.x, 0.25, self.mini_pos.y), ), ); st.mesh_square.render(ctx); self.shader.set_vec2(ctx, "texture_scale", &glam::Vec2::new(1.0, 1.0)); self.shader.set_vec2(ctx, "texture_offset", &glam::Vec2::new(0.0, 0.0)); let newpos = self.mini_pos + self.config.mini_speed * self.mini_dir; if newpos.x.abs() > PLANE_SIZE || newpos.y.abs() > PLANE_SIZE { let angle = rand::thread_rng().gen_range(0..360) as f32 * PI / 180.0; self.mini_dir = glam::Vec2::new(angle.cos(), angle.sin()); } else { self.mini_pos = newpos; } } ctx.clear_depth(); self.shader.set_f32(ctx, "fog_distance", -1.0); if self.has_layer { st.bind_2d(ctx, &self.shader); let xscale = 1.0 / self.config.layer_frames; let xoffset = xscale * self.layer_frame as f32; if st.tick % 10 == 0 { self.layer_frame += 1; self.layer_frame %= self.config.layer_frames as u64; } self.shader.set_vec2(ctx, "texture_scale", &glam::Vec2::new(xscale, 1.0)); self.shader.set_vec2(ctx, "texture_offset", &glam::Vec2::new(xoffset, 0.0)); self.layer.bind(ctx); self.shader.set_position_2d( ctx, st, &glam::Vec2::new(0.0, 0.0), &st.render_dims, ); st.mesh_square.render(ctx); self.shader.set_vec2(ctx, "texture_scale", &glam::Vec2::new(1.0, 1.0)); self.shader.set_vec2(ctx, "texture_offset", &glam::Vec2::new(0.0, 0.0)); } if self.has_interact { st.bind_2d(ctx, &self.shader); self.shader.set_f32(ctx, "opacity", self.interact_mode.progress(st.tick)); self.interact.bind(ctx); let iwidth = self.interact.width as f32 * scale; let iheight = iwidth * (self.interact.height as f32 / self.interact.width as f32); self.shader.set_position_2d( ctx, st, &glam::Vec2::new(0.0, 0.0), &glam::Vec2::new(iwidth, iheight), ); st.mesh_square.render(ctx); self.shader.set_f32(ctx, "opacity", 1.0); } st.bind_2d(ctx, &self.shader); self.credit.bind(ctx); let cwidth = self.credit.width as f32 * scale; let cheight = cwidth * (self.credit.height as f32 / self.credit.width as f32); self.shader.set_position_2d( ctx, st, &glam::Vec2::new(st.render_dims.x - cwidth, utils::lerp( st.render_dims.y, st.render_dims.y - cheight, self.credit_mode.progress(st.tick), ), ), &glam::Vec2::new(cwidth, cheight), ); st.mesh_square.render(ctx); Ok(()) } }