diff options
| author | LLLL Colonq <llll@colonq> | 2026-05-02 16:16:04 -0400 |
|---|---|---|
| committer | LLLL Colonq <llll@colonq> | 2026-05-02 16:16:04 -0400 |
| commit | d3bd791297d6aaa7abaf613aafbec99a7bb17823 (patch) | |
| tree | ace9b887fc57681dcbe8a468278cdc74ecff1b2c /src/room.rs | |
| parent | ea5929c74a4e01bcbc776832b5597fceb5261a31 (diff) | |
Eating soup!
Diffstat (limited to 'src/room.rs')
| -rw-r--r-- | src/room.rs | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/src/room.rs b/src/room.rs new file mode 100644 index 0000000..81ed88b --- /dev/null +++ b/src/room.rs @@ -0,0 +1,346 @@ +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<u8>) -> Erm<Self> { + 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::<f32>()? / 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::<f32>()? / 10.0; } + if let Some(x) = sec.get("Mini_speed") { ret.mini_speed = x.parse::<f32>()? / 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<audio::Audio>, 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<Path>, needle: &str) -> Erm<Vec<u8>> { + 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(()) + } +} |
