diff --git a/assets_gui/Cargo.toml b/assets_gui/Cargo.toml index 8835e6cf..5a53bb6c 100644 --- a/assets_gui/Cargo.toml +++ b/assets_gui/Cargo.toml @@ -4,8 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -common = { path = "../common" } -geom = { path = "../geom" } -engine = { path = "../engine" } -log = { version = "0.4.11", features=["max_level_info", "release_max_level_info"] } +common = { path = "../common" } +geom = { path = "../geom" } +engine = { path = "../engine" } +log = { version = "0.4.11", features=["max_level_info", "release_max_level_info"] } inline_tweak = "1.0.8" +egui_dock = "0.6.3" +egui = { workspace = true } diff --git a/assets_gui/src/companies.rs b/assets_gui/src/companies.rs new file mode 100644 index 00000000..ef18e014 --- /dev/null +++ b/assets_gui/src/companies.rs @@ -0,0 +1,23 @@ +use common::descriptions::GoodsCompanyDescriptionJSON; +use common::saveload::Encoder; +use std::io; + +pub struct Companies { + pub companies: Vec, + pub changed: bool, +} + +impl Companies { + pub fn new() -> io::Result { + let cjson = common::saveload::load_raw("assets/companies.json")?; + let companies = common::saveload::JSONPretty::decode(&cjson)?; + Ok(Self { + companies, + changed: false, + }) + } + + pub fn save(&self) { + common::saveload::JSONPretty::save(&self.companies, "companies"); + } +} diff --git a/assets_gui/src/gui.rs b/assets_gui/src/gui.rs new file mode 100644 index 00000000..2f4a23df --- /dev/null +++ b/assets_gui/src/gui.rs @@ -0,0 +1,185 @@ +use egui::{Color32, Ui}; +use egui_dock::{DockArea, NodeIndex, Style, TabStyle, Tree}; + +use engine::meshload::MeshProperties; +use engine::wgpu::{BindGroup, RenderPass}; +use engine::{Drawable, GfxContext, Mesh, SpriteBatch}; +use geom::Matrix4; + +use crate::companies::Companies; +use crate::State; + +#[derive(Copy, Clone, Debug)] +pub enum Tab { + View, + Explorer, + Properties, + ModelProperties, +} + +#[derive(PartialEq, Eq, Copy, Clone)] +pub enum Inspected { + None, + Company(usize), +} + +#[derive(Clone)] +pub enum Shown { + None, + Error(String), + Model((Mesh, MeshProperties)), + Sprite(SpriteBatch), +} + +pub struct Gui { + pub tree: Option>, + pub companies: Companies, + pub inspected: Inspected, + pub shown: Shown, +} + +impl Gui { + pub fn new() -> Self { + let mut tree = Tree::new(vec![Tab::View]); + + let view = NodeIndex::root(); + let [view, _] = tree.split_left(view, 0.2, vec![Tab::Explorer]); + let [view, _] = tree.split_right(view, 0.8, vec![Tab::Properties]); + tree.split_below(view, 0.8, vec![Tab::ModelProperties]); + + Self { + tree: Some(tree), + companies: Companies::new().expect("could not load companies.json"), + inspected: Inspected::None, + shown: Shown::None, + } + } +} + +impl State { + pub fn gui(&mut self, ui: &egui::Context) { + let mut tree = self.gui.tree.take().unwrap(); + DockArea::new(&mut tree) + .show_close_buttons(false) + .draggable_tabs(false) + .style(Style::from_egui(ui.style().as_ref())) + .show(ui, &mut TabViewer { state: self }); + self.gui.tree = Some(tree); + } +} + +fn explorer(state: &mut State, ui: &mut Ui) { + if state.gui.companies.changed { + if ui.button("Save").clicked() { + state.gui.companies.save(); + } + } + for (i, comp) in state.gui.companies.companies.iter().enumerate() { + let r = ui.add_sized([ui.available_width(), 40.0], egui::Button::new(&comp.name)); + if r.clicked() { + state.gui.inspected = Inspected::Company(i); + } + } +} + +fn properties(state: &mut State, ui: &mut Ui) { + match state.gui.inspected { + Inspected::None => {} + Inspected::Company(i) => { + let comp = &mut state.gui.companies.companies[i]; + ui.label(&comp.name); + } + } +} + +fn model_properties(state: &mut State, ui: &mut Ui) { + match &state.gui.shown { + Shown::None => {} + Shown::Error(e) => { + ui.label(e); + } + Shown::Model((_, props)) => { + ui.columns(2, |ui| { + ui[0].label("Vertices"); + ui[1].label(format!("{}", props.n_vertices)); + + ui[0].label("Triangles"); + ui[1].label(format!("{}", props.n_triangles)); + + ui[0].label("Materials"); + ui[1].label(format!("{}", props.n_materials)); + + ui[0].label("Textures"); + ui[1].label(format!("{}", props.n_textures)); + + ui[0].label("Draw calls"); + ui[1].label(format!("{}", props.n_draw_calls)); + }); + } + Shown::Sprite(_sprite) => { + ui.label("Sprite"); + } + } +} + +struct TabViewer<'a> { + state: &'a mut State, +} + +impl<'a> egui_dock::TabViewer for TabViewer<'a> { + type Tab = Tab; + + fn ui(&mut self, ui: &mut Ui, tab: &mut Self::Tab) { + match tab { + Tab::View => return, + Tab::Explorer => explorer(self.state, ui), + Tab::Properties => properties(self.state, ui), + Tab::ModelProperties => model_properties(self.state, ui), + } + } + + fn title(&mut self, tab: &mut Tab) -> egui::WidgetText { + match tab { + Tab::Explorer => "Explorer".into(), + Tab::Properties => "Properties".into(), + Tab::ModelProperties => "Model Properties".into(), + Tab::View => "View".into(), + } + } + + #[inline] + fn tab_style_override(&self, tab: &Self::Tab, global_style: &TabStyle) -> Option { + if matches!(tab, Tab::View) { + return Some(TabStyle { + bg_fill: Color32::TRANSPARENT, + hline_below_active_tab_name: false, + ..global_style.clone() + }); + } + None + } +} + +impl Drawable for Shown { + fn draw<'a>(&'a self, gfx: &'a GfxContext, rp: &mut RenderPass<'a>) { + match self { + Shown::None | Shown::Error(_) => {} + Shown::Model((mesh, _)) => mesh.draw(gfx, rp), + Shown::Sprite(sprite) => sprite.draw(gfx, rp), + } + } + + fn draw_depth<'a>( + &'a self, + gfx: &'a GfxContext, + rp: &mut RenderPass<'a>, + shadow_cascade: Option<&Matrix4>, + proj: &'a BindGroup, + ) { + match self { + Shown::None | Shown::Error(_) => {} + Shown::Model((mesh, _)) => mesh.draw_depth(gfx, rp, shadow_cascade, proj), + Shown::Sprite(sprite) => sprite.draw_depth(gfx, rp, shadow_cascade, proj), + } + } +} diff --git a/assets_gui/src/main.rs b/assets_gui/src/main.rs index be0c702b..7046b469 100644 --- a/assets_gui/src/main.rs +++ b/assets_gui/src/main.rs @@ -1,10 +1,23 @@ -use engine::{Context, FrameContext, GfxContext, KeyCode, MouseButton}; -use geom::{vec3, Camera, InfiniteFrustrum, LinearColor, Matrix4, Plane, Radians, Vec2, Vec3}; +use std::path::Path; -struct State { - is_captured: bool, +use egui::FontFamily::{Monospace, Proportional}; +use egui::FontId; + +use engine::meshload::load_mesh_with_properties; +use engine::{Context, FrameContext, GfxContext, SpriteBatchBuilder}; +use geom::{vec3, InfiniteFrustrum, LinearColor, Plane, Vec2, Vec3}; + +use crate::gui::{Gui, Inspected, Shown}; +use crate::orbit_camera::OrbitCamera; - camera: Camera, +mod companies; +mod gui; +mod orbit_camera; + +struct State { + gui: Gui, + camera: OrbitCamera, + last_inspect: Inspected, } impl engine::framework::State for State { @@ -15,115 +28,49 @@ impl engine::framework::State for State { gfx.sun_shadowmap = GfxContext::mk_shadowmap(&gfx.device, 2048); gfx.update_simplelit_bg(); - let mut camera = Camera::new(vec3(9.0, -30.0, 13.0), 1000.0, 1000.0); - camera.dist = 0.0; - camera.pitch = Radians(0.0); - camera.yaw = Radians(-std::f32::consts::PI / 2.0); + let mut style = (*ctx.egui.egui.style()).clone(); + + style.text_styles = [ + (egui::TextStyle::Small, FontId::new(15.0, Proportional)), + (egui::TextStyle::Body, FontId::new(18.5, Proportional)), + (egui::TextStyle::Button, FontId::new(18.5, Proportional)), + (egui::TextStyle::Heading, FontId::new(25.0, Proportional)), + (egui::TextStyle::Monospace, FontId::new(18.0, Monospace)), + ] + .into(); + + ctx.egui.egui.set_style(style); + + let camera = OrbitCamera::new(); Self { camera, - is_captured: false, + gui: Gui::new(), + last_inspect: Inspected::None, } } fn update(&mut self, ctx: &mut Context) { - if ctx.input.mouse.pressed.contains(&MouseButton::Left) { - let _ = ctx.window.set_cursor_grab(engine::CursorGrabMode::Confined); - ctx.window.set_cursor_visible(false); - self.is_captured = true; - } - - if ctx.input.cursor_left { - let _ = ctx.window.set_cursor_grab(engine::CursorGrabMode::None); - ctx.window.set_cursor_visible(true); - self.is_captured = false; - } - - if ctx.input.keyboard.pressed.contains(&KeyCode::Escape) { - let _ = ctx.window.set_cursor_grab(engine::CursorGrabMode::None); - ctx.window.set_cursor_visible(true); - self.is_captured = false; - } - - let delta = ctx.delta; - let cam_speed = if ctx.input.keyboard.pressed_scancode.contains(&42) { - 3.0 - } else { - 30.0 - } * delta; - - if ctx.input.keyboard.pressed_scancode.contains(&17) { - self.camera.pos -= self - .camera - .dir() - .xy() - .z0() - .try_normalize() - .unwrap_or(Vec3::ZERO) - * cam_speed; - } - if ctx.input.keyboard.pressed_scancode.contains(&31) { - self.camera.pos += self - .camera - .dir() - .xy() - .z0() - .try_normalize() - .unwrap_or(Vec3::ZERO) - * cam_speed; - } - if ctx.input.keyboard.pressed_scancode.contains(&30) { - self.camera.pos += self - .camera - .dir() - .perp_up() - .try_normalize() - .unwrap_or(Vec3::ZERO) - * cam_speed; - } - if ctx.input.keyboard.pressed_scancode.contains(&32) { - self.camera.pos -= self - .camera - .dir() - .perp_up() - .try_normalize() - .unwrap_or(Vec3::ZERO) - * cam_speed; - } - if ctx.input.keyboard.pressed_scancode.contains(&57) { - self.camera.pos += vec3(0.0, 0.0, 1.0) * cam_speed; - } - if ctx.input.keyboard.pressed_scancode.contains(&29) { - self.camera.pos -= vec3(0.0, 0.0, 1.0) * cam_speed; - } - - if self.is_captured { - let delta = ctx.input.mouse.screen_delta; + self.camera.camera_movement(ctx); - self.camera.yaw.0 -= 0.001 * delta.x; - self.camera.pitch.0 += 0.001 * delta.y; - self.camera.pitch.0 = self.camera.pitch.0.clamp(-1.5, 1.5); + if self.gui.inspected != self.last_inspect { + self.last_inspect = self.gui.inspected; + self.gui.shown = create_shown(&mut ctx.gfx, self, self.gui.inspected); } - let sun = vec3(1.0, -1.0, 1.0).normalize(); - let gfx = &mut ctx.gfx; - let viewproj = self.camera.build_view_projection_matrix(); - let inv_viewproj = viewproj.invert().unwrap_or_else(Matrix4::zero); - gfx.set_proj(viewproj); - gfx.set_inv_proj(inv_viewproj); - let params = gfx.render_params.value_mut(); - params.time_always = (params.time_always + delta) % 3600.0; + + let sun = vec3(1.0, -1.0, 1.0).normalize(); + params.time_always = (params.time_always + ctx.delta) % 3600.0; params.sun_col = sun.z.max(0.0).sqrt().sqrt() * LinearColor::new(1.0, 0.95 + sun.z * 0.05, 0.95 + sun.z * 0.05, 1.0); - params.cam_pos = self.camera.eye(); - params.cam_dir = self.camera.dir(); params.sun = sun; params.viewport = Vec2::new(gfx.size.0 as f32, gfx.size.1 as f32); - self.camera.dist = 300.0; + params.shadow_mapping_resolution = 2048; params.sun_shadow_proj = self + .camera .camera .build_sun_shadowmap_matrix( sun, @@ -132,14 +79,62 @@ impl engine::framework::State for State { ) .try_into() .unwrap(); - self.camera.dist = 0.0; - params.shadow_mapping_resolution = 2048; } - fn render(&mut self, _fc: &mut FrameContext) {} + fn render(&mut self, fc: &mut FrameContext) { + fc.draw(self.gui.shown.clone()); + } + + fn resized(&mut self, ctx: &mut Context, size: (u32, u32)) { + self.camera.resize(ctx, size.0 as f32, size.1 as f32); + } - fn resized(&mut self, _ctx: &mut Context, size: (u32, u32)) { - self.camera.set_viewport(size.0 as f32, size.1 as f32); + fn render_gui(&mut self, ui: &egui::Context) { + self.gui(ui); + } +} + +fn create_shown(gfx: &mut GfxContext, state: &State, inspected: Inspected) -> Shown { + match inspected { + Inspected::None => Shown::None, + Inspected::Company(i) => { + let comp = &state.gui.companies.companies[i]; + let p = Path::new(&comp.asset_location); + match p.extension() { + Some(x) if (x == "png" || x == "jpg") => { + let tex = match gfx.try_texture(p, "sprite texture") { + Ok(x) => x, + Err(e) => { + return Shown::Error(format!( + "could not load texture {}: {}", + comp.asset_location, e + )) + } + }; + let mut sb: SpriteBatchBuilder = SpriteBatchBuilder::new(tex, gfx); + sb.push(Vec3::ZERO, Vec3::X, LinearColor::WHITE, (100.0, 100.0)); + Shown::Sprite(sb.build(gfx).unwrap()) + } + Some(x) if x == "glb" => { + let model = match load_mesh_with_properties(gfx, &comp.asset_location) { + Ok(x) => x, + Err(e) => { + return Shown::Error(format!( + "could not load model {}:\n{:?}", + comp.asset_location, e + )) + } + }; + + Shown::Model(model) + } + Some(_) => Shown::Error(format!( + "unknown asset type for path: {}", + comp.asset_location + )), + None => Shown::Error(format!("no extension for path: {}", comp.asset_location)), + } + } } } diff --git a/assets_gui/src/orbit_camera.rs b/assets_gui/src/orbit_camera.rs new file mode 100644 index 00000000..7eee59b9 --- /dev/null +++ b/assets_gui/src/orbit_camera.rs @@ -0,0 +1,120 @@ +use engine::{Context, MouseButton}; +use geom::{Camera, Matrix4, Radians, Vec2, Vec3}; + +pub struct OrbitCamera { + pub camera: Camera, + pub targetpos: Vec3, + pub targetyaw: Radians, + pub targetpitch: Radians, + pub targetdist: f32, +} + +impl OrbitCamera { + pub fn new() -> Self { + Self { + camera: Camera::new(Vec3::ZERO, 1920.0, 1080.0), + targetpos: Default::default(), + targetyaw: Default::default(), + targetpitch: Default::default(), + targetdist: 100.0, + } + } + + pub fn update(&mut self, ctx: &mut Context) { + let viewproj = self.camera.build_view_projection_matrix(); + let inv_viewproj = viewproj.invert().unwrap_or_else(Matrix4::zero); + + ctx.gfx.set_proj(viewproj); + ctx.gfx.set_inv_proj(inv_viewproj); + let params = ctx.gfx.render_params.value_mut(); + params.cam_pos = self.camera.eye(); + params.cam_dir = -self.camera.dir(); + } + + pub fn resize(&mut self, ctx: &mut Context, width: f32, height: f32) { + self.camera.set_viewport(width, height); + self.update(ctx); + } + + pub fn camera_movement(&mut self, ctx: &mut Context) { + if !self.camera.pos.is_finite() { + self.camera.pos = Vec3::ZERO; + } + if !self.camera.dist.is_finite() { + self.camera.dist = 1000.0; + } + if !self.camera.yaw.0.is_finite() { + self.camera.yaw.0 = 0.3; + } + if !self.camera.pitch.0.is_finite() { + self.camera.pitch.0 = 0.3; + } + + let delta = ctx.delta.min(0.1); + let off = self.camera.offset(); + let d = off.xy().try_normalize().unwrap_or(Vec2::ZERO) * self.camera.dist; + + // D + if ctx.input.keyboard.pressed_scancode.contains(&32) { + self.targetpos += -delta * d.perpendicular().z0(); + } + // A + if ctx.input.keyboard.pressed_scancode.contains(&30) { + self.targetpos += delta * d.perpendicular().z0(); + } + + // W + if ctx.input.keyboard.pressed_scancode.contains(&17) { + self.targetpos += -delta * d.z0(); + } + // S + if ctx.input.keyboard.pressed_scancode.contains(&31) { + self.targetpos += delta * d.z0(); + } + + if ctx.input.mouse.wheel_delta > 0.0 { + self.targetdist *= (1.0f32 / 1.05).powf(0.5 + 0.1 * ctx.input.mouse.wheel_delta.abs()); + } + + if ctx.input.mouse.wheel_delta < 0.0 { + self.targetdist *= 1.05f32.powf(0.5 + 0.1 * ctx.input.mouse.wheel_delta.abs()); + } + self.targetdist = self.targetdist.clamp(5.0, 100000.0); + + let delta_mouse = ctx.input.mouse.screen_delta; + + if ctx.input.mouse.pressed.contains(&MouseButton::Right) { + self.targetyaw -= Radians(delta_mouse.x / 100.0); + self.targetpitch += Radians(delta_mouse.y / 100.0); + self.targetpitch = self + .targetpitch + .min(Radians::HALFPI - Radians(0.01)) + .max(-Radians::HALFPI + Radians(0.01)); + } + + macro_rules! lerpp { + ($a:expr, $b:expr, $amt:expr, $c:expr) => { + let coeff = delta * 1.0 * $amt; + let diff = $b - $a; + if coeff.abs() > 1.0 || $c(diff) < 0.002 { + $a = $b; + } else { + $a += diff * coeff; + } + }; + } + + lerpp!(self.camera.pos, self.targetpos, 8.0, |v: Vec3| v.mag2()); + lerpp!(self.camera.yaw, self.targetyaw, 16.0, |x: Radians| x + .0 + .abs()); + lerpp!(self.camera.pitch, self.targetpitch, 8.0, |x: Radians| x + .0 + .abs()); + lerpp!(self.camera.dist, self.targetdist, 8.0, |x: f32| x.abs()); + + self.camera.fovy = 60.0; + + self.update(ctx); + } +}