From bebc2344b3d3636a0b82101c8d3a77c52775b40e Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Wed, 3 Jan 2024 03:50:47 -0500 Subject: [PATCH 1/2] feat: add 'proper' argument parsing --- Cargo.toml | 1 + src/utils.rs | 44 ++++++++++++++++++++++++++++++++++++++++---- viewer/viewer.rs | 30 +++++++++++------------------- 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7cd658dc..39970713 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,6 +117,7 @@ bevy_transform_gizmo = { version = "0.9", optional = true } bincode2 = { version = "2.0", optional = true } byte-unit = { version = "5.0", optional = true } bytemuck = "1.14" +clap = { version = "4.4.12", features = ["derive"] } flate2 = { version = "1.0", optional = true } flexbuffers = { version = "2.0", optional = true } half = { version = "2.3.1", optional = true, features = ["serde"] } diff --git a/src/utils.rs b/src/utils.rs index f03ff538..f73f488e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,7 @@ // #[cfg(target_arch = "wasm32")] // pub use wasm_bindgen_rayon::init_thread_pool; +use clap::Parser; + #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -7,6 +9,31 @@ use wasm_bindgen::prelude::*; use std::collections::HashMap; +#[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, Parser)] +#[command(author, version, about, long_about = None)] +struct CliArgs { + /// number of random gaussians to generate + #[arg(short, long)] + num_of_gaussians : Option, + + /// number of random particle behaviors to generate + #[arg(short, long)] + num_of_particle_behaviors : Option, + + /// .gcloud or .ply file to load + #[arg(short, long)] + asset_filename : Option, +} + +#[derive(Debug, Clone, Copy)] +pub enum MainArgs { + NumOfGaussians=1, + NumOfParticleBehaviors, + AssetFilename, +} + + pub fn setup_hooks() { #[cfg(debug_assertions)] #[cfg(target_arch = "wasm32")] @@ -17,12 +44,17 @@ pub fn setup_hooks() { #[cfg(not(target_arch = "wasm32"))] -pub fn get_arg(n: usize) -> Option { - std::env::args().nth(n) +pub fn get_arg(arg: MainArgs) -> Option { + let args = CliArgs::parse(); + match arg { + MainArgs::NumOfGaussians => args.num_of_gaussians.map(|n| n.to_string()), + MainArgs::NumOfParticleBehaviors => args.num_of_particle_behaviors.map(|n| n.to_string()), + MainArgs::AssetFilename => args.asset_filename, + } } #[cfg(target_arch = "wasm32")] -pub fn get_arg(n: usize) -> Option { +pub fn get_arg(arg: MainArgs) -> Option { let window = web_sys::window()?; let location = window.location(); let search = location.search().ok()?; @@ -35,5 +67,9 @@ pub fn get_arg(n: usize) -> Option { .map(|v| (v[0].to_string(), v[1].to_string())) .collect::>(); - args.get(&format!("arg{}", n)).cloned() + let arg_value = args.get(&format!("arg{}", arg as u8)).cloned(); + match arg { + MainArgs::NumOfGaussians | MainArgs::NumOfParticleBehaviors => arg_value.and_then(|a| a.parse::().ok().map(|_|a)), + _ => arg_value, + } } diff --git a/viewer/viewer.rs b/viewer/viewer.rs index f6bb3730..ec704735 100644 --- a/viewer/viewer.rs +++ b/viewer/viewer.rs @@ -21,7 +21,7 @@ use bevy_gaussian_splatting::{ random_gaussians, utils::{ get_arg, - setup_hooks, + setup_hooks, MainArgs, }, }; @@ -74,17 +74,11 @@ fn setup_gaussian_cloud( ) { let cloud: Handle; - // TODO: add proper GaussianSplattingViewer argument parsing - let file_arg = get_arg(1); - if let Some(n) = file_arg.clone().and_then(|s| s.parse::().ok()) { + if let Some(n) = get_arg(MainArgs::NumOfGaussians){ + let n = n.parse::().unwrap(); println!("generating {} gaussians", n); cloud = gaussian_assets.add(random_gaussians(n)); - } else if let Some(filename) = file_arg { - if filename == "--help" { - println!("usage: cargo run -- [filename | n]"); - return; - } - + } else if let Some(filename) = get_arg(MainArgs::AssetFilename) { println!("loading {}", filename); cloud = asset_server.load(filename.to_string()); } else { @@ -130,16 +124,14 @@ fn setup_particle_behavior( return; } - let mut particle_behaviors = None; - - let file_arg = get_arg(1); - if let Some(_n) = file_arg.clone().and_then(|s| s.parse::().ok()) { - let behavior_arg = get_arg(2); - if let Some(k) = behavior_arg.clone().and_then(|s| s.parse::().ok()) { + let particle_behaviors = match (get_arg(MainArgs::NumOfGaussians), get_arg(MainArgs::NumOfParticleBehaviors)) { + (Some(_), Some(k)) => { + let k = k.parse::().unwrap(); println!("generating {} particle behaviors", k); - particle_behaviors = particle_behavior_assets.add(random_particle_behaviors(k)).into(); - } - } + particle_behavior_assets.add(random_particle_behaviors(k)).into() + }, + _ => None, + }; if let Some(particle_behaviors) = particle_behaviors { commands.entity(gaussian_cloud.single().0) From 07d81d072dc4ca0340a54337265be99f75bfa539 Mon Sep 17 00:00:00 2001 From: "Victor A." <52110451+cs50victor@users.noreply.github.com> Date: Wed, 3 Jan 2024 04:16:54 -0500 Subject: [PATCH 2/2] chore: update details and add minor docs --- README.md | 7 +++ src/utils.rs | 32 ++++++------- viewer/viewer.rs | 116 +++++++++++++++-------------------------------- 3 files changed, 59 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index d17430b8..7f4ad524 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,13 @@ fn setup_gaussian_cloud( } ``` +### examples + +```cargo run --release -- -f ``` + +i.e +```cargo run --release -- -f scenes/icecream.gcloud``` + ## tools - [ply to gcloud converter](tools/README.md#ply-to-gcloud-converter) diff --git a/src/utils.rs b/src/utils.rs index f73f488e..06b82b83 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,38 +2,35 @@ // pub use wasm_bindgen_rayon::init_thread_pool; use clap::Parser; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")] use std::collections::HashMap; - +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; #[cfg(not(target_arch = "wasm32"))] #[derive(Debug, Parser)] -#[command(author, version, about, long_about = None)] +#[command(about = "bevy gaussian splatting render pipeline plugin", version, long_about = None)] struct CliArgs { /// number of random gaussians to generate #[arg(short, long)] - num_of_gaussians : Option, + num_of_gaussians: Option, /// number of random particle behaviors to generate - #[arg(short, long)] - num_of_particle_behaviors : Option, - + #[arg(short = 'p', long)] + num_of_particle_behaviors: Option, + /// .gcloud or .ply file to load - #[arg(short, long)] - asset_filename : Option, + #[arg(short = 'f', long)] + filename: Option, } #[derive(Debug, Clone, Copy)] pub enum MainArgs { - NumOfGaussians=1, + NumOfGaussians = 1, NumOfParticleBehaviors, - AssetFilename, + Filename, } - pub fn setup_hooks() { #[cfg(debug_assertions)] #[cfg(target_arch = "wasm32")] @@ -42,14 +39,13 @@ pub fn setup_hooks() { } } - #[cfg(not(target_arch = "wasm32"))] pub fn get_arg(arg: MainArgs) -> Option { let args = CliArgs::parse(); match arg { MainArgs::NumOfGaussians => args.num_of_gaussians.map(|n| n.to_string()), MainArgs::NumOfParticleBehaviors => args.num_of_particle_behaviors.map(|n| n.to_string()), - MainArgs::AssetFilename => args.asset_filename, + MainArgs::Filename => args.filename, } } @@ -69,7 +65,9 @@ pub fn get_arg(arg: MainArgs) -> Option { let arg_value = args.get(&format!("arg{}", arg as u8)).cloned(); match arg { - MainArgs::NumOfGaussians | MainArgs::NumOfParticleBehaviors => arg_value.and_then(|a| a.parse::().ok().map(|_|a)), + MainArgs::NumOfGaussians | MainArgs::NumOfParticleBehaviors => { + arg_value.and_then(|a| a.parse::().ok().map(|_| a)) + } _ => arg_value, } } diff --git a/viewer/viewer.rs b/viewer/viewer.rs index ec704735..fc54ad6b 100644 --- a/viewer/viewer.rs +++ b/viewer/viewer.rs @@ -1,49 +1,31 @@ use bevy::{ - prelude::*, app::AppExit, core::Name, core_pipeline::tonemapping::Tonemapping, - diagnostic::{ - DiagnosticsStore, - FrameTimeDiagnosticsPlugin, - }, + diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}, + prelude::*, }; use bevy_inspector_egui::quick::WorldInspectorPlugin; -use bevy_panorbit_camera::{ - PanOrbitCamera, - PanOrbitCameraPlugin, -}; +use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; use bevy_gaussian_splatting::{ - GaussianCloud, - GaussianSplattingBundle, - GaussianSplattingPlugin, random_gaussians, - utils::{ - get_arg, - setup_hooks, MainArgs, - }, + utils::{get_arg, setup_hooks, MainArgs}, + GaussianCloud, GaussianSplattingBundle, GaussianSplattingPlugin, }; #[cfg(feature = "material_noise")] use bevy_gaussian_splatting::material::noise::NoiseMaterial; #[cfg(feature = "morph_particles")] -use bevy_gaussian_splatting::morph::particle::{ - ParticleBehaviors, - random_particle_behaviors, -}; +use bevy_gaussian_splatting::morph::particle::{random_particle_behaviors, ParticleBehaviors}; #[cfg(feature = "query_select")] -use bevy_gaussian_splatting::query::select::{ - InvertSelectionEvent, - SaveSelectionEvent, -}; +use bevy_gaussian_splatting::query::select::{InvertSelectionEvent, SaveSelectionEvent}; #[cfg(feature = "query_sparse")] use bevy_gaussian_splatting::query::sparse::SparseSelect; - pub struct GaussianSplattingViewer { pub editor: bool, pub esc_close: bool, @@ -66,7 +48,6 @@ impl Default for GaussianSplattingViewer { } } - fn setup_gaussian_cloud( mut commands: Commands, asset_server: Res, @@ -74,11 +55,11 @@ fn setup_gaussian_cloud( ) { let cloud: Handle; - if let Some(n) = get_arg(MainArgs::NumOfGaussians){ + if let Some(n) = get_arg(MainArgs::NumOfGaussians) { let n = n.parse::().unwrap(); println!("generating {} gaussians", n); cloud = gaussian_assets.add(random_gaussians(n)); - } else if let Some(filename) = get_arg(MainArgs::AssetFilename) { + } else if let Some(filename) = get_arg(MainArgs::Filename) { println!("loading {}", filename); cloud = asset_server.load(filename.to_string()); } else { @@ -86,10 +67,7 @@ fn setup_gaussian_cloud( } commands.spawn(( - GaussianSplattingBundle { - cloud, - ..default() - }, + GaussianSplattingBundle { cloud, ..default() }, Name::new("gaussian_cloud"), )); @@ -99,7 +77,7 @@ fn setup_gaussian_cloud( tonemapping: Tonemapping::None, ..default() }, - PanOrbitCamera{ + PanOrbitCamera { allow_upside_down: true, orbit_smoothness: 0.0, pan_smoothness: 0.0, @@ -109,7 +87,6 @@ fn setup_gaussian_cloud( )); } - #[cfg(feature = "morph_particles")] fn setup_particle_behavior( mut commands: Commands, @@ -124,17 +101,23 @@ fn setup_particle_behavior( return; } - let particle_behaviors = match (get_arg(MainArgs::NumOfGaussians), get_arg(MainArgs::NumOfParticleBehaviors)) { + let particle_behaviors = match ( + get_arg(MainArgs::NumOfGaussians), + get_arg(MainArgs::NumOfParticleBehaviors), + ) { (Some(_), Some(k)) => { let k = k.parse::().unwrap(); println!("generating {} particle behaviors", k); - particle_behavior_assets.add(random_particle_behaviors(k)).into() - }, + particle_behavior_assets + .add(random_particle_behaviors(k)) + .into() + } _ => None, }; if let Some(particle_behaviors) = particle_behaviors { - commands.entity(gaussian_cloud.single().0) + commands + .entity(gaussian_cloud.single().0) .insert(particle_behaviors); } } @@ -143,31 +126,21 @@ fn setup_particle_behavior( fn setup_noise_material( mut commands: Commands, asset_server: Res, - gaussian_clouds: Query<( - Entity, - &Handle, - Without, - )>, + gaussian_clouds: Query<(Entity, &Handle, Without)>, ) { if gaussian_clouds.is_empty() { return; } - for ( - entity, - cloud_handle, - _ - ) in gaussian_clouds.iter() { + for (entity, cloud_handle, _) in gaussian_clouds.iter() { if Some(bevy::asset::LoadState::Loading) == asset_server.get_load_state(cloud_handle) { continue; } - commands.entity(entity) - .insert(NoiseMaterial::default()); + commands.entity(entity).insert(NoiseMaterial::default()); } } - #[cfg(feature = "query_select")] fn press_i_invert_selection( keys: Res>, @@ -190,28 +163,23 @@ fn press_o_save_selection( } } - #[cfg(feature = "query_sparse")] fn setup_sparse_select( mut commands: Commands, - gaussian_cloud: Query<( - Entity, - &Handle, - Without, - )>, + gaussian_cloud: Query<(Entity, &Handle, Without)>, ) { if gaussian_cloud.is_empty() { return; } - commands.entity(gaussian_cloud.single().0) + commands + .entity(gaussian_cloud.single().0) .insert(SparseSelect { completed: true, ..default() }); } - fn example_app() { let config = GaussianSplattingViewer::default(); let mut app = App::new(); @@ -251,15 +219,13 @@ fn example_app() { app.insert_resource(ClearColor(Color::rgb_u8(0, 0, 0))); app.add_plugins( DefaultPlugins - .set(ImagePlugin::default_nearest()) - .set(WindowPlugin { - primary_window, - ..default() - }), + .set(ImagePlugin::default_nearest()) + .set(WindowPlugin { + primary_window, + ..default() + }), ); - app.add_plugins(( - PanOrbitCameraPlugin, - )); + app.add_plugins((PanOrbitCameraPlugin,)); if config.editor { app.add_plugins(WorldInspectorPlugin::new()); @@ -275,7 +241,6 @@ fn example_app() { app.add_systems(Update, fps_update_system); } - // setup for gaussian splatting app.add_plugins(GaussianSplattingPlugin); app.add_systems(Startup, setup_gaussian_cloud); @@ -298,20 +263,13 @@ fn example_app() { app.run(); } - -pub fn esc_close( - keys: Res>, - mut exit: EventWriter -) { +pub fn esc_close(keys: Res>, mut exit: EventWriter) { if keys.just_pressed(KeyCode::Escape) { exit.send(AppExit); } } -fn fps_display_setup( - mut commands: Commands, - asset_server: Res, -) { +fn fps_display_setup(mut commands: Commands, asset_server: Res) { commands.spawn(( TextBundle::from_sections([ TextSection::new( @@ -327,7 +285,8 @@ fn fps_display_setup( font_size: 60.0, color: Color::GOLD, }), - ]).with_style(Style { + ]) + .with_style(Style { position_type: PositionType::Absolute, bottom: Val::Px(5.0), left: Val::Px(15.0), @@ -353,7 +312,6 @@ fn fps_update_system( } } - pub fn main() { setup_hooks(); example_app();