diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index c9794f3d..00000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Fix bevy dynamic linker build on Windows -# https://github.com/bevyengine/bevy/issues/1126 -[target.x86_64-pc-windows-msvc] -linker = "rust-lld.exe" -rustflags = ["-Zshare-generics=off", "-Zmacro-backtrace=on"] \ No newline at end of file diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 73feecc0..599be86d 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -34,11 +34,6 @@ jobs: sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev - uses: actions/checkout@v2 - - name: Remove cargo and toolchain configurations - shell: bash - run: | - rm -rf .cargo - rm rust-toolchain.toml - uses: actions/cache@v3 with: path: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef07e066..e7a3afc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,11 +20,6 @@ jobs: ~/.cargo/ target/ key: ${{ runner.os }}-cargo-check-stable-${{ hashFiles('**/Cargo.toml', './Cargo.lock') }} - - name: Remove cargo and toolchain configurations - shell: bash - run: | - rm -rf .cargo - rm rust-toolchain.toml - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -45,11 +40,6 @@ jobs: sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev - uses: actions/checkout@v2 - - name: Remove cargo and toolchain configurations - shell: bash - run: | - rm -rf .cargo - rm rust-toolchain.toml - uses: actions/cache@v3 with: path: | @@ -77,11 +67,6 @@ jobs: sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev - uses: actions/checkout@v2 - - name: Remove cargo and toolchain configurations - shell: bash - run: | - rm -rf .cargo - rm rust-toolchain.toml - uses: actions/cache@v3 with: path: | @@ -132,11 +117,6 @@ jobs: sudo apt-get install pkg-config libx11-dev libasound2-dev libudev-dev sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libspeechd-dev - uses: actions/checkout@v2 - - name: Remove cargo and toolchain configurations - shell: bash - run: | - rm -rf .cargo - rm rust-toolchain.toml - uses: actions/cache@v3 with: path: | @@ -165,7 +145,6 @@ jobs: cp -f $exe $OUTPUT_PATH/$(basename $exe) done - uses: actions/upload-artifact@v3 - if: ${{ false }} # Skip for now with: name: ${{ env.ZIP_NAME }} path: ${{ env.OUTPUT_PATH }}/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index acf5bb4a..63bbef6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env +.rust-analyzer .vscode /target Cargo.lock \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 3f7b7157..77a3482b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,16 @@ { "rust-analyzer.cargo.features": [ "audio", - //"bevy/dynamic" - "midi", - "model", "experimental", "midi", + "model", "python" ], "rust-analyzer.inlayHints.chainingHints.enable": false, "rust-analyzer.inlayHints.typeHints.enable": false, "rust-analyzer.inlayHints.closingBraceHints.enable": false, "rust-analyzer.inlayHints.parameterHints.enable": false, + "rust-analyzer.checkOnSave.extraArgs": [ + "--target-dir", ".rust-analyzer" + ] } \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 2d29387a..e10d0843 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [workspace] members = [ "apps/cli/*", - "apps/ui/*", + #"apps/ui/*", "core/*", "utils/*" ] default-members = [ "apps/cli/*", - "apps/ui/*", + #"apps/ui/*", "core/*" ] resolver = "2" # Super duper important for bevy! @@ -18,15 +18,17 @@ authors = ["PikminGuts92"] edition = "2021" [workspace.dependencies] -clap = { version = "4.3.12", features = ["derive"] } +clap = { version = "4.4.18", features = ["derive"] } +gltf = { version = "=1.3.0", default-features = false, features = [ "import", "names", "utils" ] } +gltf-json = { version = "=1.3.0", features = [ "names" ] } grim = { path = "core/grim" } -itertools = "0.11.0" +itertools = "0.12.0" lazy_static = "1.4.0" -log = "0.4.19" -serde = { version = "1.0.171", features = ["derive"] } -serde_json = "1.0.103" +log = "0.4.20" +serde = { version = "1.0.195", features = ["derive"] } +serde_json = "1.0.111" simplelog = "0.12.1" -thiserror = "1.0.43" +thiserror = "1.0.56" [profile.dev.package."*"] opt-level = 3 diff --git a/apps/cli/p9_scene_tool/Cargo.toml b/apps/cli/p9_scene_tool/Cargo.toml index 666a634b..15c9639b 100644 --- a/apps/cli/p9_scene_tool/Cargo.toml +++ b/apps/cli/p9_scene_tool/Cargo.toml @@ -7,7 +7,7 @@ edition.workspace = true [dependencies] build-time = "0.1.3" clap = { workspace = true } -const_format = "0.2.31" +const_format = "0.2.32" grim = { workspace = true, features = [ "midi" ] } log = { workspace = true } serde = { workspace = true } @@ -17,4 +17,4 @@ thiserror = { workspace = true } [dev-dependencies] criterion = "0.5.1" -rstest = "0.18.1" +rstest = "0.18.2" diff --git a/apps/ui/preview_ui/Cargo.toml b/apps/ui/preview_ui/Cargo.toml deleted file mode 100644 index f0916c4f..00000000 --- a/apps/ui/preview_ui/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "preview_ui" -version.workspace = true -authors.workspace = true -edition.workspace = true - -[dependencies] -bevy = { version = "0.11.0", default-features = false, features = [ "bevy_core_pipeline", "bevy_pbr", "bevy_render", "bevy_winit", "x11" ] } -bevy_egui = { version = "0.21.0", features = [ "immutable_ctx" ] } -#bevy_fly_camera = "0.10.0" -bevy_fly_camera = { git = "https://github.com/zachbateman/bevy_fly_camera.git", branch = "bevy_0.11.0" } -bevy_infinite_grid = "0.8.0" -egui_extras = { version = "0.22.0", features = [ "svg" ] } -font-awesome-as-a-crate = "0.3.0" -grim = { workspace = true } -itertools = { workspace = true } -lazy_static = { workspace = true } -log = { workspace = true } -# native-dialog = "0.6.1" -serde = { workspace = true } -serde_json = { workspace = true } -thiserror = { workspace = true } - -[target.'cfg(target_family = "wasm")'.dependencies] -console_error_panic_hook = "0.1.7" diff --git a/apps/ui/preview_ui/src/events.rs b/apps/ui/preview_ui/src/events.rs deleted file mode 100644 index f31a2958..00000000 --- a/apps/ui/preview_ui/src/events.rs +++ /dev/null @@ -1,15 +0,0 @@ -use bevy::prelude::*; -use std::path::PathBuf; - -#[derive(Event)] -pub enum AppEvent { - Exit, - SelectMiloEntry(Option), - ToggleGridLines(bool), - ToggleWireframes(bool), -} - -#[derive(Event)] -pub enum AppFileEvent { - Open(PathBuf), -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/ark.rs b/apps/ui/preview_ui/src/gui/ark.rs deleted file mode 100644 index ef0cbfbf..00000000 --- a/apps/ui/preview_ui/src/gui/ark.rs +++ /dev/null @@ -1,40 +0,0 @@ -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use grim::ark::{Ark, ArkOffsetEntry}; -use super::{AppSettings, AppState, ArkDirNode, AppEvent}; - -pub fn draw_ark_tree(state: &mut AppState, ctx: &mut &Context, ui: &mut Ui) { - if let Some(root) = &state.root { - let entries = &state.ark.as_ref().unwrap().entries; - - draw_node(root, entries, ctx, ui); - } -} - -fn draw_node(node: &ArkDirNode, entries: &Vec, ctx: &mut &Context, ui: &mut Ui) { - egui::CollapsingHeader::new(&node.name) - .id_source(format!("dir_{}", &node.path)) - .default_open(false) - .show(ui, |ui| { - for child in &node.dirs { - draw_node(child, entries, ctx, ui); - } - - egui::Grid::new(format!("files_{}", &node.path)).striped(true).show(ui, |ui| { - for file_idx in &node.files { - let ark_entry = &entries[*file_idx]; - let file_name = get_file_name(&ark_entry.path); - - #[allow(unused_must_use)] { - ui.selectable_label(false, file_name); - } - ui.end_row(); - - //ui.small_button(file_name); - } - }); - }); -} - -pub fn get_file_name(path: &str) -> &str { - path.split('/').last().unwrap_or(path) -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/icons.rs b/apps/ui/preview_ui/src/gui/icons.rs deleted file mode 100644 index 6810bf0d..00000000 --- a/apps/ui/preview_ui/src/gui/icons.rs +++ /dev/null @@ -1,56 +0,0 @@ -use egui_extras::image::RetainedImage; -use font_awesome_as_a_crate as fa; -use lazy_static::lazy_static; - -lazy_static! { - pub static ref FA_GRID: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_grid", - fa::svg(fa::Type::Solid, "table-cells").unwrap() - ).unwrap(); - - pub static ref FA_CUBES: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_cubes", - fa::svg(fa::Type::Solid, "cubes").unwrap() - ).unwrap(); - - pub static ref FA_REFRESH: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_refresh", - fa::svg(fa::Type::Solid, "arrows-rotate").unwrap() - ).unwrap(); - - pub static ref FA_ARROWS_MULTI: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_arrows_multi", - fa::svg(fa::Type::Solid, "arrows-up-down-left-right").unwrap() - ).unwrap(); - - pub static ref FA_EYE: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_eye", - fa::svg(fa::Type::Solid, "eye").unwrap() - ).unwrap(); - - pub static ref FA_TRASH: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_trash", - fa::svg(fa::Type::Solid, "trash-can").unwrap() - ).unwrap(); - - pub static ref FA_EDIT: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_pen_to_square", - fa::svg(fa::Type::Solid, "pen-to-square").unwrap() - ).unwrap(); - - pub static ref FA_SEARCH: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_magnifying_glass", - fa::svg(fa::Type::Solid, "magnifying-glass").unwrap() - ).unwrap(); - - // Pro icon :( - /* pub static ref FA_GLOBE: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_globe", - fa::svg(fa::Type::Regular, "globe").unwrap() - ).unwrap(); */ - - pub static ref FA_CIRCLE: RetainedImage = egui_extras::RetainedImage::from_svg_str( - "fa_circle", - fa::svg(fa::Type::Regular, "circle").unwrap() - ).unwrap(); -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/milo.rs b/apps/ui/preview_ui/src/gui/milo.rs deleted file mode 100644 index ed8a6c37..00000000 --- a/apps/ui/preview_ui/src/gui/milo.rs +++ /dev/null @@ -1,73 +0,0 @@ -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use grim::ark::{Ark, ArkOffsetEntry}; -use itertools::*; -use super::{AppSettings, AppState, ArkDirNode, AppEvent}; - -pub fn draw_milo_tree(state: &mut AppState, _ctx: &mut &Context, ui: &mut Ui) { - if let Some(milo) = state.milo.take() { - let mut entries = milo.get_entries().iter().map(|e| e).collect::>(); - - ui.horizontal(|ui| { - ui.label("Filter:"); - ui.text_edit_singleline(&mut state.milo_view.filter); - - // Selected class name - let classes = entries.iter().map(|x| x.get_type()).unique().sorted().collect::>(); - egui::ComboBox::from_label("") - .width(100.0) - .selected_text(state.milo_view.class_filter.as_ref().unwrap_or(&String::from("(None)"))) - .show_ui(ui, |ui| { - if ui.selectable_label(state.milo_view.class_filter.is_none(), "(None)") - .clicked() { - state.milo_view.class_filter = None; - }; - - for class in classes { - let mut checked = false; - if let Some(filter) = &state.milo_view.class_filter { - checked = filter.eq(class); - } - - if ui.selectable_label(checked, class).clicked() { - state.milo_view.class_filter = Some(class.to_string()); - } - } - }); - }); - - - if let Some(selected_class) = &state.milo_view.class_filter { - entries.retain(|e| e.get_type().eq(selected_class)); - } - - if !state.milo_view.filter.is_empty() { - entries.retain(|e| e.get_name().contains(&state.milo_view.filter)); - } - - // Sort objects - entries.sort_by_key(|e| e.get_name()); - - egui::Grid::new("milo_tree").min_col_width(200.0).striped(true).show(ui, |ui| { - for entry in entries.iter() { - let entry_name = entry.get_name(); - let mut checked = false; - - if let Some(selected) = &state.milo_view.selected_entry { - checked = selected.eq(entry_name); - } - - if ui.selectable_label(checked, entry_name).clicked() { - state.add_event(AppEvent::SelectMiloEntry(Some(entry_name.to_owned()))); - } - ui.end_row(); - } - - if entries.is_empty() { - ui.label("No objects found"); - } - }); - - // Give milo back - state.milo = Some(milo); - } -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/mod.rs b/apps/ui/preview_ui/src/gui/mod.rs deleted file mode 100644 index eba919a0..00000000 --- a/apps/ui/preview_ui/src/gui/mod.rs +++ /dev/null @@ -1,291 +0,0 @@ -mod ark; -mod icons; -mod milo; -mod toolbar; - -use ark::*; -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use milo::*; -use super::{AppSettings, AppState, ArkDirNode, AppEvent}; -use toolbar::*; - -pub fn render_gui(ctx: &mut &Context, settings: &mut AppSettings, state: &mut AppState) { - // Top Toolbar - render_toolbar(ctx, settings, state); - - //ctx.set_visuals(egui::Visuals::light()); - - // Side panel - egui::SidePanel::left("side_panel").min_width(200.0).default_width(300.0).resizable(true).show(ctx, |ui| { - egui::ScrollArea::vertical().show_viewport(ui, |ui, _viewport| { - //ui.horizontal(|ui| { - //ui.set_min_width(300.0); - - ui.vertical(|ui| { - ui.horizontal(|ui| { - for (i, text) in [ "Ark", "Milo" ].iter().enumerate() { - if ui.selectable_label(i == state.side_bar_tab_index, *text).clicked() { - state.side_bar_tab_index = i; - } - } - }); - - match state.side_bar_tab_index { - 0 => draw_ark_tree(state, ctx, ui), - 1 => draw_milo_tree(state, ctx, ui), - _ => todo!() - }; - - /*ui.group(|ui| { - ui.heading("Options"); - ui.label("Do something 1"); - ui.label("Do something 2"); - - let popup_id = ui.make_persistent_id("popup_id"); - let popup_btn = ui.button("Show popup"); - - if popup_btn.clicked() { - ui.memory().toggle_popup(popup_id); - } - - egui::popup::popup_below_widget(ui, popup_id, &popup_btn, |ui| { - ui.group(|ui| { - ui.label("Some more info, or things you can select:"); - ui.label("…"); - }); - }); - });*/ - }); - - ui.separator(); - - ui.style_mut().spacing.interact_size = bevy_egui::egui::Vec2::default(); - - ui.vertical(|ui| { - ui.style_mut().spacing.item_spacing = bevy_egui::egui::Vec2::default(); - - /*if ui.checkbox(&mut settings.show_side_panel, "").changed() { - state.save_settings(&settings); - }*/ - }); - //}); - }); - }); - - /*let mut frame = egui::Frame::default(); - frame.fill = Color32::from_rgba_premultiplied(0, 128, 128, 16); - - egui::CentralPanel::default().frame(frame).show(ctx, |ui| { - ui.label("Hello world"); - });*/ - -/* - let frame = egui::Frame::none().fill(Color32::GREEN).multiply_with_opacity(0.1); - egui::CentralPanel::default().frame(frame).show(ctx, |_| {}); -*/ - // Hide menu shadow - let mut style: egui::Style = (*ctx.style()).clone(); - let shadow_color = style.visuals.window_shadow.color; - style.visuals.window_shadow.color = shadow_color.linear_multiply(0.0); - ctx.set_style(style); - - /*egui::Window::new("Hello").show(ctx, |ui| { - // let mut style = ui.style_mut(); - // style.visuals.code_bg_color = style.visuals.code_bg_color.linear_multiply(0.1); - - ui.label("world"); - });*/ - - let size = ctx.used_size(); - let _size_pos = Pos2::new(size.x, size.y); - - // Camera controls - if settings.show_controls { - egui::Window::new("Controls").resizable(false).collapsible(false).anchor(bevy_egui::egui::Align2::RIGHT_BOTTOM, bevy_egui::egui::Vec2::new(-10.0, -10.0)).show(ctx, |ui| { - egui::Grid::new("grid_controls").striped(true).show(ui, |ui| { - ui.label("Move"); - ui.label("W/A/S/D"); - ui.end_row(); - - ui.label("Up"); - ui.label("Space"); - ui.end_row(); - - ui.label("Down"); - ui.label("L-Shift"); - ui.end_row(); - - ui.label("View"); - ui.label("L-Click + Mouse"); - ui.end_row(); - }); - }); - } - - // Hotbar - egui::Window::new("Hotbar") - .title_bar(false) - .resizable(false) - .collapsible(false) - .anchor(egui::Align2::CENTER_TOP, [0., 10.]) - .auto_sized() - //.fixed_size([12., 12.]) - .show(ctx, |ui| { - const ICON_SIZE: egui::Vec2 = egui::Vec2::splat(12.); - - ui.horizontal(|ui| { - if ui.add( - egui::ImageButton::new( - icons::FA_GRID.texture_id(ctx), - ICON_SIZE - ).selected(settings.show_gridlines)) - .on_hover_text("Grid lines") - .clicked() { - settings.show_gridlines = !settings.show_gridlines; - state.add_event(AppEvent::ToggleGridLines(settings.show_gridlines)); - - state.save_settings(&settings); - } - - if ui.add( - egui::ImageButton::new( - icons::FA_CIRCLE.texture_id(ctx), - ICON_SIZE - ).selected(settings.show_wireframes)) - .on_hover_text("Wireframe") - .clicked() { - settings.show_wireframes = !settings.show_wireframes; - state.add_event(AppEvent::ToggleWireframes(settings.show_wireframes)); - - state.save_settings(&settings); - } - - // TODO: Add to settings or something - ui.add( - egui::ImageButton::new( - icons::FA_CUBES.texture_id(ctx), - ICON_SIZE - )) - .on_hover_text("Meshes"); - - ui.add( - egui::ImageButton::new( - icons::FA_ARROWS_MULTI.texture_id(ctx), - ICON_SIZE - )) - .on_hover_text("Do Something"); - - ui.add( - egui::ImageButton::new( - icons::FA_REFRESH.texture_id(ctx), - ICON_SIZE - )) - .on_hover_text("Refresh"); - }) - }); - - // Bottom Toolbar - /*egui::TopBottomPanel::bottom("bottom_panel").show(ctx, |ui| { - ui.label("Created by PikminGuts92"); - });*/ - - if state.show_options { - egui::Window::new("Options") - //.id("options_window") - .collapsible(false) - .anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0]) - .fixed_size(bevy_egui::egui::Vec2::new(600.0, 400.0)) - .open(&mut state.show_options) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.group(|ui| { - egui::Grid::new("options_list") - .striped(true) - .min_col_width(120.0) - .show(ui, |ui| { - ui.label("General"); - ui.end_row(); - - ui.label("Ark Paths"); - ui.end_row(); - - ui.label("Preferences"); - ui.end_row(); - }); - }); - - ui.separator(); - - ui.vertical_centered_justified(|ui| { - ui.heading("Ark Paths"); - - egui::Grid::new("ark_paths") - .striped(true) - .show(ui, |ui| { - for g in settings.game_paths.iter() { - ui.label(&g.game.to_string()); - ui.label(&g.platform.to_string()); - ui.label(&g.path); - - ui.end_row(); - } - }); - - ui.add_space(500.0); - }); - }); - - /*ui.columns(2, |cols| { - egui::Grid::new("options_list") - .striped(true) - .show(&mut cols[0], |ui| { - ui.label("Ark Paths"); - ui.end_row(); - - ui.label("Preferences"); - ui.end_row(); - }); - - let ui = &mut cols[1]; - ui.add(egui::Separator::default().vertical()); - - ui.group(|ui| { - ui.vertical_centered_justified(|ui| { - ui.heading("Ark Paths"); - - ui.add_space(500.0); - }); - }); - - /*egui::Grid::new("options_view") - .striped(true) - .show(&mut cols[1], |ui| { - ui.label("Options view goes here"); - ui.end_row(); - });*/ - });*/ - }); - } -} - -pub fn render_gui_info(ctx: &mut &Context, state: &mut AppState) { - //egui::Label::new("vert_face_count") - - let vert_count = state.vert_count; - let face_count = state.face_count; - - egui::Area::new("vert_face_count") - .anchor(egui::Align2::RIGHT_TOP, [-10., 10.]) - .interactable(false) - .movable(false) - .show(ctx, |ui| { - //ui.add_space(32.); - - //ui.label(format!("Vertices: {vert_count} Faces: {face_count}")); - - ui.with_layout(egui::Layout::top_down(egui::Align::RIGHT), |ui| { - ui.label(format!("Vertices: {vert_count}")); - ui.label(format!("Faces: {face_count}")); - }); - }); -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/gui/toolbar.rs b/apps/ui/preview_ui/src/gui/toolbar.rs deleted file mode 100644 index 2462ac17..00000000 --- a/apps/ui/preview_ui/src/gui/toolbar.rs +++ /dev/null @@ -1,67 +0,0 @@ -#![allow(unused_must_use)] - -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use super::{AppSettings, AppState, ArkDirNode, AppEvent}; - -pub fn render_toolbar(ctx: &mut &Context, settings: &mut AppSettings, state: &mut AppState) { - egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { - // ui.heading("Main"); - - egui::menu::bar(ui, |ui| { - // File dropdown - egui::menu::menu_button(ui, "File", |ui| { - ui.set_min_width(80.0); - - ui.button("Open"); - ui.separator(); - - ui.button("Save"); - ui.button("Save As..."); - ui.separator(); - - ui.button("Close"); - ui.separator(); - - if ui.button("Exit").clicked() { - // Close app - state.add_event(AppEvent::Exit); - } - }); - - // Edit dropdown - egui::menu::menu_button(ui, "Edit", |ui| { - ui.set_min_width(80.0); - - ui.button("Undo"); - ui.button("Redo"); - }); - - // View dropdown - egui::menu::menu_button(ui, "View", |ui| { - ui.set_min_width(80.0); - - if ui.checkbox(&mut settings.show_controls, "Controls").changed() { - state.save_settings(&settings); - } - }); - - // Tools dropdown - egui::menu::menu_button(ui, "Tools", |ui| { - ui.set_min_width(80.0); - - if ui.button("Options").clicked() { - state.show_options = true; - } - }); - - // Help dropdown - egui::menu::menu_button(ui, "Help", |ui| { - ui.set_min_width(120.0); - - ui.button("About"); - ui.separator(); - ui.button("Check for Updates"); - }); - }); - }); -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/main.rs b/apps/ui/preview_ui/src/main.rs deleted file mode 100644 index c4394607..00000000 --- a/apps/ui/preview_ui/src/main.rs +++ /dev/null @@ -1,497 +0,0 @@ -#![allow(dead_code)] -#![allow(unused_imports)] - -// Hide console if release build -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -mod events; -mod gui; -mod plugins; -mod render; -mod settings; -mod state; - -use events::*; -use gui::*; -use render::{render_milo, render_milo_entry}; -use settings::*; -use bevy::{prelude::*, render::camera::PerspectiveProjection, window::{PresentMode, PrimaryWindow, WindowMode, WindowResized}, winit::WinitWindows}; -use bevy_egui::{EguiContext, EguiPlugin, egui, egui::{Color32, Context, Pos2, Ui}}; -use bevy_fly_camera::{FlyCamera, FlyCameraPlugin}; -use bevy_infinite_grid::{GridShadowCamera, InfiniteGridBundle, InfiniteGrid, InfiniteGridPlugin}; -use grim::*; -use grim::ark::{Ark, ArkOffsetEntry}; -use grim::scene::*; -use log::{debug, info, warn}; -use plugins::*; -use state::*; -use std::{env::args, path::{Path, PathBuf}}; - -use crate::render::open_and_unpack_milo; - -#[derive(Component)] -pub struct WorldMesh { - name: String, - vert_count: usize, - face_count: usize, -} - -fn main() { - App::new() - .add_event::() - .add_event::() - //.insert_resource(ClearColor(Color::BLACK)) - .insert_resource(Msaa::Sample4) - .add_plugins(GrimPlugin) - .add_plugins(EguiPlugin) - .add_plugins(FlyCameraPlugin) - .add_plugins(InfiniteGridPlugin) - .add_systems(Update, render_gui_system) - .add_systems(Update, detect_meshes) - .add_systems(Update, control_camera) - .add_systems(Update, drop_files) - .add_systems(Update, window_resized) - .add_systems(Update, consume_file_events) - .add_systems(Update, consume_app_events) - .add_systems(Startup, setup_args) - .add_systems(Startup, setup) - .run(); -} - -fn render_gui_system(mut settings: ResMut, mut state: ResMut, mut egui_ctx_query: Query<&mut EguiContext, With>, mut event_writer: EventWriter) { - let egui_ctx = egui_ctx_query.single_mut(); - - render_gui(&mut egui_ctx.get(), &mut *settings, &mut *state); - render_gui_info(&mut egui_ctx.get(), &mut *state); - - state.consume_events(|ev| { - event_writer.send(ev); - }); -} - -fn detect_meshes( - mut state: ResMut, - mesh_entities: Query<&WorldMesh>, -) { - let mut vertex_count = 0; - let mut face_count = 0; - - for world_mesh in mesh_entities.iter() { - vertex_count += world_mesh.vert_count; - face_count += world_mesh.face_count; - } - - // Update counts - state.vert_count = vertex_count; - state.face_count = face_count; -} - -fn setup( - mut commands: Commands, - _meshes: ResMut>, - _materials: ResMut>, - mut primary_window_query: Query<&mut Window, With>, - settings: Res, - _state: Res, -) { - // Set primary window to maximized if preferred - if settings.maximized { - let mut window = primary_window_query.single_mut(); - window.set_maximized(true); - } - - // plane - /*commands.spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane { size: 5.0 })), - material: materials.add(StandardMaterial { - base_color: Color::rgb(0.3, 0.5, 0.3), - double_sided: true, - unlit: false, - ..Default::default() - }), - ..Default::default() - });*/ - - /* - commands.spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from( - shape::Icosphere { - radius: 0.8, - subdivisions: 5, - }) - ), - material: materials.add(StandardMaterial { - base_color: Color::rgb(1.0, 0.0, 1.0), - double_sided: true, - unlit: false, - ..Default::default() - }), - transform: Transform::from_xyz(0.0, 2.0, 0.0), - ..Default::default() - }); - - // cube - commands.spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), - material: materials.add(StandardMaterial { - base_color: Color::rgb(0.8, 0.7, 0.6), - double_sided: true, - unlit: false, - ..Default::default() - }), - transform: Transform::from_xyz(0.0, 0.5, 0.0), - ..Default::default() - });*/ - // light - /*commands.spawn_bundle(LightBundle { - transform: Transform::from_xyz(4.0, 8.0, 4.0), - ..Default::default() - });*/ - // camera - let mut camera = Camera3dBundle::default(); - camera.transform = Transform::from_xyz(-2.0, 2.5, 5.0) - .looking_at(Vec3::ZERO, Vec3::Y); - - commands.spawn(camera).insert(FlyCamera { - enabled: false, - sensitivity: 0.0, - ..Default::default() - }).insert(GridShadowCamera); // Fix camera - - // Infinite grid - commands.spawn(InfiniteGridBundle { - grid: InfiniteGrid { - fadeout_distance: 300., - shadow_color: None, // No shadow - ..InfiniteGrid::default() - }, - visibility: if settings.show_gridlines { - Visibility::Visible - } else { - Visibility::Hidden - }, - ..InfiniteGridBundle::default() - }); -} - -fn setup_args( - _state: ResMut, - mut ev_update_state: EventWriter, -) { - let mut args = args().skip(1).collect::>(); - if args.is_empty() { - return; - } - - ev_update_state.send(AppFileEvent::Open(args.remove(0).into())); -} - -fn consume_file_events( - mut file_events: EventReader, - mut app_event_writer: EventWriter, - mut state: ResMut, -) { - for e in file_events.iter() { - match e { - AppFileEvent::Open(file_path) => { - //milo_event_writer.send(bevy::app::AppExit); - open_file(file_path, &mut state, &mut app_event_writer); - } - } - } -} - -fn consume_app_events( - mut app_events: EventReader, - mut bevy_event_writer: EventWriter, - mut state: ResMut, - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, - mut textures: ResMut>, - mut grid: Query<&mut Visibility, With>, - mut wireframe_config: ResMut, - world_meshes: Query<(Entity, &WorldMesh)>, -) { - for e in app_events.iter() { - match e { - AppEvent::Exit => { - bevy_event_writer.send(bevy::app::AppExit); - }, - AppEvent::SelectMiloEntry(entry_name) => { - /*let render_entry = match &state.milo_view.selected_entry { - Some(name) => name.ne(entry_name), - None => true, - };*/ - - // Clear everything - let mut i = 0; - for (entity, _) in world_meshes.iter() { - i += 1; - commands.entity(entity).despawn_recursive(); - } - if i > 0 { - debug!("Removed {} meshes in scene", i); - } - - /*if render_entry { - let milo = state.milo.as_ref().unwrap(); - let info = state.system_info.as_ref().unwrap(); - - render_milo_entry( - &mut commands, - &mut meshes, - &mut materials, - &mut textures, - milo, - Some(entry_name.to_owned()), - info - ); - }*/ - - let milo = state.milo.as_ref().unwrap(); - let milo_path = state.open_file_path.as_ref().unwrap(); - let info = state.system_info.as_ref().unwrap(); - - // Render everything for now - render_milo_entry( - &mut commands, - &mut meshes, - &mut materials, - &mut textures, - milo, - milo_path, - entry_name.to_owned(), - info - ); - - state.milo_view.selected_entry = entry_name.to_owned(); - - debug!("Updated milo"); - }, - AppEvent::ToggleGridLines(show) => { - *grid.single_mut() = if *show { - Visibility::Visible - } else { - Visibility::Hidden - }; - }, - AppEvent::ToggleWireframes(show) => { - //grid.single_mut().is_visible = *show; - wireframe_config.global = *show; - } - /*AppEvent::RefreshMilo => { - return; - - if let Some(milo) = &state.milo { - let info = state.system_info.as_ref().unwrap(); - render_milo( - &mut commands, - &mut meshes, - &mut materials, - &mut textures, - milo, - info - ); - } - - debug!("Updated milo"); - },*/ - } - } -} - -fn open_file( - file_path: &PathBuf, - state: &mut ResMut, - app_event_writer: &mut EventWriter, -) { - // Clear file path - state.open_file_path.take(); - - // Get full file extension - let ext = file_path - .file_name() - .and_then(|n| n.to_str()) - .map(|n| match n.find('.') { - Some(i) => &n[i..], - _ => n - }) - .unwrap(); - - // TODO: Make case-insensitive - if ext.contains("hdr") { - // Open ark - info!("Opening hdr from \"{}\"", file_path.display()); - - let ark_res = Ark::from_path(file_path); - if let Ok(ark) = ark_res { - debug!("Successfully opened ark with {} entries", ark.entries.len()); - - state.root = Some(create_ark_tree(&ark)); - state.ark = Some(ark); - state.open_file_path = Some(file_path.to_owned()); - } - } else if ext.contains("milo") - || ext.contains("gh") - || ext.contains("rnd") { // TODO: Break out into static regex - // Open milo - info!("Opening milo from \"{}\"", file_path.display()); - - match open_and_unpack_milo(file_path) { - Ok((milo, info)) => { - debug!("Successfully opened milo with {} entries", milo.get_entries().len()); - - state.milo = Some(milo); - state.system_info = Some(info); - state.open_file_path = Some(file_path.to_owned()); - - //ev_update_state.send(AppEvent::RefreshMilo); - - const NAME_PREFS: [&str; 5] = ["venue", "top", "lod0", "lod1", "lod2"]; - - let _groups = state.milo - .as_ref() - .unwrap() - .get_entries() - .iter() - .filter(|o| o.get_type() == "Group") - .collect::>(); - - let selected_entry = None; - /*for name in NAME_PREFS { - let group = groups - .iter() - .find(|g| g.get_name().starts_with(name)); - - if let Some(grp) = group { - selected_entry = Some(grp.get_name().to_owned()); - break; - } - }*/ - - app_event_writer.send(AppEvent::SelectMiloEntry(selected_entry)); - }, - Err(err) => { - warn!("Unable to unpack milo file:\n\t: {:?}", err); - } - } - } else { - info!("Unknown file type \"{}\"", file_path.display()); - } -} - -fn create_ark_tree(ark: &Ark) -> ArkDirNode { - let mut root = ArkDirNode { - name: ark.path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_owned(), // There's gotta be a better conversion... - path: String::from(""), - dirs: Vec::new(), - files: Vec::new(), - loaded: false - }; - - root.expand(ark); - root -} - -fn control_camera( - key_input: Res>, - mouse_input: Res>, - mut egui_ctx_query: Query<&mut EguiContext, With>, - mut cam_query: Query<&mut FlyCamera>, -) { - let mut egui_ctx = egui_ctx_query.single_mut(); - let ctx = egui_ctx.get_mut(); - - let key_down = is_camera_button_down(&key_input); - let mouse_down = mouse_input.pressed(MouseButton::Left); - - for mut cam in cam_query.iter_mut() { - // Disable camera move if mouse button not held - cam.sensitivity = match mouse_down { - true => 3.0, - _ => 0.0 - }; - - cam.enabled = !ctx.wants_pointer_input() - && !ctx.is_pointer_over_area() - && (key_down || mouse_down); - } -} - -fn is_camera_button_down(key_input: &Res>) -> bool { - let control_keys = [ - KeyCode::W, - KeyCode::A, - KeyCode::S, - KeyCode::D, - KeyCode::Space, - KeyCode::ShiftLeft, - ]; - - control_keys - .iter() - .any(|k| key_input.pressed(*k)) -} - -fn window_resized( - mut resize_events: EventReader, - primary_window_query: Query>, - mut settings: ResMut, - app_state: Res, - winit_windows: NonSend, -) { - let primary_window_id = primary_window_query.single(); - let window = winit_windows.get_window(primary_window_id).unwrap(); - let maximized = window.is_maximized(); - - if settings.maximized != maximized { - if maximized { - debug!("Window maximized"); - } else { - debug!("Window unmaximized"); - } - - settings.maximized = maximized; - app_state.save_settings(&settings); - return; - } - - if maximized { - // Ignore resize if maximized - return; - } - - for e in resize_events.iter() { - debug!("Window resized: {}x{}", e.width as u32, e.height as u32); - - settings.window_width = e.width; - settings.window_height = e.height; - app_state.save_settings(&settings); - } -} - -fn drop_files( - mut drag_drop_events: EventReader, - mut file_event_writer: EventWriter, -) { - for d in drag_drop_events.iter() { - if let FileDragAndDrop::DroppedFile { path_buf, .. } = d { - debug!("Dropped \"{}\"", path_buf.to_str().unwrap()); - - file_event_writer.send(AppFileEvent::Open(path_buf.to_owned())); - } - } -} - -fn is_drop_event(dad_event: &FileDragAndDrop) -> bool { - match dad_event { - FileDragAndDrop::DroppedFile { .. } => true, - _ => false - } -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/plugins.rs b/apps/ui/preview_ui/src/plugins.rs deleted file mode 100644 index cc6844e5..00000000 --- a/apps/ui/preview_ui/src/plugins.rs +++ /dev/null @@ -1,96 +0,0 @@ -use bevy::{prelude::*, log::LogPlugin, app::PluginGroupBuilder, window::{PresentMode, WindowMode, WindowResized, WindowResolution}}; -use crate::settings::*; -use crate::state::*; -use log::info; -use std::{env::args, path::{Path, PathBuf}}; - -const SETTINGS_FILE_NAME: &str = "settings.json"; -const PROJECT_NAME: &str = env!("CARGO_PKG_NAME"); -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -struct MinimalPlugins; - -pub struct GrimPlugin; - -// Using until bevy_fly_camera is updated -// https://github.com/mcpar-land/bevy_fly_camera/pull/19 -impl PluginGroup for MinimalPlugins { - fn build(self) -> PluginGroupBuilder { - // Reference: https://github.com/bevyengine/bevy/blob/main/crates/bevy_internal/src/default_plugins.rs - PluginGroupBuilder::start::() - // Basic stuff - .add(bevy::log::LogPlugin::default()) - .add(bevy::core::TaskPoolPlugin::default()) - .add(bevy::core::TypeRegistrationPlugin::default()) - .add(bevy::core::FrameCountPlugin::default()) - .add(bevy::time::TimePlugin::default()) - .add(bevy::transform::TransformPlugin::default()) - .add(bevy::hierarchy::HierarchyPlugin::default()) - .add(bevy::diagnostic::DiagnosticsPlugin::default()) - .add(bevy::input::InputPlugin::default()) - .add(bevy::window::WindowPlugin::default()) - .add(bevy::a11y::AccessibilityPlugin) - // Optional features being used - .add(bevy::asset::AssetPlugin::default()) - .add(bevy::scene::ScenePlugin::default()) - .add(bevy::winit::WinitPlugin::default()) - .add(bevy::render::RenderPlugin::default()) - .add(bevy::render::texture::ImagePlugin::default()) - .add(bevy::core_pipeline::CorePipelinePlugin::default()) - .add(bevy::pbr::PbrPlugin::default()) - .add(bevy::pbr::wireframe::WireframePlugin) - } -} - -impl Plugin for GrimPlugin { - fn build(&self, app: &mut App) { - // Load settings - #[cfg(target_family = "wasm")] std::panic::set_hook(Box::new(console_error_panic_hook::hook)); - #[cfg(target_family = "wasm")] let app_state = AppState::default(); - #[cfg(target_family = "wasm")] let app_settings = AppSettings::default(); - - #[cfg(not(target_family = "wasm"))] let app_state = load_state(); - #[cfg(not(target_family = "wasm"))] let app_settings = load_settings(&app_state.settings_path); - - app - //.add_plugins(DefaultPlugins); - .add_plugins(MinimalPlugins.set(WindowPlugin { - primary_window: Some(Window { - title: format!("Preview v{}", VERSION), - resolution: WindowResolution::new( - app_settings.window_width, - app_settings.window_height - ), - mode: WindowMode::Windowed, - present_mode: PresentMode::Fifo, // vsync - resizable: true, - ..Default::default() - }), - ..Default::default() - })) - .insert_resource(bevy::pbr::wireframe::WireframeConfig { - global: app_settings.show_wireframes - }) - .insert_resource(app_state) - .insert_resource(app_settings); - } -} - -fn load_state() -> AppState { - let exe_path = &std::env::current_exe().unwrap(); - let exe_dir_path = exe_path.parent().unwrap(); - let settings_path = exe_dir_path.join(&format!("{}.{}", PROJECT_NAME, SETTINGS_FILE_NAME)); - - AppState { - settings_path, - //show_options: true, // TODO: Remove after testing - ..Default::default() - } -} - -fn load_settings(settings_path: &Path) -> AppSettings { - let settings = AppSettings::load_from_file(settings_path); - info!("Loaded settings from \"{}\"", settings_path.to_str().unwrap()); - - settings -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/render/loader.rs b/apps/ui/preview_ui/src/render/loader.rs deleted file mode 100644 index 2bfdf2e8..00000000 --- a/apps/ui/preview_ui/src/render/loader.rs +++ /dev/null @@ -1,180 +0,0 @@ -use grim::{Platform, SystemInfo}; -use grim::io::*; -use grim::scene::{GroupObject, MatObject, Matrix, MeshObject, Milo, MiloObject, Object, ObjectDir, PackedObject, RndMesh, Tex, Trans, TransConstraint}; - -use itertools::*; - -use std::collections::HashMap; - -pub struct MiloLoader<'a> { - milo: &'a ObjectDir, - milo_path: &'a std::path::Path, - objects: HashMap<&'a str, &'a Object>, - groups: HashMap<&'a str, &'a GroupObject>, - mats: HashMap<&'a str, &'a MatObject>, - meshes: HashMap<&'a str, &'a MeshObject>, - textures: HashMap<&'a str, &'a Tex>, - cached_textures: HashMap<&'a str, (&'a Tex, Vec, ImageInfo)>, - transforms: HashMap<&'a str, &'a dyn Trans>, -} - -pub enum TextureEncoding { - RGBA, - DXT1, - DXT5, - ATI2 -} - -pub struct ImageInfo { - pub width: u32, - pub height: u32, - pub mips: u32, - pub encoding: TextureEncoding, -} - -impl From<&grim::texture::Bitmap> for ImageInfo { - fn from(b: &grim::texture::Bitmap) -> Self { - Self { - width: b.width as u32, - height: b.height as u32, - mips: b.mip_maps as u32, - encoding: TextureEncoding::RGBA, // Actually set elsewhere - } - } -} - -impl<'a> MiloLoader<'a> { - pub fn new(milo: &'a ObjectDir, milo_path: &'a std::path::Path) -> MiloLoader<'a> { - let entries = milo.get_entries(); - - let objects = entries - .iter() - .fold(HashMap::new(), |mut acc, o| { - acc.insert(o.get_name(), o); - acc - }); - - let groups = get_objects_mapped( - entries, - |o| match o { - Object::Group(g) => Some(g), - _ => None, - }); - - let mats = get_objects_mapped( - entries, - |o| match o { - Object::Mat(m) => Some(m), - _ => None, - }); - - let meshes = get_objects_mapped( - entries, - |o| match o { - Object::Mesh(m) => Some(m), - _ => None, - }); - - let textures = get_objects_mapped( - entries, - |o| match o { - Object::Tex(t) => Some(t), - _ => None, - }); - - let transforms = get_objects_mapped_dyn( - entries, - |o| match o { - Object::Group(grp) => Some(grp as &dyn Trans), - Object::Mesh(mesh) => Some(mesh as &dyn Trans), - Object::Trans(trans) => Some(trans as &dyn Trans), - _ => None, - }); - - MiloLoader { - milo, - milo_path, - objects, - groups, - mats, - meshes, - textures, - cached_textures: HashMap::new(), - transforms, - } - } - - pub fn get_object(&self, name: &str) -> Option<&'a Object> { - self.objects - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_group(&self, name: &str) -> Option<&'a GroupObject> { - self.groups - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_mat(&self, name: &str) -> Option<&'a MatObject> { - self.mats - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_mesh(&self, name: &str) -> Option<&'a MeshObject> { - self.meshes - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_texture(&self, name: &str) -> Option<&'a Tex> { - self.textures - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_cached_texture(&self, name: &str) -> Option<&(&'a Tex, Vec, ImageInfo)> { - self.cached_textures.get(name) - } - - pub fn set_cached_texture(&mut self, name: &str, rgba: Vec, image_info: ImageInfo) { - let tex = self.get_texture(name).unwrap(); - - self.cached_textures.insert(tex.get_name().as_str(), (tex, rgba, image_info)); - } - - pub fn get_transform(&self, name: &str) -> Option<&'a dyn Trans> { - self.transforms - .get(name) - .and_then(|o| Some(*o)) - } - - pub fn get_milo_path(&self) -> &std::path::Path { - self.milo_path - } -} - -fn get_objects_mapped(objects: &Vec, filter: impl Fn(&Object) -> Option<&T>) -> HashMap<&str, &T> { - objects - .iter() - .map(filter) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .fold(HashMap::new(), |mut acc, o| { - acc.insert(o.get_name().as_str(), o); - acc - }) -} - -fn get_objects_mapped_dyn(objects: &Vec, filter: impl Fn(&Object) -> Option<&T>) -> HashMap<&str, &T> { - objects - .iter() - .map(filter) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .fold(HashMap::new(), |mut acc, o| { - acc.insert(o.get_name().as_str(), o); - acc - }) -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/render/milo_entry.rs b/apps/ui/preview_ui/src/render/milo_entry.rs deleted file mode 100644 index bc969b65..00000000 --- a/apps/ui/preview_ui/src/render/milo_entry.rs +++ /dev/null @@ -1,585 +0,0 @@ -use bevy::prelude::*; -use bevy::render::render_resource::{AddressMode, Extent3d, SamplerDescriptor, TextureDimension, TextureFormat}; -use bevy::render::texture::ImageSampler; - -use itertools::*; -use log::warn; - -use std::collections::HashMap; -use std::error::Error; -use std::fs; -use std::io::Read; -use std::path::{Path, PathBuf}; -use thiserror::Error; - -use grim::{Platform, SystemInfo}; -use grim::io::*; -use grim::scene::{GroupObject, Matrix, MeshObject, Milo, MiloObject, Object, ObjectDir, PackedObject, RndMesh, Tex, Trans, TransConstraint}; -use grim::texture::Bitmap; - -use crate::WorldMesh; -use super::{ImageInfo, map_matrix, MiloLoader, TextureEncoding}; - -pub fn render_milo_entry( - commands: &mut Commands, - bevy_meshes: &mut ResMut>, - materials: &mut ResMut>, - bevy_textures: &mut ResMut>, - milo: &ObjectDir, - milo_path: &Path, - milo_entry: Option, - system_info: &SystemInfo, -) { - let mut loader = MiloLoader::new(milo, milo_path); - - // Get meshes for single object or return all meshes - // TODO: Make less hacky - let meshes = match milo_entry { - Some(entry) => { - let milo_object = loader.get_object(&entry).unwrap(); - get_object_meshes( - milo_object, - &mut loader, - ) - }, - None => milo.get_entries() - .iter() - .map(|e| match e { - Object::Mesh(m) => Some(m), - _ => None, - }) - .filter(|e| e.is_some()) - .map(|e| e.unwrap()) - .collect::>() - }; - - // Translate to bevy coordinate system - let trans_mat = Mat4::from_cols_array(&[ - -1.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 1.0, - ]); - - // Scale down - let scale_mat = Mat4::from_scale(Vec3::new(0.1, 0.1, 0.1)); - - let trans = Transform::from_matrix(trans_mat * scale_mat); - let global_trans = GlobalTransform::from(trans); - - // Root transform - let root_entity = commands.spawn_empty() - .insert(trans) - .insert(global_trans) - .insert(VisibilityBundle::default()) - .id(); - - for mesh in meshes { - // Ignore meshes without geometry (used mostly in GH1) - if mesh.vertices.is_empty() || mesh.name.starts_with("shadow") { - continue; - } - - // Get transform - let mat = get_computed_mat(mesh as &dyn Trans, &mut loader); - - let mut bevy_mesh = Mesh::new(bevy::render::render_resource::PrimitiveTopology::TriangleList); - - let vert_count = mesh.get_vertices().len(); - - let mut positions = vec![Default::default(); vert_count]; - let mut normals = vec![Default::default(); vert_count]; - let mut tangents = vec![Default::default(); vert_count]; - let mut uvs = vec![Default::default(); vert_count]; - - for (i, vert) in mesh.get_vertices().iter().enumerate() { - positions[i] = [vert.pos.x, vert.pos.y, vert.pos.z]; - - // TODO: Figure out normals/tangents - //normals.push([vert.normals.x, vert.normals.y, vert.normals.z]); - normals[i] = [1.0, 1.0, 1.0]; - tangents[i] = [0.0, 0.0, 0.0, 1.0]; - - uvs[i] = [vert.uv.u, vert.uv.v]; - } - - let indices = bevy::render::mesh::Indices::U16( - mesh.faces.iter().flat_map(|f| *f).collect() - ); - - bevy_mesh.set_indices(Some(indices)); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); - - // Load textures - let tex_names = loader.get_mat(&mesh.mat) - .map(|mat| ( - mat.diffuse_tex.to_owned(), - mat.normal_map.to_owned(), - mat.emissive_map.to_owned(), - )); - - let (diffuse, normal, emissive) = tex_names - .map(|(diffuse, normal, emissive)| ( - get_texture(&mut loader, &diffuse, system_info) - .map(map_texture) - .map(|t| bevy_textures.add(t)), - get_texture(&mut loader, &normal, system_info) - .map(map_texture) - .map(|t| bevy_textures.add(t)), - get_texture(&mut loader, &emissive, system_info) - .map(map_texture) - .map(|t| bevy_textures.add(t)), - )) - .unwrap_or_default(); - - let bevy_mat = match loader.get_mat(&mesh.mat) { - Some(mat) => StandardMaterial { - base_color: Color::rgba( - mat.color.r, - mat.color.g, - mat.color.b, - mat.alpha, - ), - double_sided: true, - unlit: true, - base_color_texture: diffuse, - normal_map_texture: normal, - emissive_texture: emissive, - //roughness: 0.8, // TODO: Bevy 0.6 migration - /*base_color_texture: get_texture(&mut loader, &mat.diffuse_tex, system_info) - .and_then(map_texture) - .and_then(|t| Some(bevy_textures.add(t))), - normal_map: get_texture(&mut loader, &mat.norm_detail_map, system_info) - .and_then(map_texture) - .and_then(|t| Some(bevy_textures.add(t))), - emissive_texture: get_texture(&mut loader, &mat.emissive_map, system_info) - .and_then(map_texture) - .and_then(|t| Some(bevy_textures.add(t))),*/ - ..Default::default() - }, - None => StandardMaterial { - base_color: Color::rgb(0.3, 0.5, 0.3), - double_sided: true, - unlit: false, - ..Default::default() - }, - }; - - // Add mesh - commands.entity(root_entity) - .with_children(|parent| { - parent.spawn(PbrBundle { - mesh: bevy_meshes.add(bevy_mesh), - material: materials.add(bevy_mat), - transform: Transform::from_matrix(mat), - ..Default::default() - }).insert(WorldMesh { - name: mesh.name.to_owned(), - vert_count: mesh.vertices.len(), - face_count: mesh.faces.len() - }); - }); - } -} - -fn get_object_meshes<'a>( - milo_object: &'a Object, - loader: &mut MiloLoader<'a>, -) -> Vec<&'a MeshObject> { - let mut meshes = Vec::new(); - - match milo_object { - Object::Group(grp) => { - // Iterate sub objects - for obj_name in &grp.objects { - let child_object = loader.get_object(&obj_name); - if child_object.is_none() { - continue; - } - - let mut child_meshes = get_object_meshes( - child_object.unwrap(), - loader, - ); - - meshes.append(&mut child_meshes); - } - }, - Object::Mesh(mesh) => { - meshes.push(mesh); - - // Iterate sub meshes - for sub_draw_name in &mesh.draw_objects { - let child_draw = loader.get_object(&sub_draw_name); - if child_draw.is_none() { - continue; - } - - let mut child_meshes = get_object_meshes( - child_draw.unwrap(), - loader, - ); - - meshes.append(&mut child_meshes); - } - }, - _ => { - - } - }; - - // Return meshes - meshes -} - -fn get_object_meshes_with_transform<'a>( - milo_object: &'a Object, - loader: &mut MiloLoader<'a>, -) -> Vec<(&'a MeshObject, Mat4)> { - let mut meshes = Vec::new(); - - match milo_object { - Object::Group(grp) => { - // Iterate sub objects - for obj_name in &grp.objects { - let child_object = loader.get_object(&obj_name); - if child_object.is_none() { - continue; - } - - let child_meshes = get_object_meshes_with_transform( - child_object.unwrap(), - loader, - ); - - for (mesh, mat) in child_meshes { - meshes.push((mesh, mat)); - } - } - }, - Object::Mesh(mesh) => { - let mat = get_computed_mat(mesh as &dyn Trans, loader); - meshes.push((mesh, mat)); - - // Iterate sub meshes - for sub_draw_name in &mesh.draw_objects { - let child_draw = loader.get_object(&sub_draw_name); - if child_draw.is_none() { - continue; - } - - let child_meshes = get_object_meshes_with_transform( - child_draw.unwrap(), - loader, - ); - - for (mesh, mat) in child_meshes { - meshes.push((mesh, mat)); - } - } - }, - _ => { - - } - }; - - // Return meshes - meshes -} - -fn get_computed_mat<'a>( - milo_object: &'a dyn Trans, - loader: &mut MiloLoader<'a>, -) -> Mat4 { - let parent_name = milo_object.get_parent(); - if parent_name.eq(milo_object.get_name()) { - // References self, use own world transform - return map_matrix(milo_object.get_world_xfm()); - } - - // Use relative transform - if let Some(parent) = loader.get_transform(parent_name) { - let parent_mat = get_computed_mat(parent, loader); - let local_mat = map_matrix(milo_object.get_local_xfm()); - - return parent_mat * local_mat; - } - - if !parent_name.is_empty() { - warn!("Can't find trans for {}", parent_name); - } - - map_matrix(milo_object.get_world_xfm()) -} - -fn get_product_local_mat<'a>( - milo_object: &'a dyn Trans, - loader: &mut MiloLoader<'a>, -) -> Mat4 { - let parent_name = milo_object.get_parent(); - if parent_name.eq(milo_object.get_name()) { - // References self, use own local transform - return map_matrix(milo_object.get_local_xfm()); - } - - // Use relative transform - if let Some(parent) = loader.get_transform(parent_name) { - let parent_mat = get_product_local_mat(parent, loader); - let local_mat = map_matrix(milo_object.get_local_xfm()); - - return parent_mat * local_mat; - } - - if parent_name.is_empty() { - warn!("Can't find trans for {}", parent_name); - } - - map_matrix(milo_object.get_local_xfm()) -} - -fn get_texture<'a, 'b>(loader: &'b mut MiloLoader<'a>, tex_name: &str, system_info: &SystemInfo) -> Option<&'b (&'a Tex, Vec, ImageInfo)> { - // Check for cached texture - if let Some(_cached) = loader.get_cached_texture(tex_name).take() { - // TODO: Figure out why commented out line doesn't work (stupid lifetimes) - //return Some(cached); - return loader.get_cached_texture(tex_name); - } - - let mut ext_tex = None; - - // Get bitmap and decode texture - // TODO: Check for external textures - loader.get_texture(tex_name) - .and_then(|t| { - if t.bitmap.is_some() { - t.bitmap.as_ref() - } else { - // Load external texture - let milo_dir_path = loader.get_milo_path().parent().unwrap(); - - // Insert "gen" sub folder - // TODO: Support loading from milo gen folder too? - let ext_img_path = match t.ext_path.rfind("/") { - Some(last_slash_idx) => { - let (dir_path, file_name) = t.ext_path.split_at(last_slash_idx); - - milo_dir_path.join(dir_path).join("gen").join(&file_name[1..]) - }, - None => milo_dir_path.join("gen").join(&t.ext_path), - }; - - let ext_img_file_stem = ext_img_path.file_stem().and_then(|fs| fs.to_str()).unwrap(); - let ext_img_path_dir = ext_img_path.parent().unwrap(); - - let files = ext_img_path_dir.find_files_with_depth(FileSearchDepth::Immediate).unwrap(); - - // TODO: Do case-insensitive compare - let matching_file = files - .iter() - .find(|f| f - .file_stem() - //.is_some_and(|fs| fs.eq_ignore_ascii_case(ext_img_file_stem)) - .and_then(|fs| fs.to_str()) - .is_some_and(|fs| fs.starts_with(ext_img_file_stem)) - ); - - if let Some(file_path) = matching_file { - log::info!("Found external texture file!\n\t{file_path:?}"); - - let data = if file_path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("gz")) { - // File is gz compressed - let file_size = grim::io::get_file_size(file_path) as usize; - let mut file = std::fs::File::open(file_path).unwrap(); - - // Read to buffer - let mut file_data = vec![0u8; file_size]; - file.read_exact(&mut file_data).unwrap(); - - // Inflate - grim::io::inflate_gzip_block_no_buffer(&file_data).unwrap() - } else if file_path.extension().is_some_and(|ext| ext.eq_ignore_ascii_case("z")) { - // File is zlib compressed - let file_size = grim::io::get_file_size(file_path) as usize; - let mut file = std::fs::File::open(file_path).unwrap(); - - // Read to buffer - let mut file_data = vec![0u8; file_size]; - file.read_exact(&mut file_data).unwrap(); - - // Inflate - grim::io::inflate_deflate_block_no_buffer(&file_data).unwrap() - } else { - let file_size = grim::io::get_file_size(file_path) as usize; - let mut file = std::fs::File::open(file_path).unwrap(); - - // Read to buffer - let mut file_data = vec![0u8; file_size]; - file.read_exact(&mut file_data).unwrap(); - - file_data - }; - - let mut stream = grim::io::MemoryStream::from_slice_as_read(&data); - let bitmap = grim::texture::Bitmap::from_stream(&mut stream, system_info); - - if bitmap.is_ok() { - log::info!("Successfully opened bitmap"); - } else { - log::warn!("Error opening bitmap"); - } - - ext_tex = bitmap.ok(); - return ext_tex.as_ref(); - } - - None - } - }) - .and_then(|b| match (system_info.platform, b.encoding) { - (Platform::X360 | Platform::PS3, 8 | 24 | 32) => { - let enc = match b.encoding { - 8 => TextureEncoding::DXT1, - 24 => TextureEncoding::DXT5, - 32 | _ => TextureEncoding::ATI2, - }; - - let mut data = b.raw_data.to_owned(); - - if system_info.platform.eq(&Platform::X360) { - // Swap bytes - for ab in data.chunks_mut(2) { - let tmp = ab[0]; - - ab[0] = ab[1]; - ab[1] = tmp; - } - } - - Some((data, ImageInfo { - encoding: enc, - ..b.into() - })) - }, - _ => b.unpack_rgba(system_info).ok() - .and_then(|rgba| Some((rgba, ImageInfo { - encoding: TextureEncoding::RGBA, - ..b.into() - }))) - }) - .and_then(move |(rgba, image_info)| { - // Cache decoded texture - loader.set_cached_texture(tex_name, rgba, image_info); - loader.get_cached_texture(tex_name) - }) -} - -fn map_texture<'a>(tex: &'a (&'a Tex, Vec, ImageInfo)) -> Image { - let (tex, rgba, ImageInfo { width, height, mips: _, encoding: enc }) = tex; - - let bpp: usize = match enc { - TextureEncoding::DXT1 => 4, - TextureEncoding::DXT5 | TextureEncoding::ATI2 => 8, - TextureEncoding::RGBA => 32, - }; - - let tex_size = ((*width as usize) * (*height as usize) * bpp) / 8; - let use_mips = rgba.len() > tex_size; // TODO: Always support mips? - - let img_slice = if use_mips { - &rgba - } else { - &rgba[..tex_size] - }; - - let image_new_fn = match enc { - TextureEncoding::RGBA => image_new_fill, // Use fill method for older textures - _ => image_new, - }; - - let mut texture = /*Image::new_fill*/ image_new_fn( - Extent3d { - width: tex.width.into(), - height: tex.height.into(), - depth_or_array_layers: 1, - }, - TextureDimension::D2, - img_slice, - match enc { - TextureEncoding::DXT1 => TextureFormat::Bc1RgbaUnormSrgb, - TextureEncoding::DXT5 => TextureFormat::Bc3RgbaUnormSrgb, - TextureEncoding::ATI2 => TextureFormat::Bc5RgUnorm, - _ => TextureFormat::Rgba8UnormSrgb, - } - ); - - // Update texture wrap mode - texture.sampler_descriptor = ImageSampler::Descriptor(SamplerDescriptor { - address_mode_u: AddressMode::Repeat, - address_mode_v: AddressMode::Repeat, - anisotropy_clamp: 1, // 16 - ..SamplerDescriptor::default() - }); - - // Set mipmap level - if use_mips { - texture.texture_descriptor.mip_level_count = tex - .bitmap - .as_ref() - .map_or(1, |b| (b.mip_maps as u32) + 1); - } - - texture -} - -fn image_new( - size: Extent3d, - dimension: TextureDimension, - pixel: &[u8], - format: TextureFormat, -) -> Image { - // Problematic!!! - /*debug_assert_eq!( - size.volume() * format.pixel_size(), - data.len(), - "Pixel data, size and format have to match", - );*/ - let mut image = Image { - data: pixel.to_owned(), - ..Default::default() - }; - image.texture_descriptor.dimension = dimension; - image.texture_descriptor.size = size; - image.texture_descriptor.format = format; - image -} - -fn image_new_fill( - size: Extent3d, - dimension: TextureDimension, - pixel: &[u8], - format: TextureFormat, -) -> Image { - let mut value = Image::default(); - value.texture_descriptor.format = format; - value.texture_descriptor.dimension = dimension; - value.resize(size); - - // Problematic!!! - /*debug_assert_eq!( - pixel.len() % format.pixel_size(), - 0, - "Must not have incomplete pixel data." - ); - debug_assert!( - pixel.len() <= value.data.len(), - "Fill data must fit within pixel buffer." - );*/ - - for current_pixel in value.data.chunks_exact_mut(pixel.len()) { - current_pixel.copy_from_slice(pixel); - } - value -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/render/mod.rs b/apps/ui/preview_ui/src/render/mod.rs deleted file mode 100644 index 2d74877c..00000000 --- a/apps/ui/preview_ui/src/render/mod.rs +++ /dev/null @@ -1,269 +0,0 @@ -mod loader; -mod milo_entry; - -use bevy::prelude::*; -use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat}; - -pub use loader::*; -pub use milo_entry::*; - -use log::{debug, info, warn, error}; - -use std::collections::HashMap; -use std::error::Error; -use std::fs; -use std::path::{Path, PathBuf}; -use thiserror::Error; - -use grim::{Platform, SystemInfo}; -use grim::io::*; -use grim::scene::{RndMesh, Matrix, MeshObject, MiloObject, Object, ObjectDir, PackedObject, Tex, Trans, TransConstraint}; - -pub fn open_and_unpack_milo>(milo_path: T) -> Result<(ObjectDir, SystemInfo), Box> { - let milo_path = milo_path.as_ref(); - - let mut stream = FileStream::from_path_as_read_open(milo_path)?; - let milo = MiloArchive::from_stream(&mut stream)?; - - let system_info = SystemInfo::guess_system_info(&milo, &milo_path); - let mut obj_dir = milo.unpack_directory(&system_info)?; - obj_dir.unpack_entries(&system_info)?; - - Ok((obj_dir, system_info)) -} - -pub fn render_milo( - commands: &mut Commands, - bevy_meshes: &mut ResMut>, - materials: &mut ResMut>, - bevy_textures: &mut ResMut>, - milo: &ObjectDir, - system_info: &SystemInfo, -) { - let entries = milo.get_entries(); - - let groups = entries - .iter() - .map(|o| match o { - Object::Group(grp) => Some(grp), - _ => None, - }) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let mats = entries - .iter() - .map(|o| match o { - Object::Mat(mat) => Some(mat), - _ => None, - }) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let meshes = entries - .iter() - .map(|o| match o { - Object::Mesh(mesh) => Some(mesh), - _ => None, - }) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let textures = entries - .iter() - .map(|o| match o { - Object::Tex(tex) => Some(tex), - _ => None, - }) - .filter(|o| o.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let transforms = entries - .iter() - .map(|o| match o { - Object::Group(grp) => Some(get_transform(grp)), - Object::Mesh(mesh) => Some(get_transform(mesh)), - Object::Trans(trans) => Some(get_transform(trans)), - _ => None, - }) - .filter(|t| t.is_some()) - .map(|o| o.unwrap()) - .collect::>(); - - let mut tex_map = HashMap::new(); - - for tex in textures.iter() { - if let Some(bitmap) = &tex.bitmap { - // TODO: Use bevy supported texture formats instead of converting to rgba - // DXT1 = Bc1RgbaUnorm - // DXT5 = Bc3RgbaUnorm - // ATI2 = Bc5RgUnorm - match bitmap.unpack_rgba(system_info) { - Ok(rgba) => { - debug!("Processing {}", tex.get_name()); - - // TODO: Figure out how bevy can support mip maps - let tex_size = (bitmap.width as usize) * (bitmap.height as usize) * 4; - - let bevy_tex = Image::new_fill( - Extent3d { - width: bitmap.width.into(), - height: bitmap.height.into(), - depth_or_array_layers: 1 - }, - TextureDimension::D2, - &rgba[..tex_size], - TextureFormat::Rgba8UnormSrgb, - ); - - tex_map.insert(tex.get_name().as_str(), bevy_tex); - }, - Err(_err) => { - error!("Failed to convert {}", tex.get_name()); - } - } - } - } - - debug!("Found {} groups, {} meshes, {} textures, and {} materials", groups.len(), meshes.len(), textures.len(), mats.len()); - - for mesh in meshes { - // Ignore meshes without geometry (used mostly in GH1) - if mesh.vertices.is_empty() { - continue; - } - - let mut bevy_mesh = Mesh::new(bevy::render::render_resource::PrimitiveTopology::TriangleList); - - let mut positions = Vec::new(); - let mut normals = Vec::new(); - let mut tangents = Vec::new(); - let mut uvs = Vec::new(); - - for vert in mesh.get_vertices() { - positions.push([vert.pos.x, vert.pos.y, vert.pos.z]); - - // TODO: Figure out normals/tangents - //normals.push([vert.normals.x, vert.normals.y, vert.normals.z]); - normals.push([1.0, 1.0, 1.0]); - tangents.push([0.0, 0.0, 0.0, 1.0]); - - uvs.push([vert.uv.u, vert.uv.v]); - } - - let indices = bevy::render::mesh::Indices::U16( - mesh.faces.iter().flat_map(|f| *f).collect() - ); - - bevy_mesh.set_indices(Some(indices)); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); - bevy_mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); - - // Get base matrix - let base_matrix = transforms - .iter() - .find(|t| t.get_name().eq(mesh.get_parent())) - .and_then(|p| Some(map_matrix(p.get_world_xfm()))) - .unwrap_or(map_matrix(mesh.get_world_xfm())); - - // Translate to bevy coordinate system - let matrix = Mat4::from_cols_array(&[ - -1.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.0, 1.0, - ]); - - let mat = mats - .iter() - .find(|m| m.get_name().eq(&mesh.mat)); - - if mat.is_none() { - warn!("Mat not found for \"{}\"", &mesh.mat); - } else { - let mat = mat.unwrap(); - if !mat.diffuse_tex.is_empty() && tex_map.get(mat.diffuse_tex.as_str()).is_none() { - warn!("Diffuse tex not found for \"{}\"", &mat.diffuse_tex); - } - } - - let bevy_mat = match mat { - Some(mat) => StandardMaterial { - base_color: Color::rgba( - mat.color.r, - mat.color.g, - mat.color.b, - mat.alpha, - ), - double_sided: true, - unlit: true, - base_color_texture: match tex_map.get(mat.diffuse_tex.as_str()) { - Some(texture) - => Some(bevy_textures.add(texture.to_owned())), - None => None, - }, - // TODO: Add extra texture maps - normal_map_texture: match tex_map.get(mat.normal_map.as_str()) { - Some(texture) - => Some(bevy_textures.add(texture.to_owned())), - None => None, - }, - emissive_texture: match tex_map.get(mat.emissive_map.as_str()) { - Some(texture) - => Some(bevy_textures.add(texture.to_owned())), - None => None, - }, - ..Default::default() - }, - None => StandardMaterial { - base_color: Color::rgb(0.3, 0.5, 0.3), - double_sided: true, - unlit: false, - ..Default::default() - }, - }; - - // Add mesh - commands.spawn(PbrBundle { - mesh: bevy_meshes.add(bevy_mesh), - material: materials.add(bevy_mat), - transform: Transform::from_matrix(base_matrix) - * Transform::from_matrix(matrix) - * Transform::from_scale(Vec3::new(0.1, 0.1, 0.1)), - ..Default::default() - }); - - debug!("Added {}", &mesh.name); - } -} - -fn get_transform(trans: &T) -> &dyn Trans { - trans -} - -pub fn map_matrix(m: &Matrix) -> Mat4 { - Mat4::from_cols_array(&[ - m.m11, - m.m12, - m.m13, - m.m14, - m.m21, - m.m22, - m.m23, - m.m24, - m.m31, - m.m32, - m.m33, - m.m34, - m.m41, - m.m42, - m.m43, - m.m44, - ]) -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/settings.rs b/apps/ui/preview_ui/src/settings.rs deleted file mode 100644 index 30fe11de..00000000 --- a/apps/ui/preview_ui/src/settings.rs +++ /dev/null @@ -1,100 +0,0 @@ -use bevy::prelude::*; -use serde::{Deserialize, Serialize}; -use log::error; -use std::fmt::{self, Display, Formatter}; -use std::fs::{read_to_string, self}; -use std::path::Path; - -#[derive(Debug, Deserialize, Serialize)] -pub struct GamePath { - pub path: String, - pub game: Game, - pub platform: Platform, -} - -#[derive(Debug, Deserialize, Serialize)] -pub enum Game { - Unknown, - TBRB, - GDRB -} - -#[derive(Debug, Deserialize, Serialize)] -pub enum Platform { - X360, - PS3, - Wii -} - -impl Display for Game { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl Display for Platform { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -#[derive(Debug, Deserialize, Resource, Serialize)] -pub struct AppSettings { - pub show_controls: bool, - pub game_paths: Vec, - pub window_width: f32, - pub window_height: f32, - pub maximized: bool, - pub show_gridlines: bool, - pub show_wireframes: bool, -} - -impl Default for AppSettings { - fn default() -> Self { - AppSettings { - show_controls: true, - game_paths: Vec::new(), - window_width: 1280.0, - window_height: 720.0, - maximized: false, - show_gridlines: true, - show_wireframes: false, - } - } -} - -impl AppSettings { - pub fn load_from_file(json_path: T) -> Self where T: AsRef { - let json_path = json_path.as_ref(); - - if !json_path.exists() { - return AppSettings::default(); - } - - let json_text = read_to_string(json_path); - - if let Ok(text) = json_text { - let settings = serde_json::from_str::(&text); - - if let Err(err) = &settings { - error!("Unable to parse settings: {:?}", err); - } - - return settings.unwrap_or_default(); - } - - AppSettings::default() - } - - pub fn save_to_file(&self, json_path: T) where T: AsRef { - #[cfg(not(target_family = "wasm"))] { - let json_path = json_path.as_ref(); - - // TODO: Check if directory exists and check for error - let json_text = serde_json::to_string_pretty(&self).unwrap(); - - fs::write(json_path, json_text) - .expect("Error writing settings to file"); - } - } -} \ No newline at end of file diff --git a/apps/ui/preview_ui/src/state.rs b/apps/ui/preview_ui/src/state.rs deleted file mode 100644 index e4a36b66..00000000 --- a/apps/ui/preview_ui/src/state.rs +++ /dev/null @@ -1,139 +0,0 @@ -use super::{AppSettings, AppEvent}; -use bevy::prelude::*; -use grim::*; -use grim::ark::{Ark, ArkOffsetEntry}; -use grim::scene::*; -use itertools::Itertools; -use log::debug; -use std::{env::args, path::{Path, PathBuf}}; - -type ConsumeEventFn = fn(AppEvent); - -#[derive(Default)] -pub struct MiloView { - pub filter: String, - pub class_filter: Option, - pub selected_entry: Option, -} - -#[derive(Default, Resource)] -pub struct AppState { - pub ark: Option, - pub root: Option, - pub system_info: Option, - pub milo: Option, - pub open_file_path: Option, - pub settings_path: PathBuf, - pub show_options: bool, - pub pending_events: Vec, - pub side_bar_tab_index: usize, - pub milo_view: MiloView, - pub vert_count: usize, - pub face_count: usize, -} - -impl AppState { - pub fn save_settings(&self, settings: &AppSettings) { - settings.save_to_file(&self.settings_path); - debug!("Saved settings to \"{}\"", &self.settings_path.to_str().unwrap()); - } - - pub fn consume_events(&mut self, mut callback: impl FnMut(AppEvent)) { - while !self.pending_events.is_empty() { - callback(self.pending_events.remove(0)); - } - } - - pub fn add_event(&mut self, ev: AppEvent) { - self.pending_events.push(ev); - } -} - -#[derive(Debug)] -pub struct ArkDirNode { - pub name: String, - pub path: String, - pub dirs: Vec, - pub files: Vec, - pub loaded: bool, -} - -impl ArkDirNode { - pub fn expand(&mut self, ark: &Ark) { - if self.loaded { - return; - } - - let (mut dirs, mut files) = get_dirs_and_files(&self.path, ark); - self.dirs.append(&mut dirs); - self.files.append(&mut files); - self.loaded = true; - - // TODO: Rely on lazy load - for c in &mut self.dirs { - c.expand(ark); - } - } -} - -fn get_dirs_and_files(dir: &str, ark: &Ark) -> (Vec, Vec) { - let is_root = match dir { - "" | "." => true, - _ => false, - }; - - if is_root { - let files = ark.entries - .iter() - .enumerate() - .filter(|(_i, e)| !e.path.contains('/') - || (e.path.starts_with("./") && e.path.matches(|c: char | c.eq(&'/')).count() == 1)) - .map(|(i, _)| i) - .collect::>(); - - let dirs = ark.entries - .iter() - .filter(|e| e.path.contains('/')) - .map(|e| e.path.split('/').next().unwrap()) - .unique() - .filter(|s| !s.eq(&".")) - .map(|s| ArkDirNode { - name: s.to_owned(), - path: s.to_owned(), - dirs: Vec::new(), - files: Vec::new(), - loaded: false, - }) - .collect::>(); - - return (dirs, files); - } - - let dir_path = format!["{}/", dir]; - let slash_count = dir_path.matches(|c: char| c.eq(&'/')).count(); - - let files = ark.entries - .iter() - .enumerate() - .filter(|(_i, e)| e.path.starts_with(&dir_path) - && e.path.matches(|c: char| c.eq(&'/')).count() == slash_count) - .map(|(i, _)| i) - .collect::>(); - - let dirs = ark.entries - .iter() - .filter(|e| e.path.starts_with(&dir_path) - && e.path.matches(|c: char| c.eq(&'/')).count() > slash_count) - .map(|e| e.path.split('/').nth(slash_count).unwrap()) - .unique() - .map(|s| ArkDirNode { - name: s.to_owned(), - path: format!("{}{}", dir_path, s), - dirs: Vec::new(), - files: Vec::new(), - loaded: false, - }) - .collect::>(); - - (dirs, files) -} \ No newline at end of file diff --git a/core/grim/Cargo.toml b/core/grim/Cargo.toml index 85257f06..1919f2c5 100644 --- a/core/grim/Cargo.toml +++ b/core/grim/Cargo.toml @@ -5,17 +5,17 @@ authors.workspace = true edition.workspace = true [dependencies] -base64 = "0.21.2" -bitstream-io = { version = "1.7.0", optional = true } -ffmpeg-next = { version = "6.0.0", optional = true } -flate2 = "1.0.26" +base64 = "0.21.7" +bitstream-io = { version = "2.2.0", optional = true } +flate2 = "1.0.28" fon = { version = "0.6.0", optional = true } -gltf = { version = "1.2.0", optional = true, features = [ "extras", "import", "names" ] } -gltf-json = { version = "1.2.0", optional = true, features = [ "names" ] } +gltf = { workspace = true, optional = true } +gltf-json = { workspace = true, optional = true } +grim_gltf = { path = "../grim_gltf", optional = true } grim_macros = { path = "../grim_macros" } grim_traits = { path = "../grim_traits" } half = { version = "2.3.1", default-features = false } -image = { version = "0.24.6", default-features = false, features = [ "dxt", "jpeg", "png" ] } +image = { version = "0.24.8", default-features = false, features = [ "dxt", "png" ] } itertools = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } @@ -24,21 +24,21 @@ nalgebra = { version = "0.32.3", optional = true } nom = "7.1.3" # pyo3 = { version = "0.17.3", optional = true, features = [ "extension-module" ] } pyo3 = { git = "https://github.com/PyO3/pyo3", branch = "cfg-feature-pyo3", optional = true, features = [ "extension-module" ] } -rayon = "1.7.0" -regex = { version = "1.9.1", default-features = false, features = [ "std" ] } +rayon = "1.8.0" +regex = { version = "1.10.2", default-features = false, features = [ "std" ] } serde = { optional = true, workspace = true } thiserror = { workspace = true } wav = { version = "1.0.0", optional = true } [dev-dependencies] criterion = "0.5.1" -rstest = "0.18.1" +rstest = "0.18.2" [features] audio = [ "bitstream-io", "fon", "wav" ] -audio_experimental = [ "ffmpeg-next" ] +audio_experimental = [] midi = [ "midly" ] -model = [ "gltf", "gltf-json", "nalgebra", "serde" ] +model = [ "gltf", "gltf-json", "grim_gltf", "nalgebra", "serde" ] python = [ "pyo3" ] [[bench]] diff --git a/core/grim/src/model/export.rs b/core/grim/src/model/export.rs index a59806b8..8d590ca6 100644 --- a/core/grim/src/model/export.rs +++ b/core/grim/src/model/export.rs @@ -5,6 +5,7 @@ use crate::{Platform, SystemInfo}; use grim_traits::scene::Group; use itertools::*; use gltf_json as json; +use grim_gltf::*; use nalgebra as na; use serde::ser::Serialize; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -89,7 +90,7 @@ fn map_bones_to_nodes(dir_name: &str, bones: &Vec) -> Vec, bones: &Vec) None }, extensions: None, - extras: None, + extras: Default::default(), matrix: if mat.is_identity(f32::EPSILON) { // Don't add identities None @@ -254,7 +255,7 @@ pub fn export_object_dir_to_gltf(obj_dir: &ObjectDir, output_path: &Path, sys_in Some(str_data) }, extensions: None, - extras: None + extras: Default::default() }) .collect(); @@ -278,7 +279,7 @@ pub fn export_object_dir_to_gltf(obj_dir: &ObjectDir, output_path: &Path, sys_in scenes: vec![ json::Scene { extensions: None, - extras: None, + extras: Default::default(), name: None, nodes: vec![json::Index::new(0)], } @@ -286,7 +287,7 @@ pub fn export_object_dir_to_gltf(obj_dir: &ObjectDir, output_path: &Path, sys_in skins: vec![ json::Skin { extensions: None, - extras: None, + extras: Default::default(), inverse_bind_matrices: None, joints: joints, name: None, @@ -629,7 +630,7 @@ impl GltfExporter { camera: None, children: None, extensions: None, - extras: None, + extras: Default::default(), matrix: None, mesh: None, name: Some(name.to_owned()), @@ -837,7 +838,7 @@ impl GltfExporter { } }, extensions: None, - extras: None + extras: Default::default() }; let texture = json::Texture { @@ -845,7 +846,7 @@ impl GltfExporter { sampler: Some(json::Index::new(0u32)), source: json::Index::new(i as u32), // Image index extensions: None, - extras: None + extras: Default::default() }; (image, texture) @@ -882,7 +883,7 @@ impl GltfExporter { index: json::Index::new(*d as u32), tex_coord: 0, extensions: None, - extras: None + extras: Default::default() }), //base_color_factor: ..Default::default() @@ -937,7 +938,7 @@ impl GltfExporter { skins.push(json::Skin { extensions: None, - extras: None, + extras: Default::default(), inverse_bind_matrices: ibm_idx .map(|i| json::Index::new(i as u32)), joints: joints @@ -1067,7 +1068,7 @@ impl GltfExporter { byte_length: total_size as u32, uri: Some(str_data), extensions: None, - extras: None + extras: Default::default() } }]; @@ -1080,7 +1081,7 @@ impl GltfExporter { buffer: json::Index::new(0), target: None, extensions: None, - extras: None + extras: Default::default() }, json::buffer::View { name: Some(String::from("uvs")), @@ -1090,7 +1091,7 @@ impl GltfExporter { buffer: json::Index::new(0), target: None, extensions: None, - extras: None + extras: Default::default() }, json::buffer::View { name: Some(String::from("weights_tans")), @@ -1100,7 +1101,7 @@ impl GltfExporter { buffer: json::Index::new(0), target: None, extensions: None, - extras: None + extras: Default::default() }, json::buffer::View { name: Some(String::from("faces")), @@ -1110,7 +1111,7 @@ impl GltfExporter { buffer: json::Index::new(0), target: None, extensions: None, - extras: None + extras: Default::default() } ]; } @@ -1373,12 +1374,12 @@ impl GltfExporter { .map(|idx| json::Index::new(*idx as u32)), mode: json::validation::Checked::Valid(gltf::mesh::Mode::Triangles), targets: None, - extras: None, + extras: Default::default(), extensions: None }, ], weights: None, - extras: None, + extras: Default::default(), extensions: None }); @@ -1491,7 +1492,7 @@ impl GltfExporter { gltf.scenes = vec![ json::Scene { extensions: None, - extras: None, + extras: Default::default(), name: None, nodes: scene_nodes .into_iter() @@ -1522,7 +1523,7 @@ impl GltfExporter { scenes: vec![ json::Scene { extensions: None, - extras: None, + extras: Default::default(), name: None, nodes: vec![json::Index::new(0)], } @@ -1530,7 +1531,7 @@ impl GltfExporter { skins: vec![ json::Skin { extensions: None, - extras: None, + extras: Default::default(), inverse_bind_matrices: None, joints: joints, name: None, @@ -1721,10 +1722,10 @@ impl GltfExporter { node: json::Index::new(node_idx as u32), path: json::validation::Checked::Valid(json::animation::Property::Translation), extensions: None, - extras: None + extras: Default::default() }, extensions: None, - extras: None + extras: Default::default() }); samplers.push(json::animation::Sampler { @@ -1732,7 +1733,7 @@ impl GltfExporter { output: json::Index::new(output_idx as u32), interpolation: json::validation::Checked::Valid(json::animation::Interpolation::Linear), extensions: None, - extras: None + extras: Default::default() }); } @@ -1754,10 +1755,10 @@ impl GltfExporter { node: json::Index::new(node_idx as u32), path: json::validation::Checked::Valid(json::animation::Property::Rotation), extensions: None, - extras: None + extras: Default::default() }, extensions: None, - extras: None + extras: Default::default() }); samplers.push(json::animation::Sampler { @@ -1765,7 +1766,7 @@ impl GltfExporter { output: json::Index::new(output_idx as u32), interpolation: json::validation::Checked::Valid(json::animation::Interpolation::Linear), extensions: None, - extras: None + extras: Default::default() }); } @@ -1787,10 +1788,10 @@ impl GltfExporter { node: json::Index::new(node_idx as u32), path: json::validation::Checked::Valid(json::animation::Property::Scale), extensions: None, - extras: None + extras: Default::default() }, extensions: None, - extras: None + extras: Default::default() }); samplers.push(json::animation::Sampler { @@ -1798,7 +1799,7 @@ impl GltfExporter { output: json::Index::new(output_idx as u32), interpolation: json::validation::Checked::Valid(json::animation::Interpolation::Linear), extensions: None, - extras: None + extras: Default::default() }); } } @@ -1814,7 +1815,7 @@ impl GltfExporter { channels, samplers, extensions: None, - extras: None + extras: Default::default() }); } @@ -1951,10 +1952,10 @@ impl GltfExporter { node: json::Index::new(node_idx as u32), path: json::validation::Checked::Valid(json::animation::Property::Translation), extensions: None, - extras: None + extras: Default::default() }, extensions: None, - extras: None + extras: Default::default() }); samplers.push(json::animation::Sampler { @@ -1962,7 +1963,7 @@ impl GltfExporter { output: json::Index::new(output_idx as u32), interpolation: json::validation::Checked::Valid(json::animation::Interpolation::Linear), extensions: None, - extras: None + extras: Default::default() });*/ const FPS: f32 = 1. / 30.; @@ -1990,10 +1991,10 @@ impl GltfExporter { node: json::Index::new(node_idx as u32), path: json::validation::Checked::Valid(json::animation::Property::Translation), extensions: None, - extras: None + extras: Default::default() }, extensions: None, - extras: None + extras: Default::default() }); samplers.push(json::animation::Sampler { @@ -2001,7 +2002,7 @@ impl GltfExporter { output: json::Index::new(output_idx as u32), interpolation: json::validation::Checked::Valid(json::animation::Interpolation::Linear), extensions: None, - extras: None + extras: Default::default() }); } else { // Add empty pos sample @@ -2025,10 +2026,10 @@ impl GltfExporter { node: json::Index::new(node_idx as u32), path: json::validation::Checked::Valid(json::animation::Property::Translation), extensions: None, - extras: None + extras: Default::default() }, extensions: None, - extras: None + extras: Default::default() }); samplers.push(json::animation::Sampler { @@ -2036,7 +2037,7 @@ impl GltfExporter { output: json::Index::new(output_idx as u32), interpolation: json::validation::Checked::Valid(json::animation::Interpolation::Linear), extensions: None, - extras: None + extras: Default::default() }); } @@ -2150,10 +2151,10 @@ impl GltfExporter { node: json::Index::new(node_idx as u32), path: json::validation::Checked::Valid(json::animation::Property::Rotation), extensions: None, - extras: None + extras: Default::default() }, extensions: None, - extras: None + extras: Default::default() }); samplers.push(json::animation::Sampler { @@ -2161,7 +2162,7 @@ impl GltfExporter { output: json::Index::new(output_idx as u32), interpolation: json::validation::Checked::Valid(json::animation::Interpolation::Linear), extensions: None, - extras: None + extras: Default::default() }); } @@ -2178,7 +2179,7 @@ impl GltfExporter { channels, samplers, extensions: None, - extras: None + extras: Default::default() }); } @@ -2186,10 +2187,6 @@ impl GltfExporter { } } -fn align_to_multiple_of_four(n: usize) -> usize { - (n + 3) & !3 -} - fn decompose_trs(mat: na::Matrix4) -> (na::Vector3, na::UnitQuaternion, na::Vector3) { // Decompose matrix to T*R*S let translate = mat.column(3).xyz(); @@ -2234,268 +2231,6 @@ fn decompose_trs_with_milo_coords(mut mat: na::Matrix4) -> (na::Vector3)>, - accessors: Vec, -} - -impl AccessorBuilder { - fn new() -> AccessorBuilder { - AccessorBuilder { - working_data: Default::default(), - accessors: Vec::new() - } - } - - fn calc_stride(&self) -> usize { - N * T::size() - } - - fn update_buffer_view(&mut self, mut data: Vec) -> (usize, usize) { - let stride = self.calc_stride::(); - let data_size = data.len(); - let next_idx = self.working_data.len(); - - // Upsert buffer data - let (idx, buff) = self.working_data - .entry(stride) - .and_modify(|(_, b)| b.append(&mut data)) - .or_insert_with(|| (next_idx, data)); - - // Return index of updated buffer view + insert offset - (*idx, buff.len() - data_size) - } - - pub fn add_scalar, T: ComponentValue, U: IntoIterator>(&mut self, name: S, data: U) -> Option { - // Map to iter of single-item arrays (definitely hacky) - self.add_array(name, data.into_iter().map(|d| [d])) - } - - pub fn add_array, T: ComponentValue, U: IntoIterator, V: Into<[T; N]>>(&mut self, name: S, data: U) -> Option { - let comp_type = T::get_component_type(); - - let acc_type = match N { - 1 => json::accessor::Type::Scalar, - 2 => json::accessor::Type::Vec2, - 3 => json::accessor::Type::Vec3, - 4 => json::accessor::Type::Vec4, - 9 => json::accessor::Type::Mat3, - 16 => json::accessor::Type::Mat4, - _ => unimplemented!() - }; - - // Write to stream and find min/max values - let mut data_stream = Vec::new(); - let (count, min, max) = data - .into_iter() - .fold((0usize, [T::max(); N], [T::min(); N]), |(count, mut min, mut max), item| { - let mut i = 0; - for v in item.into() { - // Encode + append each value to master buffer - data_stream.append(&mut v.encode()); - - // Calc min + max values - min[i] = min[i].get_min(v); - max[i] = max[i].get_max(v); - - i += 1; - } - - (count + 1, min, max) - }); - - if count == 0 { - // If count is 0, don't bother adding - return None; - } - - // Update buffer views - let (buff_idx, buff_off) = self.update_buffer_view::(data_stream); - - let acc_index = self.accessors.len(); - - let (min_value, max_value) = Self::get_min_max_values( - &acc_type, - min, - max - ).unwrap(); - - // Create accessor - let accessor = json::Accessor { - buffer_view: Some(json::Index::new(buff_idx as u32)), - byte_offset: buff_off as u32, - count: count as u32, - component_type: json::validation::Checked::Valid(json::accessor::GenericComponentType(comp_type)), - extensions: None, - extras: None, - type_: json::validation::Checked::Valid(acc_type), - min: Some(min_value), - max: Some(max_value), - name: match name.into() { - s if !s.is_empty() => Some(s), - _ => None - }, - normalized: false, - sparse: None - }; - - self.accessors.push(accessor); - Some(acc_index) - } - - fn generate_buffer_views(&mut self) -> (Vec, Vec) { - // Get view info and sort by assigned index - let view_data = self.working_data - .drain() - .map(|(k, (idx, data))| (idx, k, data)) // (idx, stride, data) - .sorted_by(|(a, ..), (b, ..)| a.cmp(b)); - - let mut views = Vec::new(); - let mut all_data = Vec::new(); - - for (_idx, stride, mut data) in view_data { - // Pad buffer view if required - let padded_size = align_to_multiple_of_four(data.len()); - if padded_size > data.len() { - let diff_size = padded_size - data.len(); - data.append(&mut vec![0u8; diff_size]); - } - - let data_size = data.len(); - let data_offset = all_data.len(); - - // Move data from view to full buffer - all_data.append(&mut data); - - views.push(json::buffer::View { - name: None, - byte_length: data_size as u32, - byte_offset: Some(data_offset as u32), - byte_stride: match stride { - 64 => None, // Hacky way to disable writing stride for inverse bind transforms - s if s % 4 == 0 => Some(stride as u32), - _ => None // Don't encode if not multiple - }, - buffer: json::Index::new(0), - target: None, - extensions: None, - extras: None - }); - } - - (views, all_data) - } - - fn generate>(mut self, name: T) -> (Vec, Vec, json::Buffer, Vec) { - // Generate buffer views + final buffer blob - let (views, buffer_data) = self.generate_buffer_views(); - - // Create buffer json - let buffer = json::Buffer { - name: None, - byte_length: buffer_data.len() as u32, - uri: match name.into() { - s if !s.is_empty() => Some(s), - _ => None - }, - extensions: None, - extras: None - }; - - // Return everything - (self.accessors, - views, - buffer, - buffer_data) - } - - fn get_min_max_values(acc_type: &json::accessor::Type, min: [T; N], max: [T; N]) -> Option<(json::Value, json::Value)> { - let result = match acc_type { - json::accessor::Type::Scalar => ( - json::serialize::to_value([min.iter().fold(T::max(), |acc, m| acc.get_min(*m))]), - json::serialize::to_value([max.iter().fold(T::min(), |acc, m| acc.get_max(*m))]), - ), - _ => ( - json::serialize::to_value(min.to_vec()), - json::serialize::to_value(max.to_vec()), - ), - }; - - match result { - (Ok(min), Ok(max)) => Some((min, max)), - _ => None - } - } -} - -trait ComponentValue : Copy + Serialize { - fn min() -> Self; - fn max() -> Self; - - fn get_min(self, other: Self) -> Self; - fn get_max(self, other: Self) -> Self; - - fn encode(self) -> Vec; - fn get_component_type() -> json::accessor::ComponentType; - - fn size() -> usize { - std::mem::size_of::() - } -} - -impl ComponentValue for u16 { - fn min() -> Self { - u16::MIN - } - - fn max() -> Self { - u16::MAX - } - - fn get_min(self, other: Self) -> Self { - std::cmp::min(self, other) - } - - fn get_max(self, other: Self) -> Self { - std::cmp::max(self, other) - } - - fn encode(self) -> Vec { - self.to_le_bytes().to_vec() - } - - fn get_component_type() -> json::accessor::ComponentType { - json::accessor::ComponentType::U16 - } -} - -impl ComponentValue for f32 { - fn min() -> Self { - f32::MIN - } - - fn max() -> Self { - f32::MAX - } - - fn get_min(self, other: Self) -> Self { - f32::min(self, other) - } - - fn get_max(self, other: Self) -> Self { - f32::max(self, other) - } - - fn encode(self) -> Vec { - self.to_le_bytes().to_vec() - } - - fn get_component_type() -> json::accessor::ComponentType { - json::accessor::ComponentType::F32 - } -} - #[cfg(test)] mod tests { use rstest::*; diff --git a/core/grim/src/model/gltf.rs b/core/grim/src/model/gltf.rs index 0e0d1411..969bc225 100644 --- a/core/grim/src/model/gltf.rs +++ b/core/grim/src/model/gltf.rs @@ -68,6 +68,30 @@ impl GLTFImporter { for mesh in meshes.iter_mut() { mesh.parent = group.name.to_owned(); group.objects.push(mesh.name.to_owned()); + + // Add empty material for mesh + if mesh.mat.is_empty() { + let mesh_base_name = &mesh + .name + .to_lowercase() + .replace(".mesh", ""); + + let mat_name = format!("{mesh_base_name}_material.mat"); + + // Update mat name in mesh + mesh.mat = mat_name.to_owned(); + + // Use default material + let mat = MatObject { + name: mat_name, + blend: Blend::kBlendSrcAlpha, + z_mode: ZMode::kZModeNormal, + prelit: false, + ..Default::default() + }; + + self.mats.push(mat); + } } // Add meshes to asset manager @@ -204,6 +228,7 @@ impl GLTFImporter { name: mat_name, blend: Blend::kBlendSrcAlpha, z_mode: ZMode::kZModeNormal, + prelit: false, ..Default::default() }; @@ -339,17 +364,22 @@ impl GLTFImporter { ]) .collect(); + // TODO: Figure out what happens if normals are missing... + let positions = reader.read_positions().unwrap(); + let normals = reader.read_normals().unwrap(); + let uvs = reader.read_tex_coords(0) + .map(|tc| tc.into_f32() + .collect::>()) + .unwrap_or_else(|| (0..positions.len()).map(|_| Default::default()).collect::>()); + let verts_interleaved = izip!( - reader.read_positions().unwrap(), - reader.read_normals().unwrap(), - //reader.read_colors(0).unwrap().into_rgb_f32().into_iter(), - //reader.read_tex_coords(0).unwrap().into_f32(), - reader.read_tex_coords(0) // Hacky way to get tex coords or default if none found - .map(|tc| tc.into_f32() - .collect::>()) - .unwrap_or_default() + positions, + normals, + uvs, ); + println!("Found {} verts!", verts_interleaved.len()); + let verts = verts_interleaved .map(|(pos, norm, uv)| Vert { pos: Vector4 { diff --git a/core/grim_gltf/Cargo.toml b/core/grim_gltf/Cargo.toml new file mode 100644 index 00000000..ea6837e8 --- /dev/null +++ b/core/grim_gltf/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "grim_gltf" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +gltf = { workspace = true } +gltf-json = { workspace = true } +itertools = { workspace = true } +serde = { workspace = true } \ No newline at end of file diff --git a/core/grim_gltf/src/lib.rs b/core/grim_gltf/src/lib.rs new file mode 100644 index 00000000..6fc3b429 --- /dev/null +++ b/core/grim_gltf/src/lib.rs @@ -0,0 +1,270 @@ +use gltf_json as json; +use itertools::*; +use serde::ser::Serialize; +use std::collections::{HashMap}; + +pub struct AccessorBuilder { + // Key = stride, Value = (idx, data) + working_data: HashMap)>, + accessors: Vec, +} + +impl AccessorBuilder { + pub fn new() -> AccessorBuilder { + AccessorBuilder { + working_data: Default::default(), + accessors: Vec::new() + } + } + + fn calc_stride(&self) -> usize { + N * T::size() + } + + fn update_buffer_view(&mut self, mut data: Vec) -> (usize, usize) { + let stride = self.calc_stride::(); + let data_size = data.len(); + let next_idx = self.working_data.len(); + + // Upsert buffer data + let (idx, buff) = self.working_data + .entry(stride) + .and_modify(|(_, b)| b.append(&mut data)) + .or_insert_with(|| (next_idx, data)); + + // Return index of updated buffer view + insert offset + (*idx, buff.len() - data_size) + } + + pub fn add_scalar, T: ComponentValue, U: IntoIterator>(&mut self, name: S, data: U) -> Option { + // Map to iter of single-item arrays (definitely hacky) + self.add_array(name, data.into_iter().map(|d| [d])) + } + + pub fn add_array, T: ComponentValue, U: IntoIterator, V: Into<[T; N]>>(&mut self, name: S, data: U) -> Option { + let comp_type = T::get_component_type(); + + let acc_type = match N { + 1 => json::accessor::Type::Scalar, + 2 => json::accessor::Type::Vec2, + 3 => json::accessor::Type::Vec3, + 4 => json::accessor::Type::Vec4, + 9 => json::accessor::Type::Mat3, + 16 => json::accessor::Type::Mat4, + _ => unimplemented!() + }; + + // Write to stream and find min/max values + let mut data_stream = Vec::new(); + let (count, min, max) = data + .into_iter() + .fold((0usize, [T::max(); N], [T::min(); N]), |(count, mut min, mut max), item| { + let mut i = 0; + for v in item.into() { + // Encode + append each value to master buffer + data_stream.append(&mut v.encode()); + + // Calc min + max values + min[i] = min[i].get_min(v); + max[i] = max[i].get_max(v); + + i += 1; + } + + (count + 1, min, max) + }); + + if count == 0 { + // If count is 0, don't bother adding + return None; + } + + // Update buffer views + let (buff_idx, buff_off) = self.update_buffer_view::(data_stream); + + let acc_index = self.accessors.len(); + + let (min_value, max_value) = Self::get_min_max_values( + &acc_type, + min, + max + ).unwrap(); + + // Create accessor + let accessor = json::Accessor { + buffer_view: Some(json::Index::new(buff_idx as u32)), + byte_offset: Some(buff_off as u32), + count: count as u32, + component_type: json::validation::Checked::Valid(json::accessor::GenericComponentType(comp_type)), + extensions: None, + extras: Default::default(), + type_: json::validation::Checked::Valid(acc_type), + min: Some(min_value), + max: Some(max_value), + name: match name.into() { + s if !s.is_empty() => Some(s), + _ => None + }, + normalized: false, + sparse: None + }; + + self.accessors.push(accessor); + Some(acc_index) + } + + fn generate_buffer_views(&mut self) -> (Vec, Vec) { + // Get view info and sort by assigned index + let view_data = self.working_data + .drain() + .map(|(k, (idx, data))| (idx, k, data)) // (idx, stride, data) + .sorted_by(|(a, ..), (b, ..)| a.cmp(b)); + + let mut views = Vec::new(); + let mut all_data = Vec::new(); + + for (_idx, stride, mut data) in view_data { + // Pad buffer view if required + let padded_size = align_to_multiple_of_four(data.len()); + if padded_size > data.len() { + let diff_size = padded_size - data.len(); + data.append(&mut vec![0u8; diff_size]); + } + + let data_size = data.len(); + let data_offset = all_data.len(); + + // Move data from view to full buffer + all_data.append(&mut data); + + views.push(json::buffer::View { + name: None, + byte_length: data_size as u32, + byte_offset: Some(data_offset as u32), + byte_stride: match stride { + 64 => None, // Hacky way to disable writing stride for inverse bind transforms + s if s % 4 == 0 => Some(stride as u32), + _ => None // Don't encode if not multiple + }, + buffer: json::Index::new(0), + target: None, + extensions: None, + extras: Default::default() + }); + } + + (views, all_data) + } + + pub fn generate>(mut self, name: T) -> (Vec, Vec, json::Buffer, Vec) { + // Generate buffer views + final buffer blob + let (views, buffer_data) = self.generate_buffer_views(); + + // Create buffer json + let buffer = json::Buffer { + name: None, + byte_length: buffer_data.len() as u32, + uri: match name.into() { + s if !s.is_empty() => Some(s), + _ => None + }, + extensions: None, + extras: Default::default() + }; + + // Return everything + (self.accessors, + views, + buffer, + buffer_data) + } + + fn get_min_max_values(acc_type: &json::accessor::Type, min: [T; N], max: [T; N]) -> Option<(json::Value, json::Value)> { + let result = match acc_type { + json::accessor::Type::Scalar => ( + json::serialize::to_value([min.iter().fold(T::max(), |acc, m| acc.get_min(*m))]), + json::serialize::to_value([max.iter().fold(T::min(), |acc, m| acc.get_max(*m))]), + ), + _ => ( + json::serialize::to_value(min.to_vec()), + json::serialize::to_value(max.to_vec()), + ), + }; + + match result { + (Ok(min), Ok(max)) => Some((min, max)), + _ => None + } + } +} + +pub trait ComponentValue : Copy + Serialize { + fn min() -> Self; + fn max() -> Self; + + fn get_min(self, other: Self) -> Self; + fn get_max(self, other: Self) -> Self; + + fn encode(self) -> Vec; + fn get_component_type() -> json::accessor::ComponentType; + + fn size() -> usize { + std::mem::size_of::() + } +} + +impl ComponentValue for u16 { + fn min() -> Self { + u16::MIN + } + + fn max() -> Self { + u16::MAX + } + + fn get_min(self, other: Self) -> Self { + std::cmp::min(self, other) + } + + fn get_max(self, other: Self) -> Self { + std::cmp::max(self, other) + } + + fn encode(self) -> Vec { + self.to_le_bytes().to_vec() + } + + fn get_component_type() -> json::accessor::ComponentType { + json::accessor::ComponentType::U16 + } +} + +impl ComponentValue for f32 { + fn min() -> Self { + f32::MIN + } + + fn max() -> Self { + f32::MAX + } + + fn get_min(self, other: Self) -> Self { + f32::min(self, other) + } + + fn get_max(self, other: Self) -> Self { + f32::max(self, other) + } + + fn encode(self) -> Vec { + self.to_le_bytes().to_vec() + } + + fn get_component_type() -> json::accessor::ComponentType { + json::accessor::ComponentType::F32 + } +} + +fn align_to_multiple_of_four(n: usize) -> usize { + (n + 3) & !3 +} \ No newline at end of file diff --git a/core/grim_macros/Cargo.toml b/core/grim_macros/Cargo.toml index a2bb4093..1b3f25eb 100644 --- a/core/grim_macros/Cargo.toml +++ b/core/grim_macros/Cargo.toml @@ -10,6 +10,6 @@ proc-macro = true [dependencies] grim_traits = { path = "../grim_traits" } lazy_static = { workspace = true } -proc-macro2 = "1.0.56" -quote = "1.0.26" -syn = { version = "1.0.109", default-features = false, features = [ "clone-impls", "derive", "parsing", "printing", "proc-macro" ] } +proc-macro2 = "1.0.76" +quote = "1.0.35" +syn = { version = "2.0.48", default-features = false, features = [ "clone-impls", "derive", "parsing", "printing", "proc-macro" ] } diff --git a/core/grim_macros/src/common.rs b/core/grim_macros/src/common.rs index c397f9d8..d07ade09 100644 --- a/core/grim_macros/src/common.rs +++ b/core/grim_macros/src/common.rs @@ -1,6 +1,9 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{AttributeArgs, DeriveInput, Meta, MetaList, NestedMeta, Path, parse::Parser, parse_macro_input}; +use syn::{Attribute, DeriveInput, Meta, Path, parse::Parser, punctuated::Punctuated, Token}; + +/* +TODO: Finish migrating from v1 pub fn get_meta_list(args: &Vec) -> Vec { let mut metas = Vec::new(); @@ -17,18 +20,23 @@ pub fn get_meta_list(args: &Vec) -> Vec { } metas -} +} */ -pub fn get_meta_paths(args: &Vec) -> Vec { +pub fn get_meta_paths(args: &Vec) -> Vec { let mut paths = Vec::new(); for arg in args { - if let NestedMeta::Meta(meta) = arg { - if let Meta::Path(path) = meta { - paths.push(path.to_owned()); - } else { - panic!() - } + //arg.parse_nested_meta(logic) + let nested_meta = arg.parse_args_with(Punctuated::::parse_terminated); + + let Ok(nested_meta) = nested_meta else { + continue; + }; + + //let nested = arg.parse_nested_meta(|_| Ok(())).unwrap(); // Accept all meta attributes + + for meta in nested_meta { + paths.push(meta.path().clone()); } } diff --git a/core/grim_macros/src/lib.rs b/core/grim_macros/src/lib.rs index be3bc34d..ec6b48b1 100644 --- a/core/grim_macros/src/lib.rs +++ b/core/grim_macros/src/lib.rs @@ -4,7 +4,7 @@ use crate::scene::get_object_tokens; use crate::scene::get_milo_object_tokens; use proc_macro::TokenStream; -use syn::{AttributeArgs, DeriveInput, Meta, MetaList, NestedMeta, parse::Parser, parse_macro_input}; +use syn::{DeriveInput, Meta, parse::Parser, parse_macro_input, punctuated::Punctuated, Token}; use quote::quote; mod common; @@ -75,8 +75,33 @@ pub fn version(_args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn milo(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as AttributeArgs); - let paths = get_meta_paths(&args); + //let args = parse_macro_input!(args as AttributeArgs); + + //syn::Attribute::parse_outer(); + //let args = parse_macro_input!(args with syn::Attribute::parse_outer); + + //Punctuated + + // TODO: Move to struct (Replaces get_meta_paths) + // https://docs.rs/syn/latest/syn/meta/fn.parser.html#example + // https://docs.rs/syn/latest/syn/meta/struct.ParseNestedMeta.html + let mut paths = Vec::new(); + let meta_path_parser = syn::meta::parser(|m| { + paths.push(m.path); + Ok(()) + }); + + //let args = syn::Attribute::parse_outer.parse(args); + parse_macro_input!(args with meta_path_parser); + /*let Ok(args) = args else { + println!("Failed to parse args"); + return input; + };*/ + + + //let paths = syn::Attribute::parse_args_with(Punctuated::::parse_terminated); + + //let paths = get_meta_paths(&args); let mut input = parse_macro_input!(input as DeriveInput); let mut transformed_input = proc_macro2::TokenStream::new(); @@ -108,8 +133,13 @@ pub fn milo(args: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn milo_super(args: TokenStream, input: TokenStream) -> TokenStream { - let args = parse_macro_input!(args as AttributeArgs); - let paths = get_meta_paths(&args); + let mut paths = Vec::new(); + let meta_path_parser = syn::meta::parser(|m| { + paths.push(m.path); + Ok(()) + }); + + parse_macro_input!(args with meta_path_parser); let mut input = parse_macro_input!(input as DeriveInput); let mut transformed_input = proc_macro2::TokenStream::new(); diff --git a/core/grim_macros/src/scene/mod.rs b/core/grim_macros/src/scene/mod.rs index 4ce212e1..def654c2 100644 --- a/core/grim_macros/src/scene/mod.rs +++ b/core/grim_macros/src/scene/mod.rs @@ -12,7 +12,7 @@ pub use milo_object::*; use proc_macro::TokenStream; use quote::quote; use std::collections::HashMap; -use syn::{AttributeArgs, DeriveInput, Meta, MetaList, NestedMeta, Path, parse::Parser, parse_macro_input}; +use syn::{Path, parse_macro_input}; type GetObjectTokensFn = fn() -> ObjectTokens; diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 37046e0d..00000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly-2023-06-11" # 1.72.0 diff --git a/utils/anim_preview/Cargo.toml b/utils/anim_preview/Cargo.toml index d4451a90..b03336d4 100644 --- a/utils/anim_preview/Cargo.toml +++ b/utils/anim_preview/Cargo.toml @@ -8,5 +8,5 @@ edition.workspace = true grim = { workspace = true } keyframe = "1.1.1" nalgebra = "0.32.3" -rerun = { version = "0.7.0", features = [ "native_viewer" ] } -shared = { path = "../shared" } \ No newline at end of file +rerun = { version = "0.12.0", features = [ "native_viewer" ] } +shared = { path = "../shared" } diff --git a/utils/anim_preview/src/main.rs b/utils/anim_preview/src/main.rs index 665b3db2..d14bee09 100644 --- a/utils/anim_preview/src/main.rs +++ b/utils/anim_preview/src/main.rs @@ -17,11 +17,12 @@ use nalgebra as na; use rerun::external::glam; use rerun::{ coordinates::{Handedness, SignedAxis3}, - components::{Arrow3D, ColorRGBA, LineStrip3D, MeshId, Point3D, Quaternion, Radius, RawMesh3D, Scalar, TextEntry, Transform3D, Vec3D, ViewCoordinates}, - MsgSender, RecordingStream, RecordingStreamBuilder, + components::{LineStrip3D, Position3D, Radius, Scalar, Transform3D, ViewCoordinates}, + RecordingStream, RecordingStreamBuilder, time::Timeline, - transform::{Transform3DRepr, TranslationRotationScale3D}, + transform::{TranslationRotationScale3D}, }; +use rerun::{Arrows3D, Points3D}; use shared::*; @@ -79,35 +80,15 @@ fn main() -> Result<(), Box> { }) .collect::>(); - let (mut rec_stream, storage) = RecordingStreamBuilder::new("anim_preview").memory()?; + let (rec_stream, storage) = RecordingStreamBuilder::new("anim_preview").memory()?; - MsgSender::new(format!("world")) - /*.with_component(&[ + rec_stream.log_timeless( + "world", + &rerun::ViewCoordinates::new( ViewCoordinates::from_up_and_handedness( SignedAxis3::POSITIVE_Z, - Handedness::Right) - ]) - .unwrap()*/ - .with_splat(ViewCoordinates::from_up_and_handedness( - SignedAxis3::POSITIVE_Z, - Handedness::Right)) - .unwrap() - /*.with_splat(Transform3D::Rigid3({ - let q = na::UnitQuaternion - ::from_axis_angle( - &na::Vector3::z_axis(), - std::f32::consts::PI - ); - - Rigid3 { - rotation: Quaternion::new(q.i, q.j, q.k, q.w), - ..Default::default() - } - })) - .unwrap()*/ - .with_timeless(true) - .send(&mut rec_stream) - .unwrap(); + Handedness::Right)) + )?; for mesh_anim in mesh_anims { println!("{}", mesh_anim.get_name()); @@ -156,15 +137,15 @@ fn main() -> Result<(), Box> { let glam_points = points .into_iter() - .map(|Vector3 { x, y, z }| Point3D::new(x, y, z)) + .map(|Vector3 { x, y, z }| Position3D::new(x, y, z)) .collect::>(); // Send points to rerun - MsgSender::new(mesh_anim.get_name().as_str()) - .with_component(&glam_points)? - .with_time(Timeline::new_sequence("frame"), i as i64) - .send(&mut rec_stream) - .unwrap(); + rec_stream.set_time_sequence("frame", i as i64); + rec_stream.log( + mesh_anim.get_name().as_str(), + &Points3D::new(glam_points) + )?; // Send line strip to rerun /*MsgSender::new(mesh_anim.get_name().as_str()) @@ -493,51 +474,41 @@ fn add_bones_to_stream(bone: &BoneNode, rec_stream: &RecordingStream, i: usize) .collect::>(); // Add vertex - MsgSender::new(format!("world/{}", bone.name)) - .with_component(&[ - Point3D::from([v[0], v[1], v[2]]) - ]) - .unwrap() - .with_splat(Transform3D { - transform: Transform3DRepr::TranslationRotationScale({ - let q = na::UnitQuaternion - ::from_axis_angle( - &na::Vector3::z_axis(), - std::f32::consts::PI - ); - - TranslationRotationScale3D { - rotation: Some(Quaternion::new(q.i, q.j, q.k, q.w).into()), - ..Default::default() - } - }), - from_parent: true - }) - .unwrap() - /*.with_splat(ViewCoordinates::from_up_and_handedness( - SignedAxis3::POSITIVE_Z, - Handedness::Right)) - .unwrap()*/ - //.with_splat(Radius(1.0)) - //.unwrap() - .with_time(Timeline::new_sequence("frame"), i as i64) - .send(rec_stream) - .unwrap(); + /*rec_stream.set_time_sequence("frame", i as i64); + rec_stream.log_component_batches( + format!("world/{}", bone.name), + false, + [ + &Points3D::new([Position3D::from([v[0], v[1], v[2]])]), + /* + .with_splat(Transform3D { + transform: Transform3DRepr::TranslationRotationScale({ + let q = na::UnitQuaternion + ::from_axis_angle( + &na::Vector3::z_axis(), + std::f32::consts::PI + ); + + TranslationRotationScale3D { + rotation: Some(Quaternion::new(q.i, q.j, q.k, q.w).into()), + ..Default::default() + } + }), + from_parent: true + }) + */ + ] + );*/ // Add lines from node to children - MsgSender::new(format!("world/{}/lines", bone.name)) - .with_component(&strips) - .unwrap() - /*.with_splat(ViewCoordinates::from_up_and_handedness( - SignedAxis3::POSITIVE_Z, - Handedness::Right)) - .unwrap()*/ - .with_time(Timeline::new_sequence("frame"), i as i64) - .send(rec_stream) - .unwrap(); + rec_stream.set_time_sequence("frame", i as i64); + rec_stream.log( + format!("world/{}/lines", bone.name), + &rerun::LineStrips3D::new(strips) + ).unwrap(); // Add direction arrow (not working) - MsgSender::new(format!("world/{}/arrows", bone.name)) + /*MsgSender::new(format!("world/{}/arrows", bone.name)) .with_component(&[ Arrow3D { origin: Vec3D([v[0], v[1], v[2]]), @@ -567,7 +538,7 @@ fn add_bones_to_stream(bone: &BoneNode, rec_stream: &RecordingStream, i: usize) .unwrap()*/ .with_time(Timeline::new_sequence("frame"), i as i64) .send(rec_stream) - .unwrap(); + .unwrap();*/ for ch in bone.children.iter() { add_bones_to_stream(ch, rec_stream, i); diff --git a/utils/lipsync_preview/Cargo.toml b/utils/lipsync_preview/Cargo.toml index c697f569..433904bd 100644 --- a/utils/lipsync_preview/Cargo.toml +++ b/utils/lipsync_preview/Cargo.toml @@ -6,8 +6,9 @@ edition.workspace = true [dependencies] grim = { workspace = true, features = [ "audio" ] } -eframe = "0.22.0" +eframe = "0.25.0" +egui_plot = "0.25.0" keyframe = "1.1.1" nalgebra = "0.32.3" #rerun = "0.2.0" -shared = { path = "../shared" } \ No newline at end of file +shared = { path = "../shared" } diff --git a/utils/lipsync_preview/src/app.rs b/utils/lipsync_preview/src/app.rs index afc73bd0..435703b9 100644 --- a/utils/lipsync_preview/src/app.rs +++ b/utils/lipsync_preview/src/app.rs @@ -105,10 +105,10 @@ impl eframe::App for LipsyncApp { .inner_margin(egui::Vec2::ZERO); egui::CentralPanel::default().frame(frame).show(ctx, |ui| { - let x_grid_spacer = egui::plot::uniform_grid_spacer(|_g| [60. * 60., 60., 20.]); - let y_grid_spacer = egui::plot::uniform_grid_spacer(|_g| [0.5, 0.1, 0.05]); + let x_grid_spacer = egui_plot::uniform_grid_spacer(|_g| [60. * 60., 60., 20.]); + let y_grid_spacer = egui_plot::uniform_grid_spacer(|_g| [0.5, 0.1, 0.05]); - egui::plot::Plot::new("main_plot") + egui_plot::Plot::new("main_plot") .center_y_axis(false) .min_size(egui::Vec2::new(0., 1.)) .x_grid_spacer(x_grid_spacer) @@ -120,20 +120,20 @@ impl eframe::App for LipsyncApp { let sample_points = convert_to_egui_points(channel_samples, 1, false); //plot_ui.points(sample_points.name("audio").color(Color32::BLUE)); - plot_ui.line(egui::plot::Line::new(sample_points).name("audio").color(Color32::BLUE)); + plot_ui.line(egui_plot::Line::new(sample_points).name("audio").color(Color32::BLUE)); } // Draw weights let weight_points = convert_to_egui_points(&self.weight_points, 1, false); //plot_ui.points(egui::plot::Points::new(weight_points).name("weights").color(Color32::GREEN)); - plot_ui.line(egui::plot::Line::new(weight_points).name("weights").color(Color32::GREEN)); + plot_ui.line(egui_plot::Line::new(weight_points).name("weights").color(Color32::GREEN)); }); }); } } -fn convert_to_egui_points(points: &Vec, step: usize, hide_zero: bool) -> egui::plot::PlotPoints { +fn convert_to_egui_points(points: &Vec, step: usize, hide_zero: bool) -> egui_plot::PlotPoints { let series = points .iter() .enumerate() diff --git a/utils/lipsync_preview/src/main.rs b/utils/lipsync_preview/src/main.rs index 33ee0635..1056ed43 100644 --- a/utils/lipsync_preview/src/main.rs +++ b/utils/lipsync_preview/src/main.rs @@ -23,9 +23,11 @@ fn main() -> Result<(), Box> { app.load_args(&args)?; let ops = NativeOptions { - drag_and_drop_support: true, + viewport: eframe::egui::ViewportBuilder::default() + .with_min_inner_size([320.0, 240.0]) + .with_drag_and_drop(true), // icon_data: Some(icon), - min_window_size: Some([1000., 600.].into()), + // min_window_size: Some([1000., 600.].into()), follow_system_theme: false, // Always dark by default default_theme: eframe::Theme::Dark, ..NativeOptions::default()