Path: blob/main/examples/tools/scene_viewer/scene_viewer_plugin.rs
6601 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::{6gltf::Gltf, input::common_conditions::input_just_pressed, prelude::*, scene::InstanceId,7};89use std::{f32::consts::*, fmt};1011use super::camera_controller::*;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 = "animation"))]35const INSTRUCTIONS: &str = r#"36Scene Controls:37L - animate light direction38U - toggle shadows39C - cycle through the camera controller and any cameras loaded from the scene4041compile with "--features animation" for animation controls.42"#;4344#[cfg(feature = "animation")]45const INSTRUCTIONS: &str = "46Scene Controls:47L - animate light direction48U - toggle shadows49B - toggle bounding boxes50C - cycle through the camera controller and any cameras loaded from the scene5152Space - Play/Pause animation53Enter - Cycle through animations54";5556impl fmt::Display for SceneHandle {57fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {58write!(f, "{INSTRUCTIONS}")59}60}6162pub struct SceneViewerPlugin;6364impl Plugin for SceneViewerPlugin {65fn build(&self, app: &mut App) {66app.init_resource::<CameraTracker>()67.add_systems(PreUpdate, scene_load_check)68.add_systems(69Update,70(71update_lights,72camera_tracker,73toggle_bounding_boxes.run_if(input_just_pressed(KeyCode::KeyB)),74),75);76}77}7879fn toggle_bounding_boxes(mut config: ResMut<GizmoConfigStore>) {80config.config_mut::<AabbGizmoConfigGroup>().1.draw_all ^= true;81}8283fn scene_load_check(84asset_server: Res<AssetServer>,85mut scenes: ResMut<Assets<Scene>>,86gltf_assets: Res<Assets<Gltf>>,87mut scene_handle: ResMut<SceneHandle>,88mut scene_spawner: ResMut<SceneSpawner>,89) {90match scene_handle.instance_id {91None => {92if asset_server93.load_state(&scene_handle.gltf_handle)94.is_loaded()95{96let gltf = gltf_assets.get(&scene_handle.gltf_handle).unwrap();97if gltf.scenes.len() > 1 {98info!(99"Displaying scene {} out of {}",100scene_handle.scene_index,101gltf.scenes.len()102);103info!("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).");104}105106let gltf_scene_handle =107gltf.scenes108.get(scene_handle.scene_index)109.unwrap_or_else(|| {110panic!(111"glTF file doesn't contain scene {}!",112scene_handle.scene_index113)114});115let scene = scenes.get_mut(gltf_scene_handle).unwrap();116117let mut query = scene118.world119.query::<(Option<&DirectionalLight>, Option<&PointLight>)>();120scene_handle.has_light =121query122.iter(&scene.world)123.any(|(maybe_directional_light, maybe_point_light)| {124maybe_directional_light.is_some() || maybe_point_light.is_some()125});126127scene_handle.instance_id = Some(scene_spawner.spawn(gltf_scene_handle.clone()));128129info!("Spawning scene...");130}131}132Some(instance_id) if !scene_handle.is_loaded => {133if scene_spawner.instance_is_ready(instance_id) {134info!("...done!");135scene_handle.is_loaded = true;136}137}138Some(_) => {}139}140}141142fn update_lights(143key_input: Res<ButtonInput<KeyCode>>,144time: Res<Time>,145mut query: Query<(&mut Transform, &mut DirectionalLight)>,146mut animate_directional_light: Local<bool>,147) {148for (_, mut light) in &mut query {149if key_input.just_pressed(KeyCode::KeyU) {150light.shadows_enabled = !light.shadows_enabled;151}152}153154if key_input.just_pressed(KeyCode::KeyL) {155*animate_directional_light = !*animate_directional_light;156}157if *animate_directional_light {158for (mut transform, _) in &mut query {159transform.rotation = Quat::from_euler(160EulerRot::ZYX,1610.0,162time.elapsed_secs() * PI / 15.0,163-FRAC_PI_4,164);165}166}167}168169#[derive(Resource, Default)]170struct CameraTracker {171active_index: Option<usize>,172cameras: Vec<Entity>,173}174175impl CameraTracker {176fn track_camera(&mut self, entity: Entity) -> bool {177self.cameras.push(entity);178if self.active_index.is_none() {179self.active_index = Some(self.cameras.len() - 1);180true181} else {182false183}184}185186fn active_camera(&self) -> Option<Entity> {187self.active_index.map(|i| self.cameras[i])188}189190fn set_next_active(&mut self) -> Option<Entity> {191let active_index = self.active_index?;192let new_i = (active_index + 1) % self.cameras.len();193self.active_index = Some(new_i);194Some(self.cameras[new_i])195}196}197198fn camera_tracker(199mut camera_tracker: ResMut<CameraTracker>,200keyboard_input: Res<ButtonInput<KeyCode>>,201mut queries: ParamSet<(202Query<(Entity, &mut Camera), (Added<Camera>, Without<CameraController>)>,203Query<(Entity, &mut Camera), (Added<Camera>, With<CameraController>)>,204Query<&mut Camera>,205)>,206) {207// track added scene camera entities first, to ensure they are preferred for the208// default active camera209for (entity, mut camera) in queries.p0().iter_mut() {210camera.is_active = camera_tracker.track_camera(entity);211}212213// iterate added custom camera entities second214for (entity, mut camera) in queries.p1().iter_mut() {215camera.is_active = camera_tracker.track_camera(entity);216}217218if keyboard_input.just_pressed(KeyCode::KeyC) {219// disable currently active camera220if let Some(e) = camera_tracker.active_camera()221&& let Ok(mut camera) = queries.p2().get_mut(e)222{223camera.is_active = false;224}225226// enable next active camera227if let Some(e) = camera_tracker.set_next_active()228&& let Ok(mut camera) = queries.p2().get_mut(e)229{230camera.is_active = true;231}232}233}234235236