Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/tools/scene_viewer/scene_viewer_plugin.rs
9358 views
1
//! A glTF scene viewer plugin. Provides controls for directional lighting, and switching between scene cameras.
2
//! To use in your own application:
3
//! - Copy the code for the `SceneViewerPlugin` and add the plugin to your App.
4
//! - Insert an initialized `SceneHandle` resource into your App's `AssetServer`.
5
6
use bevy::{
7
camera_controller::free_camera::FreeCamera,
8
gizmos::skinned_mesh_bounds::SkinnedMeshBoundsGizmoConfigGroup, gltf::Gltf,
9
input::common_conditions::input_just_pressed, prelude::*, scene::InstanceId,
10
};
11
12
use std::{f32::consts::*, fmt};
13
14
#[derive(Resource)]
15
pub struct SceneHandle {
16
pub gltf_handle: Handle<Gltf>,
17
scene_index: usize,
18
instance_id: Option<InstanceId>,
19
pub is_loaded: bool,
20
pub has_light: bool,
21
}
22
23
impl SceneHandle {
24
pub fn new(gltf_handle: Handle<Gltf>, scene_index: usize) -> Self {
25
Self {
26
gltf_handle,
27
scene_index,
28
instance_id: None,
29
is_loaded: false,
30
has_light: false,
31
}
32
}
33
}
34
35
#[cfg(not(feature = "gltf_animation"))]
36
const INSTRUCTIONS: &str = r#"
37
Scene Controls:
38
L - animate light direction
39
U - toggle shadows
40
F - toggle camera frusta
41
C - cycle through the camera controller and any cameras loaded from the scene
42
43
compile with "--features animation" for animation controls.
44
"#;
45
46
#[cfg(feature = "gltf_animation")]
47
const INSTRUCTIONS: &str = "
48
Scene Controls:
49
L - animate light direction
50
U - toggle shadows
51
B - toggle bounding boxes
52
F - toggle camera frusta
53
J - toggle skinned mesh joint bounding boxes
54
C - cycle through the camera controller and any cameras loaded from the scene
55
56
Space - Play/Pause animation
57
Enter - Cycle through animations
58
";
59
60
impl fmt::Display for SceneHandle {
61
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62
write!(f, "{INSTRUCTIONS}")
63
}
64
}
65
66
pub struct SceneViewerPlugin;
67
68
impl Plugin for SceneViewerPlugin {
69
fn build(&self, app: &mut App) {
70
app.init_resource::<CameraTracker>()
71
.add_systems(PreUpdate, scene_load_check)
72
.add_systems(
73
Update,
74
(
75
update_lights,
76
camera_tracker,
77
(
78
toggle_bounding_boxes.run_if(input_just_pressed(KeyCode::KeyB)),
79
toggle_camera_frusta.run_if(input_just_pressed(KeyCode::KeyF)),
80
toggle_skinned_mesh_bounds.run_if(input_just_pressed(KeyCode::KeyJ)),
81
)
82
.chain(),
83
),
84
);
85
}
86
}
87
88
fn toggle_bounding_boxes(mut config: ResMut<GizmoConfigStore>) {
89
config.config_mut::<AabbGizmoConfigGroup>().1.draw_all ^= true;
90
}
91
92
fn toggle_camera_frusta(mut config: ResMut<GizmoConfigStore>) {
93
config.config_mut::<FrustumGizmoConfigGroup>().1.draw_all ^= true;
94
}
95
96
fn toggle_skinned_mesh_bounds(mut config: ResMut<GizmoConfigStore>) {
97
config
98
.config_mut::<SkinnedMeshBoundsGizmoConfigGroup>()
99
.1
100
.draw_all ^= true;
101
}
102
103
fn scene_load_check(
104
asset_server: Res<AssetServer>,
105
mut scenes: ResMut<Assets<Scene>>,
106
gltf_assets: Res<Assets<Gltf>>,
107
mut scene_handle: ResMut<SceneHandle>,
108
mut scene_spawner: ResMut<SceneSpawner>,
109
) {
110
match scene_handle.instance_id {
111
None => {
112
if asset_server
113
.load_state(&scene_handle.gltf_handle)
114
.is_loaded()
115
{
116
let gltf = gltf_assets.get(&scene_handle.gltf_handle).unwrap();
117
if gltf.scenes.len() > 1 {
118
info!(
119
"Displaying scene {} out of {}",
120
scene_handle.scene_index,
121
gltf.scenes.len()
122
);
123
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).");
124
}
125
126
let gltf_scene_handle =
127
gltf.scenes
128
.get(scene_handle.scene_index)
129
.unwrap_or_else(|| {
130
panic!(
131
"glTF file doesn't contain scene {}!",
132
scene_handle.scene_index
133
)
134
});
135
let scene = scenes.get_mut(gltf_scene_handle).unwrap();
136
137
let mut query = scene
138
.world
139
.query::<(Option<&DirectionalLight>, Option<&PointLight>)>();
140
scene_handle.has_light =
141
query
142
.iter(&scene.world)
143
.any(|(maybe_directional_light, maybe_point_light)| {
144
maybe_directional_light.is_some() || maybe_point_light.is_some()
145
});
146
147
scene_handle.instance_id = Some(scene_spawner.spawn(gltf_scene_handle.clone()));
148
149
info!("Spawning scene...");
150
}
151
}
152
Some(instance_id) if !scene_handle.is_loaded => {
153
if scene_spawner.instance_is_ready(instance_id) {
154
info!("...done!");
155
scene_handle.is_loaded = true;
156
}
157
}
158
Some(_) => {}
159
}
160
}
161
162
fn update_lights(
163
key_input: Res<ButtonInput<KeyCode>>,
164
time: Res<Time>,
165
mut query: Query<(&mut Transform, &mut DirectionalLight)>,
166
mut animate_directional_light: Local<bool>,
167
) {
168
for (_, mut light) in &mut query {
169
if key_input.just_pressed(KeyCode::KeyU) {
170
light.shadow_maps_enabled = !light.shadow_maps_enabled;
171
}
172
}
173
174
if key_input.just_pressed(KeyCode::KeyL) {
175
*animate_directional_light = !*animate_directional_light;
176
}
177
if *animate_directional_light {
178
for (mut transform, _) in &mut query {
179
transform.rotation = Quat::from_euler(
180
EulerRot::ZYX,
181
0.0,
182
time.elapsed_secs() * PI / 15.0,
183
-FRAC_PI_4,
184
);
185
}
186
}
187
}
188
189
#[derive(Resource, Default)]
190
struct CameraTracker {
191
active_index: Option<usize>,
192
cameras: Vec<Entity>,
193
}
194
195
impl CameraTracker {
196
fn track_camera(&mut self, entity: Entity) -> bool {
197
self.cameras.push(entity);
198
if self.active_index.is_none() {
199
self.active_index = Some(self.cameras.len() - 1);
200
true
201
} else {
202
false
203
}
204
}
205
206
fn active_camera(&self) -> Option<Entity> {
207
self.active_index.map(|i| self.cameras[i])
208
}
209
210
fn set_next_active(&mut self) -> Option<Entity> {
211
let active_index = self.active_index?;
212
let new_i = (active_index + 1) % self.cameras.len();
213
self.active_index = Some(new_i);
214
Some(self.cameras[new_i])
215
}
216
}
217
218
fn camera_tracker(
219
mut camera_tracker: ResMut<CameraTracker>,
220
keyboard_input: Res<ButtonInput<KeyCode>>,
221
mut queries: ParamSet<(
222
Query<(Entity, &mut Camera), (Added<Camera>, Without<FreeCamera>)>,
223
Query<(Entity, &mut Camera), (Added<Camera>, With<FreeCamera>)>,
224
Query<&mut Camera>,
225
)>,
226
) {
227
// track added scene camera entities first, to ensure they are preferred for the
228
// default active camera
229
for (entity, mut camera) in queries.p0().iter_mut() {
230
camera.is_active = camera_tracker.track_camera(entity);
231
}
232
233
// iterate added custom camera entities second
234
for (entity, mut camera) in queries.p1().iter_mut() {
235
camera.is_active = camera_tracker.track_camera(entity);
236
}
237
238
if keyboard_input.just_pressed(KeyCode::KeyC) {
239
// disable currently active camera
240
if let Some(e) = camera_tracker.active_camera()
241
&& let Ok(mut camera) = queries.p2().get_mut(e)
242
{
243
camera.is_active = false;
244
}
245
246
// enable next active camera
247
if let Some(e) = camera_tracker.set_next_active()
248
&& let Ok(mut camera) = queries.p2().get_mut(e)
249
{
250
camera.is_active = true;
251
}
252
}
253
}
254
255