//! A glTF scene viewer plugin. Provides controls for animation, directional lighting, and switching between scene cameras. //! To use in your own application: //! - Copy the code for the `SceneViewerPlugin` and add the plugin to your App. //! - Insert an initialized `SceneHandle` resource into your App's `AssetServer`. use bevy::{ asset::LoadState, gltf::Gltf, input::common_conditions::input_just_pressed, prelude::*, scene::InstanceId, }; use std::f32::consts::*; use std::fmt; use super::camera_controller_plugin::*; #[derive(Resource)] pub struct SceneHandle { gltf_handle: Handle, scene_index: usize, #[cfg(feature = "animation")] animations: Vec>, instance_id: Option, pub is_loaded: bool, pub has_light: bool, } impl SceneHandle { pub fn new(gltf_handle: Handle, scene_index: usize) -> Self { Self { gltf_handle, scene_index, #[cfg(feature = "animation")] animations: Vec::new(), instance_id: None, is_loaded: false, has_light: false, } } } impl fmt::Display for SceneHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, " Scene Controls: L - animate light direction U - toggle shadows B - toggle bounding boxes C - cycle through the camera controller and any cameras loaded from the scene Space - Play/Pause animation Enter - Cycle through animations " ) } } pub struct SceneViewerPlugin; impl Plugin for SceneViewerPlugin { fn build(&self, app: &mut App) { app.init_resource::() .add_systems(PreUpdate, scene_load_check) .add_systems( Update, ( update_lights, camera_tracker, toggle_bounding_boxes.run_if(input_just_pressed(KeyCode::B)), #[cfg(feature = "animation")] (start_animation, keyboard_animation_control), ), ); } } fn toggle_bounding_boxes(mut config: ResMut) { config.aabb.draw_all ^= true; } fn scene_load_check( asset_server: Res, mut scenes: ResMut>, gltf_assets: ResMut>, mut scene_handle: ResMut, mut scene_spawner: ResMut, ) { match scene_handle.instance_id { None => { if asset_server.get_load_state(&scene_handle.gltf_handle) == LoadState::Loaded { let gltf = gltf_assets.get(&scene_handle.gltf_handle).unwrap(); if gltf.scenes.len() > 1 { info!( "Displaying scene {} out of {}", scene_handle.scene_index, gltf.scenes.len() ); info!("You can select the scene by adding '#Scene' followed by a number to the end of the file path (e.g '#Scene1' to load the second scene)."); } let gltf_scene_handle = gltf.scenes .get(scene_handle.scene_index) .unwrap_or_else(|| { panic!( "glTF file doesn't contain scene {}!", scene_handle.scene_index ) }); let scene = scenes.get_mut(gltf_scene_handle).unwrap(); let mut query = scene .world .query::<(Option<&DirectionalLight>, Option<&PointLight>)>(); scene_handle.has_light = query .iter(&scene.world) .any(|(maybe_directional_light, maybe_point_light)| { maybe_directional_light.is_some() || maybe_point_light.is_some() }); scene_handle.instance_id = Some(scene_spawner.spawn(gltf_scene_handle.clone_weak())); #[cfg(feature = "animation")] { scene_handle.animations = gltf.animations.clone(); if !scene_handle.animations.is_empty() { info!( "Found {} animation{}", scene_handle.animations.len(), if scene_handle.animations.len() == 1 { "" } else { "s" } ); } } info!("Spawning scene..."); } } Some(instance_id) if !scene_handle.is_loaded => { if scene_spawner.instance_is_ready(instance_id) { info!("...done!"); scene_handle.is_loaded = true; } } Some(_) => {} } } #[cfg(feature = "animation")] fn start_animation( mut player: Query<&mut AnimationPlayer>, mut done: Local, scene_handle: Res, ) { if !*done { if let Ok(mut player) = player.get_single_mut() { if let Some(animation) = scene_handle.animations.first() { player.play(animation.clone_weak()).repeat(); *done = true; } } } } #[cfg(feature = "animation")] fn keyboard_animation_control( keyboard_input: Res>, mut animation_player: Query<&mut AnimationPlayer>, scene_handle: Res, mut current_animation: Local, mut changing: Local, ) { if scene_handle.animations.is_empty() { return; } if let Ok(mut player) = animation_player.get_single_mut() { if keyboard_input.just_pressed(KeyCode::Space) { if player.is_paused() { player.resume(); } else { player.pause(); } } if *changing { // change the animation the frame after return was pressed *current_animation = (*current_animation + 1) % scene_handle.animations.len(); player .play(scene_handle.animations[*current_animation].clone_weak()) .repeat(); *changing = false; } if keyboard_input.just_pressed(KeyCode::Return) { // delay the animation change for one frame *changing = true; // set the current animation to its start and pause it to reset to its starting state player.set_elapsed(0.0).pause(); } } } fn update_lights( key_input: Res>, time: Res