summaryrefslogtreecommitdiff
path: root/src/room.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/room.rs')
-rw-r--r--src/room.rs346
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(())
+ }
+}