# Objective NOTE: This depends on #7267 and should not be merged until #7267 is merged. If you are reviewing this before that is merged, I highly recommend viewing the Base Sets commit instead of trying to find my changes amongst those from #7267. "Default sets" as described by the [Stageless RFC](https://github.com/bevyengine/rfcs/pull/45) have some [unfortunate consequences](https://github.com/bevyengine/bevy/discussions/7365). ## Solution This adds "base sets" as a variant of `SystemSet`: A set is a "base set" if `SystemSet::is_base` returns `true`. Typically this will be opted-in to using the `SystemSet` derive: ```rust #[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)] #[system_set(base)] enum MyBaseSet { A, B, } ``` **Base sets are exclusive**: a system can belong to at most one "base set". Adding a system to more than one will result in an error. When possible we fail immediately during system-config-time with a nice file + line number. For the more nested graph-ey cases, this will fail at the final schedule build. **Base sets cannot belong to other sets**: this is where the word "base" comes from Systems and Sets can only be added to base sets using `in_base_set`. Calling `in_set` with a base set will fail. As will calling `in_base_set` with a normal set. ```rust app.add_system(foo.in_base_set(MyBaseSet::A)) // X must be a normal set ... base sets cannot be added to base sets .configure_set(X.in_base_set(MyBaseSet::A)) ``` Base sets can still be configured like normal sets: ```rust app.add_system(MyBaseSet::B.after(MyBaseSet::Ap)) ``` The primary use case for base sets is enabling a "default base set": ```rust schedule.set_default_base_set(CoreSet::Update) // this will belong to CoreSet::Update by default .add_system(foo) // this will override the default base set with PostUpdate .add_system(bar.in_base_set(CoreSet::PostUpdate)) ``` This allows us to build apis that work by default in the standard Bevy style. This is a rough analog to the "default stage" model, but it use the new "stageless sets" model instead, with all of the ordering flexibility (including exclusive systems) that it provides. --- ## Changelog - Added "base sets" and ported CoreSet to use them. ## Migration Guide TODO
		
			
				
	
	
		
			290 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			290 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
//! 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, prelude::*, scene::InstanceId};
 | 
						|
 | 
						|
use std::f32::consts::*;
 | 
						|
use std::fmt;
 | 
						|
 | 
						|
use super::camera_controller_plugin::*;
 | 
						|
 | 
						|
#[derive(Resource)]
 | 
						|
pub struct SceneHandle {
 | 
						|
    gltf_handle: Handle<Gltf>,
 | 
						|
    scene_index: usize,
 | 
						|
    #[cfg(feature = "animation")]
 | 
						|
    animations: Vec<Handle<AnimationClip>>,
 | 
						|
    instance_id: Option<InstanceId>,
 | 
						|
    pub is_loaded: bool,
 | 
						|
    pub has_light: bool,
 | 
						|
}
 | 
						|
 | 
						|
impl SceneHandle {
 | 
						|
    pub fn new(gltf_handle: Handle<Gltf>, 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
 | 
						|
    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::<CameraTracker>()
 | 
						|
            .add_system(scene_load_check.in_base_set(CoreSet::PreUpdate))
 | 
						|
            .add_system(update_lights)
 | 
						|
            .add_system(camera_tracker);
 | 
						|
 | 
						|
        #[cfg(feature = "animation")]
 | 
						|
        app.add_system(start_animation)
 | 
						|
            .add_system(keyboard_animation_control);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
fn scene_load_check(
 | 
						|
    asset_server: Res<AssetServer>,
 | 
						|
    mut scenes: ResMut<Assets<Scene>>,
 | 
						|
    gltf_assets: ResMut<Assets<Gltf>>,
 | 
						|
    mut scene_handle: ResMut<SceneHandle>,
 | 
						|
    mut scene_spawner: ResMut<SceneSpawner>,
 | 
						|
) {
 | 
						|
    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<bool>,
 | 
						|
    scene_handle: Res<SceneHandle>,
 | 
						|
) {
 | 
						|
    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<Input<KeyCode>>,
 | 
						|
    mut animation_player: Query<&mut AnimationPlayer>,
 | 
						|
    scene_handle: Res<SceneHandle>,
 | 
						|
    mut current_animation: Local<usize>,
 | 
						|
    mut changing: Local<bool>,
 | 
						|
) {
 | 
						|
    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<Input<KeyCode>>,
 | 
						|
    time: Res<Time>,
 | 
						|
    mut query: Query<(&mut Transform, &mut DirectionalLight)>,
 | 
						|
    mut animate_directional_light: Local<bool>,
 | 
						|
) {
 | 
						|
    for (_, mut light) in &mut query {
 | 
						|
        if key_input.just_pressed(KeyCode::U) {
 | 
						|
            light.shadows_enabled = !light.shadows_enabled;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if key_input.just_pressed(KeyCode::L) {
 | 
						|
        *animate_directional_light = !*animate_directional_light;
 | 
						|
    }
 | 
						|
    if *animate_directional_light {
 | 
						|
        for (mut transform, _) in &mut query {
 | 
						|
            transform.rotation = Quat::from_euler(
 | 
						|
                EulerRot::ZYX,
 | 
						|
                0.0,
 | 
						|
                time.elapsed_seconds() * PI / 15.0,
 | 
						|
                -FRAC_PI_4,
 | 
						|
            );
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Resource, Default)]
 | 
						|
struct CameraTracker {
 | 
						|
    active_index: Option<usize>,
 | 
						|
    cameras: Vec<Entity>,
 | 
						|
}
 | 
						|
 | 
						|
impl CameraTracker {
 | 
						|
    fn track_camera(&mut self, entity: Entity) -> bool {
 | 
						|
        self.cameras.push(entity);
 | 
						|
        if self.active_index.is_none() {
 | 
						|
            self.active_index = Some(self.cameras.len() - 1);
 | 
						|
            true
 | 
						|
        } else {
 | 
						|
            false
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    fn active_camera(&self) -> Option<Entity> {
 | 
						|
        self.active_index.map(|i| self.cameras[i])
 | 
						|
    }
 | 
						|
 | 
						|
    fn set_next_active(&mut self) -> Option<Entity> {
 | 
						|
        let active_index = self.active_index?;
 | 
						|
        let new_i = (active_index + 1) % self.cameras.len();
 | 
						|
        self.active_index = Some(new_i);
 | 
						|
        Some(self.cameras[new_i])
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
fn camera_tracker(
 | 
						|
    mut camera_tracker: ResMut<CameraTracker>,
 | 
						|
    keyboard_input: Res<Input<KeyCode>>,
 | 
						|
    mut queries: ParamSet<(
 | 
						|
        Query<(Entity, &mut Camera), (Added<Camera>, Without<CameraController>)>,
 | 
						|
        Query<(Entity, &mut Camera), (Added<Camera>, With<CameraController>)>,
 | 
						|
        Query<&mut Camera>,
 | 
						|
    )>,
 | 
						|
) {
 | 
						|
    // track added scene camera entities first, to ensure they are preferred for the
 | 
						|
    // default active camera
 | 
						|
    for (entity, mut camera) in queries.p0().iter_mut() {
 | 
						|
        camera.is_active = camera_tracker.track_camera(entity);
 | 
						|
    }
 | 
						|
 | 
						|
    // iterate added custom camera entities second
 | 
						|
    for (entity, mut camera) in queries.p1().iter_mut() {
 | 
						|
        camera.is_active = camera_tracker.track_camera(entity);
 | 
						|
    }
 | 
						|
 | 
						|
    if keyboard_input.just_pressed(KeyCode::C) {
 | 
						|
        // disable currently active camera
 | 
						|
        if let Some(e) = camera_tracker.active_camera() {
 | 
						|
            if let Ok(mut camera) = queries.p2().get_mut(e) {
 | 
						|
                camera.is_active = false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // enable next active camera
 | 
						|
        if let Some(e) = camera_tracker.set_next_active() {
 | 
						|
            if let Ok(mut camera) = queries.p2().get_mut(e) {
 | 
						|
                camera.is_active = true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |