use bevy::{ core_pipeline::clear_color::ClearColorConfig, gltf::Gltf, input::{ keyboard::KeyboardInput, mouse::{MouseMotion, MouseWheel}, ButtonState, }, pbr::CascadeShadowConfigBuilder, prelude::*, render::{ camera::RenderTarget, render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, view::RenderLayers, }, winit::WinitSettings, }; fn main() { App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "GLTF Inspector".into(), resolution: (640., 480.).into(), ..default() }), ..default() })) // Only run when window is active to reduce cpu cycles // .insert_resource(WinitSettings::desktop_app()) .add_startup_system(load_models) .add_startup_system(spawn_base_scene) .add_system(spawn_models) // .add_system(control_ui) .add_system(control_camera) .add_system(rotate_model) .add_system(zoom_model) .run(); } /// /// Stores GLTF handles for later use #[derive(Resource)] struct Models { _handles: Vec>, } /// /// Marks GLTF model as inspectable #[derive(Component)] struct Inspect; #[derive(Component)] struct SelectionUI; #[derive(Component)] struct PreviewCamera; /// /// Load all GLTF models on startup fn load_models(mut commands: Commands, ass: Res) { let weak_handles = ass.load_folder("models").expect("Load gltfs"); let _handles: Vec> = weak_handles .iter() .map(|weak| weak.clone().typed::()) .collect(); info!("Scene Handles: {:#?}", _handles); commands.insert_resource(Models { _handles }); } /// /// Spawn base scene fn spawn_base_scene(mut commands: Commands) { commands.spawn(( Camera2dBundle { camera_2d: Camera2d { clear_color: ClearColorConfig::Custom(Color::BLACK), }, ..default() }, UiCameraConfig { ..default() }, SelectionUI, )); } fn control_camera( mut keyboard_evr: EventReader, mut ui_camera: Query<&mut Camera, (With, Without)>, mut scene_camera: Query<&mut Camera, (With, Without)>, ) { for ev in keyboard_evr.iter() { match ev { KeyboardInput { state: ButtonState::Pressed, key_code: Some(KeyCode::Space), .. } => { // Disable UI camera let mut ui_cam = ui_camera.single_mut(); info!("Toggling UI camera {}", !ui_cam.is_active); ui_cam.is_active = !ui_cam.is_active; // Enable scene camera let mut scene_cam = scene_camera .iter_mut() .nth(0) .expect("Failed to get Scene camera"); info!("Toggling Scene camera {}", !scene_cam.is_active); scene_cam.is_active = !scene_cam.is_active; } KeyboardInput { state: ButtonState::Released, key_code: Some(KeyCode::Space), .. } => { // No-Op } _ => {} // No-Op } } } /// /// Spawn a loaded scene for inspection /// TODO: Update/add/delete when files are updated fn spawn_models( mut commands: Commands, mut scene_evr: EventReader>, mut images: ResMut>, ) { if !scene_evr.is_empty() { info!("Spawning scenes"); } for ev in scene_evr.iter() { match ev { AssetEvent::Created { handle } => { info!("Creating scene {:#?}", handle); // Preview image { info!("Creating preview"); let size = Extent3d { width: 512, height: 512, ..default() }; // Create render target image for the preview camera let mut image = Image { texture_descriptor: TextureDescriptor { label: None, size, dimension: TextureDimension::D2, format: TextureFormat::Bgra8UnormSrgb, mip_level_count: 1, sample_count: 1, usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT, view_formats: &[], }, ..default() }; // Fill with zeroes image.resize(size); let image_handle = images.add(image); // Spawn preview camera commands.spawn(( Camera3dBundle { camera_3d: Camera3d { clear_color: ClearColorConfig::Custom(Color::WHITE), ..default() }, camera: Camera { order: -1, target: RenderTarget::Image(image_handle.clone()), ..default() }, transform: Transform::from_xyz(5.0, 5.0, 5.0) .looking_at(Vec3::ZERO, Vec3::Y), ..default() }, UiCameraConfig { show_ui: false }, PreviewCamera, )); // Assign render target image to UI element commands .spawn(NodeBundle { style: Style { size: Size::all(Val::Percent(50.0)), justify_content: JustifyContent::SpaceBetween, ..default() }, ..default() }) .with_children(|parent| { parent.spawn(ImageBundle { style: Style { size: Size::all(Val::Percent(100.0)), ..default() }, image: UiImage { texture: image_handle.clone(), ..default() }, ..default() }); }); } // Spawn the actual scene commands .spawn(( Inspect, VisibilityBundle::default(), TransformBundle::default(), // TODO: Move to staging area RenderLayers::layer(1), // SUS )) .with_children(|builder| { builder.spawn(( Camera3dBundle { camera_3d: Camera3d { clear_color: ClearColorConfig::Custom(Color::WHITE), ..default() }, camera: Camera { is_active: false, ..default() }, transform: Transform::from_xyz(0.0, 0.0, 5.0) .looking_at(Vec3::ZERO, Vec3::Y), ..default() }, Inspect, RenderLayers::layer(1), // SUS )); builder.spawn(( DirectionalLightBundle { directional_light: DirectionalLight { shadows_enabled: true, ..default() }, cascade_shadow_config: CascadeShadowConfigBuilder { num_cascades: 1, maximum_distance: 1.6, ..default() } .into(), ..default() }, Inspect, RenderLayers::layer(1), // SUS )); builder.spawn(( SceneBundle { scene: handle.clone(), ..default() }, Inspect, RenderLayers::layer(1), // SUS )); }); } AssetEvent::Removed { handle: _handle } => { todo!("Remove deleted scene") } AssetEvent::Modified { handle: _handle } => { todo!("Update modified scene") } } } } // fn control_ui(mut commands: Commands, cameras: Query>) { // todo!() // } /// /// Rotate a model as part of inspection fn rotate_model( buttons: Res>, mut mouse_evr: EventReader, mut transforms: Query<&mut Transform, (With, Without)>, ) { if buttons.pressed(MouseButton::Left) { for MouseMotion { delta } in mouse_evr.iter() { for mut transform in transforms.iter_mut() { let rot_y = delta.x / 1000.0; let rot_x = delta.y / 1000.0; transform.rotate_y(rot_y); transform.rotate_x(rot_x); } } } } /// /// Zoom in and out of the model fn zoom_model( mut wheel_evr: EventReader, mut transforms: Query<&mut Transform, With>, ) { for ev in wheel_evr.iter() { for mut transform in transforms.iter_mut() { let scale = (Vec3::ONE * ev.y) / 100.0; transform.scale += scale; } } }