mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
scene_viewer: load cameras (#4425)
# Objective glTF files can contain cameras. Currently the scene viewer example uses _a_ camera defined in the file if possible, otherwise it spawns a new one. It would be nice if instead it could load all the cameras and cycle through them, while also having a separate user-controller camera. ## Solution - instead of just a camera that is already defined, always spawn a new separate user-controller camera - maintain a list of loaded cameras and cycle through them (wrapping to the user-controller camera) when pressing `C` This matches the behavious that https://github.khronos.org/glTF-Sample-Viewer-Release/ has. ## Implementation notes - The gltf scene asset loader just spawns the cameras into the world, but does not return a mapping of camera index to bevy entity. So instead the scene_viewer example just collects all spawned cameras with a good old `query.iter().collect()`, so the order is unspecified and may change between runs. ## Demo https://user-images.githubusercontent.com/22177966/161826637-40161482-5b3b-4df5-aae8-1d5e9b918393.mp4 using the virtual city glTF sample file: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/VC Co-authored-by: Jakob Hellermann <hellermann@sipgate.de>
This commit is contained in:
parent
612a5ba3a9
commit
193e8c4ada
2 changed files with 88 additions and 52 deletions
|
@ -28,7 +28,7 @@ use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use wgpu::Extent3d;
|
||||
|
||||
#[derive(Component, Default, Debug, Reflect)]
|
||||
#[derive(Component, Default, Debug, Reflect, Clone)]
|
||||
#[reflect(Component)]
|
||||
pub struct Camera {
|
||||
pub projection_matrix: Mat4,
|
||||
|
|
|
@ -5,7 +5,7 @@ use bevy::{
|
|||
math::Vec3A,
|
||||
prelude::*,
|
||||
render::{
|
||||
camera::{Camera2d, Camera3d, CameraProjection},
|
||||
camera::{ActiveCamera, Camera3d, CameraProjection},
|
||||
primitives::{Aabb, Frustum, Sphere},
|
||||
},
|
||||
scene::InstanceId,
|
||||
|
@ -26,6 +26,7 @@ Controls:
|
|||
Q - down
|
||||
L - animate light direction
|
||||
U - toggle shadows
|
||||
C - cycle through cameras
|
||||
5/6 - decrease/increase shadow projection width
|
||||
7/8 - decrease/increase shadow projection height
|
||||
9/0 - decrease/increase shadow projection near/far
|
||||
|
@ -51,11 +52,11 @@ Controls:
|
|||
.add_startup_system(setup)
|
||||
.add_system_to_stage(CoreStage::PreUpdate, scene_load_check)
|
||||
.add_system_to_stage(CoreStage::PreUpdate, camera_spawn_check)
|
||||
.add_system(check_camera_controller)
|
||||
.add_system(update_lights)
|
||||
.add_system(camera_controller.after(check_camera_controller))
|
||||
.add_system(camera_controller)
|
||||
.add_system(start_animation)
|
||||
.add_system(keyboard_animation_control)
|
||||
.add_system(keyboard_cameras_control)
|
||||
.run();
|
||||
}
|
||||
|
||||
|
@ -97,15 +98,6 @@ fn scene_load_check(
|
|||
let gltf_scene_handle = gltf.scenes.first().expect("glTF file contains no scenes!");
|
||||
let scene = scenes.get_mut(gltf_scene_handle).unwrap();
|
||||
|
||||
let mut query = scene
|
||||
.world
|
||||
.query::<(Option<&Camera2d>, Option<&Camera3d>)>();
|
||||
scene_handle.has_camera =
|
||||
query
|
||||
.iter(&scene.world)
|
||||
.any(|(maybe_camera2d, maybe_camera3d)| {
|
||||
maybe_camera2d.is_some() || maybe_camera3d.is_some()
|
||||
});
|
||||
let mut query = scene
|
||||
.world
|
||||
.query::<(Option<&DirectionalLight>, Option<&PointLight>)>();
|
||||
|
@ -197,10 +189,50 @@ fn keyboard_animation_control(
|
|||
}
|
||||
}
|
||||
|
||||
fn keyboard_cameras_control(
|
||||
mut cameras: Local<(bool, Vec<Entity>)>,
|
||||
mut active_camera: Local<Option<usize>>, // `None` means user-controlled camera
|
||||
|
||||
scene_handle: Res<SceneHandle>,
|
||||
keyboard_input: Res<Input<KeyCode>>,
|
||||
camera_query: Query<Entity, (With<Camera3d>, Without<CameraController>)>,
|
||||
user_camera_query: Query<Entity, (With<Camera3d>, With<CameraController>)>,
|
||||
mut active_camera_3d: ResMut<ActiveCamera<Camera3d>>,
|
||||
) {
|
||||
if !scene_handle.is_loaded {
|
||||
return;
|
||||
}
|
||||
if !cameras.0 {
|
||||
cameras.1 = camera_query.iter().collect();
|
||||
cameras.0 = true;
|
||||
}
|
||||
|
||||
if keyboard_input.just_pressed(KeyCode::C) && !cameras.1.is_empty() {
|
||||
*active_camera = match *active_camera {
|
||||
Some(index) if index + 1 == cameras.1.len() => None,
|
||||
Some(index) => Some(index + 1),
|
||||
None => Some(0),
|
||||
};
|
||||
|
||||
match *active_camera {
|
||||
Some(i) => {
|
||||
info!("Using camera {i}");
|
||||
active_camera_3d.set(cameras.1[i]);
|
||||
}
|
||||
None => {
|
||||
info!("Using user-controller camera");
|
||||
active_camera_3d.set(user_camera_query.single());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn camera_spawn_check(
|
||||
mut commands: Commands,
|
||||
mut scene_handle: ResMut<SceneHandle>,
|
||||
meshes: Query<(&GlobalTransform, Option<&Aabb>), With<Handle<Mesh>>>,
|
||||
cameras_3d: Query<(&GlobalTransform, &Camera), With<Camera3d>>,
|
||||
mut active_camera_3d: ResMut<ActiveCamera<Camera3d>>,
|
||||
) {
|
||||
// If the scene did not contain a camera, find an approximate bounding box of the scene from
|
||||
// its meshes and spawn a camera that fits it in view
|
||||
|
@ -229,33 +261,51 @@ fn camera_spawn_check(
|
|||
let aabb = Aabb::from_min_max(Vec3::from(min), Vec3::from(max));
|
||||
|
||||
if !scene_handle.has_camera {
|
||||
let transform = Transform::from_translation(
|
||||
Vec3::from(aabb.center) + size * Vec3::new(0.5, 0.25, 0.5),
|
||||
)
|
||||
.looking_at(Vec3::from(aabb.center), Vec3::Y);
|
||||
let view = transform.compute_matrix();
|
||||
let mut perspective_projection = PerspectiveProjection::default();
|
||||
perspective_projection.far = perspective_projection.far.max(size * 10.0);
|
||||
let view_projection = view.inverse() * perspective_projection.get_projection_matrix();
|
||||
let frustum = Frustum::from_view_projection(
|
||||
&view_projection,
|
||||
&transform.translation,
|
||||
&transform.back(),
|
||||
perspective_projection.far(),
|
||||
);
|
||||
|
||||
info!("Spawning a 3D perspective camera");
|
||||
commands.spawn_bundle(PerspectiveCameraBundle {
|
||||
camera: Camera {
|
||||
let bundle = if let Some((transform, camera)) = cameras_3d.iter().next() {
|
||||
let mut transform: Transform = (*transform).into();
|
||||
let (yaw, pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
|
||||
transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.0);
|
||||
PerspectiveCameraBundle {
|
||||
camera: camera.clone(),
|
||||
transform,
|
||||
..PerspectiveCameraBundle::new_3d()
|
||||
}
|
||||
} else {
|
||||
let transform = Transform::from_translation(
|
||||
Vec3::from(aabb.center) + size * Vec3::new(0.5, 0.25, 0.5),
|
||||
)
|
||||
.looking_at(Vec3::from(aabb.center), Vec3::Y);
|
||||
let view = transform.compute_matrix();
|
||||
let mut perspective_projection = PerspectiveProjection::default();
|
||||
perspective_projection.far = perspective_projection.far.max(size * 10.0);
|
||||
let view_projection =
|
||||
view.inverse() * perspective_projection.get_projection_matrix();
|
||||
let frustum = Frustum::from_view_projection(
|
||||
&view_projection,
|
||||
&transform.translation,
|
||||
&transform.back(),
|
||||
perspective_projection.far(),
|
||||
);
|
||||
let camera = Camera {
|
||||
near: perspective_projection.near,
|
||||
far: perspective_projection.far,
|
||||
..default()
|
||||
},
|
||||
perspective_projection,
|
||||
frustum,
|
||||
transform,
|
||||
..PerspectiveCameraBundle::new_3d()
|
||||
});
|
||||
};
|
||||
PerspectiveCameraBundle {
|
||||
camera,
|
||||
perspective_projection,
|
||||
frustum,
|
||||
transform,
|
||||
..PerspectiveCameraBundle::new_3d()
|
||||
}
|
||||
};
|
||||
|
||||
info!("Spawning a 3D perspective camera");
|
||||
let entity = commands
|
||||
.spawn_bundle(bundle)
|
||||
.insert(CameraController::default())
|
||||
.id();
|
||||
active_camera_3d.set(entity);
|
||||
|
||||
scene_handle.has_camera = true;
|
||||
}
|
||||
|
@ -293,20 +343,6 @@ fn camera_spawn_check(
|
|||
}
|
||||
}
|
||||
|
||||
fn check_camera_controller(
|
||||
mut commands: Commands,
|
||||
camera: Query<Entity, (With<Camera>, Without<CameraController>)>,
|
||||
mut found_camera: Local<bool>,
|
||||
) {
|
||||
if *found_camera {
|
||||
return;
|
||||
}
|
||||
if let Some(entity) = camera.iter().next() {
|
||||
commands.entity(entity).insert(CameraController::default());
|
||||
*found_camera = true;
|
||||
}
|
||||
}
|
||||
|
||||
const SCALE_STEP: f32 = 0.1;
|
||||
|
||||
fn update_lights(
|
||||
|
@ -412,7 +448,7 @@ fn camera_controller(
|
|||
|
||||
if let Ok((mut transform, mut options)) = query.get_single_mut() {
|
||||
if !options.initialized {
|
||||
let (_roll, yaw, pitch) = transform.rotation.to_euler(EulerRot::ZYX);
|
||||
let (yaw, pitch, _roll) = transform.rotation.to_euler(EulerRot::YXZ);
|
||||
options.yaw = yaw;
|
||||
options.pitch = pitch;
|
||||
options.initialized = true;
|
||||
|
|
Loading…
Reference in a new issue