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
@ -28,7 +28,7 @@ use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use wgpu::Extent3d;
|
use wgpu::Extent3d;
|
||||||
|
|
||||||
#[derive(Component, Default, Debug, Reflect)]
|
#[derive(Component, Default, Debug, Reflect, Clone)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
pub projection_matrix: Mat4,
|
pub projection_matrix: Mat4,
|
||||||
|
@ -5,7 +5,7 @@ use bevy::{
|
|||||||
math::Vec3A,
|
math::Vec3A,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
render::{
|
render::{
|
||||||
camera::{Camera2d, Camera3d, CameraProjection},
|
camera::{ActiveCamera, Camera3d, CameraProjection},
|
||||||
primitives::{Aabb, Frustum, Sphere},
|
primitives::{Aabb, Frustum, Sphere},
|
||||||
},
|
},
|
||||||
scene::InstanceId,
|
scene::InstanceId,
|
||||||
@ -26,6 +26,7 @@ Controls:
|
|||||||
Q - down
|
Q - down
|
||||||
L - animate light direction
|
L - animate light direction
|
||||||
U - toggle shadows
|
U - toggle shadows
|
||||||
|
C - cycle through cameras
|
||||||
5/6 - decrease/increase shadow projection width
|
5/6 - decrease/increase shadow projection width
|
||||||
7/8 - decrease/increase shadow projection height
|
7/8 - decrease/increase shadow projection height
|
||||||
9/0 - decrease/increase shadow projection near/far
|
9/0 - decrease/increase shadow projection near/far
|
||||||
@ -51,11 +52,11 @@ Controls:
|
|||||||
.add_startup_system(setup)
|
.add_startup_system(setup)
|
||||||
.add_system_to_stage(CoreStage::PreUpdate, scene_load_check)
|
.add_system_to_stage(CoreStage::PreUpdate, scene_load_check)
|
||||||
.add_system_to_stage(CoreStage::PreUpdate, camera_spawn_check)
|
.add_system_to_stage(CoreStage::PreUpdate, camera_spawn_check)
|
||||||
.add_system(check_camera_controller)
|
|
||||||
.add_system(update_lights)
|
.add_system(update_lights)
|
||||||
.add_system(camera_controller.after(check_camera_controller))
|
.add_system(camera_controller)
|
||||||
.add_system(start_animation)
|
.add_system(start_animation)
|
||||||
.add_system(keyboard_animation_control)
|
.add_system(keyboard_animation_control)
|
||||||
|
.add_system(keyboard_cameras_control)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,15 +98,6 @@ fn scene_load_check(
|
|||||||
let gltf_scene_handle = gltf.scenes.first().expect("glTF file contains no scenes!");
|
let gltf_scene_handle = gltf.scenes.first().expect("glTF file contains no scenes!");
|
||||||
let scene = scenes.get_mut(gltf_scene_handle).unwrap();
|
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
|
let mut query = scene
|
||||||
.world
|
.world
|
||||||
.query::<(Option<&DirectionalLight>, Option<&PointLight>)>();
|
.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(
|
fn camera_spawn_check(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut scene_handle: ResMut<SceneHandle>,
|
mut scene_handle: ResMut<SceneHandle>,
|
||||||
meshes: Query<(&GlobalTransform, Option<&Aabb>), With<Handle<Mesh>>>,
|
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
|
// 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
|
// 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));
|
let aabb = Aabb::from_min_max(Vec3::from(min), Vec3::from(max));
|
||||||
|
|
||||||
if !scene_handle.has_camera {
|
if !scene_handle.has_camera {
|
||||||
let transform = Transform::from_translation(
|
let bundle = if let Some((transform, camera)) = cameras_3d.iter().next() {
|
||||||
Vec3::from(aabb.center) + size * Vec3::new(0.5, 0.25, 0.5),
|
let mut transform: Transform = (*transform).into();
|
||||||
)
|
let (yaw, pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
|
||||||
.looking_at(Vec3::from(aabb.center), Vec3::Y);
|
transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.0);
|
||||||
let view = transform.compute_matrix();
|
PerspectiveCameraBundle {
|
||||||
let mut perspective_projection = PerspectiveProjection::default();
|
camera: camera.clone(),
|
||||||
perspective_projection.far = perspective_projection.far.max(size * 10.0);
|
transform,
|
||||||
let view_projection = view.inverse() * perspective_projection.get_projection_matrix();
|
..PerspectiveCameraBundle::new_3d()
|
||||||
let frustum = Frustum::from_view_projection(
|
}
|
||||||
&view_projection,
|
} else {
|
||||||
&transform.translation,
|
let transform = Transform::from_translation(
|
||||||
&transform.back(),
|
Vec3::from(aabb.center) + size * Vec3::new(0.5, 0.25, 0.5),
|
||||||
perspective_projection.far(),
|
)
|
||||||
);
|
.looking_at(Vec3::from(aabb.center), Vec3::Y);
|
||||||
|
let view = transform.compute_matrix();
|
||||||
info!("Spawning a 3D perspective camera");
|
let mut perspective_projection = PerspectiveProjection::default();
|
||||||
commands.spawn_bundle(PerspectiveCameraBundle {
|
perspective_projection.far = perspective_projection.far.max(size * 10.0);
|
||||||
camera: Camera {
|
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,
|
near: perspective_projection.near,
|
||||||
far: perspective_projection.far,
|
far: perspective_projection.far,
|
||||||
..default()
|
..default()
|
||||||
},
|
};
|
||||||
perspective_projection,
|
PerspectiveCameraBundle {
|
||||||
frustum,
|
camera,
|
||||||
transform,
|
perspective_projection,
|
||||||
..PerspectiveCameraBundle::new_3d()
|
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;
|
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;
|
const SCALE_STEP: f32 = 0.1;
|
||||||
|
|
||||||
fn update_lights(
|
fn update_lights(
|
||||||
@ -412,7 +448,7 @@ fn camera_controller(
|
|||||||
|
|
||||||
if let Ok((mut transform, mut options)) = query.get_single_mut() {
|
if let Ok((mut transform, mut options)) = query.get_single_mut() {
|
||||||
if !options.initialized {
|
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.yaw = yaw;
|
||||||
options.pitch = pitch;
|
options.pitch = pitch;
|
||||||
options.initialized = true;
|
options.initialized = true;
|
||||||
|
Loading…
Reference in New Issue
Block a user