Path: blob/main/examples/tools/scene_viewer/scene_viewer_plugin.rs
9358 views
//! A glTF scene viewer plugin. Provides controls for directional lighting, and switching between scene cameras.1//! To use in your own application:2//! - Copy the code for the `SceneViewerPlugin` and add the plugin to your App.3//! - Insert an initialized `SceneHandle` resource into your App's `AssetServer`.45use bevy::{6camera_controller::free_camera::FreeCamera,7gizmos::skinned_mesh_bounds::SkinnedMeshBoundsGizmoConfigGroup, gltf::Gltf,8input::common_conditions::input_just_pressed, prelude::*, scene::InstanceId,9};1011use std::{f32::consts::*, fmt};1213#[derive(Resource)]14pub struct SceneHandle {15pub gltf_handle: Handle<Gltf>,16scene_index: usize,17instance_id: Option<InstanceId>,18pub is_loaded: bool,19pub has_light: bool,20}2122impl SceneHandle {23pub fn new(gltf_handle: Handle<Gltf>, scene_index: usize) -> Self {24Self {25gltf_handle,26scene_index,27instance_id: None,28is_loaded: false,29has_light: false,30}31}32}3334#[cfg(not(feature = "gltf_animation"))]35const INSTRUCTIONS: &str = r#"36Scene Controls:37L - animate light direction38U - toggle shadows39F - toggle camera frusta40C - cycle through the camera controller and any cameras loaded from the scene4142compile with "--features animation" for animation controls.43"#;4445#[cfg(feature = "gltf_animation")]46const INSTRUCTIONS: &str = "47Scene Controls:48L - animate light direction49U - toggle shadows50B - toggle bounding boxes51F - toggle camera frusta52J - toggle skinned mesh joint bounding boxes53C - cycle through the camera controller and any cameras loaded from the scene5455Space - Play/Pause animation56Enter - Cycle through animations57";5859impl fmt::Display for SceneHandle {60fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {61write!(f, "{INSTRUCTIONS}")62}63}6465pub struct SceneViewerPlugin;6667impl Plugin for SceneViewerPlugin {68fn build(&self, app: &mut App) {69app.init_resource::<CameraTracker>()70.add_systems(PreUpdate, scene_load_check)71.add_systems(72Update,73(74update_lights,75camera_tracker,76(77toggle_bounding_boxes.run_if(input_just_pressed(KeyCode::KeyB)),78toggle_camera_frusta.run_if(input_just_pressed(KeyCode::KeyF)),79toggle_skinned_mesh_bounds.run_if(input_just_pressed(KeyCode::KeyJ)),80)81.chain(),82),83);84}85}8687fn toggle_bounding_boxes(mut config: ResMut<GizmoConfigStore>) {88config.config_mut::<AabbGizmoConfigGroup>().1.draw_all ^= true;89}9091fn toggle_camera_frusta(mut config: ResMut<GizmoConfigStore>) {92config.config_mut::<FrustumGizmoConfigGroup>().1.draw_all ^= true;93}9495fn toggle_skinned_mesh_bounds(mut config: ResMut<GizmoConfigStore>) {96config97.config_mut::<SkinnedMeshBoundsGizmoConfigGroup>()98.199.draw_all ^= true;100}101102fn scene_load_check(103asset_server: Res<AssetServer>,104mut scenes: ResMut<Assets<Scene>>,105gltf_assets: Res<Assets<Gltf>>,106mut scene_handle: ResMut<SceneHandle>,107mut scene_spawner: ResMut<SceneSpawner>,108) {109match scene_handle.instance_id {110None => {111if asset_server112.load_state(&scene_handle.gltf_handle)113.is_loaded()114{115let gltf = gltf_assets.get(&scene_handle.gltf_handle).unwrap();116if gltf.scenes.len() > 1 {117info!(118"Displaying scene {} out of {}",119scene_handle.scene_index,120gltf.scenes.len()121);122info!("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).");123}124125let gltf_scene_handle =126gltf.scenes127.get(scene_handle.scene_index)128.unwrap_or_else(|| {129panic!(130"glTF file doesn't contain scene {}!",131scene_handle.scene_index132)133});134let scene = scenes.get_mut(gltf_scene_handle).unwrap();135136let mut query = scene137.world138.query::<(Option<&DirectionalLight>, Option<&PointLight>)>();139scene_handle.has_light =140query141.iter(&scene.world)142.any(|(maybe_directional_light, maybe_point_light)| {143maybe_directional_light.is_some() || maybe_point_light.is_some()144});145146scene_handle.instance_id = Some(scene_spawner.spawn(gltf_scene_handle.clone()));147148info!("Spawning scene...");149}150}151Some(instance_id) if !scene_handle.is_loaded => {152if scene_spawner.instance_is_ready(instance_id) {153info!("...done!");154scene_handle.is_loaded = true;155}156}157Some(_) => {}158}159}160161fn update_lights(162key_input: Res<ButtonInput<KeyCode>>,163time: Res<Time>,164mut query: Query<(&mut Transform, &mut DirectionalLight)>,165mut animate_directional_light: Local<bool>,166) {167for (_, mut light) in &mut query {168if key_input.just_pressed(KeyCode::KeyU) {169light.shadow_maps_enabled = !light.shadow_maps_enabled;170}171}172173if key_input.just_pressed(KeyCode::KeyL) {174*animate_directional_light = !*animate_directional_light;175}176if *animate_directional_light {177for (mut transform, _) in &mut query {178transform.rotation = Quat::from_euler(179EulerRot::ZYX,1800.0,181time.elapsed_secs() * PI / 15.0,182-FRAC_PI_4,183);184}185}186}187188#[derive(Resource, Default)]189struct CameraTracker {190active_index: Option<usize>,191cameras: Vec<Entity>,192}193194impl CameraTracker {195fn track_camera(&mut self, entity: Entity) -> bool {196self.cameras.push(entity);197if self.active_index.is_none() {198self.active_index = Some(self.cameras.len() - 1);199true200} else {201false202}203}204205fn active_camera(&self) -> Option<Entity> {206self.active_index.map(|i| self.cameras[i])207}208209fn set_next_active(&mut self) -> Option<Entity> {210let active_index = self.active_index?;211let new_i = (active_index + 1) % self.cameras.len();212self.active_index = Some(new_i);213Some(self.cameras[new_i])214}215}216217fn camera_tracker(218mut camera_tracker: ResMut<CameraTracker>,219keyboard_input: Res<ButtonInput<KeyCode>>,220mut queries: ParamSet<(221Query<(Entity, &mut Camera), (Added<Camera>, Without<FreeCamera>)>,222Query<(Entity, &mut Camera), (Added<Camera>, With<FreeCamera>)>,223Query<&mut Camera>,224)>,225) {226// track added scene camera entities first, to ensure they are preferred for the227// default active camera228for (entity, mut camera) in queries.p0().iter_mut() {229camera.is_active = camera_tracker.track_camera(entity);230}231232// iterate added custom camera entities second233for (entity, mut camera) in queries.p1().iter_mut() {234camera.is_active = camera_tracker.track_camera(entity);235}236237if keyboard_input.just_pressed(KeyCode::KeyC) {238// disable currently active camera239if let Some(e) = camera_tracker.active_camera()240&& let Ok(mut camera) = queries.p2().get_mut(e)241{242camera.is_active = false;243}244245// enable next active camera246if let Some(e) = camera_tracker.set_next_active()247&& let Ok(mut camera) = queries.p2().get_mut(e)248{249camera.is_active = true;250}251}252}253254255