Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/tools/scene_viewer/main.rs
6598 views
1
//! A simple glTF scene viewer made with Bevy.
2
//!
3
//! Just run `cargo run --release --example scene_viewer /path/to/model.gltf`,
4
//! replacing the path as appropriate.
5
//! In case of multiple scenes, you can select which to display by adapting the file path: `/path/to/model.gltf#Scene1`.
6
//! With no arguments it will load the `FlightHelmet` glTF model from the repository assets subdirectory.
7
//! Pass `--help` to see all the supported arguments.
8
//!
9
//! If you want to hot reload asset changes, enable the `file_watcher` cargo feature.
10
11
use argh::FromArgs;
12
use bevy::{
13
asset::UnapprovedPathMode,
14
camera::primitives::{Aabb, Sphere},
15
core_pipeline::prepass::{DeferredPrepass, DepthPrepass},
16
gltf::GltfPlugin,
17
pbr::DefaultOpaqueRendererMethod,
18
prelude::*,
19
render::experimental::occlusion_culling::OcclusionCulling,
20
};
21
22
#[path = "../../helpers/camera_controller.rs"]
23
mod camera_controller;
24
25
#[cfg(feature = "animation")]
26
mod animation_plugin;
27
mod morph_viewer_plugin;
28
mod scene_viewer_plugin;
29
30
use camera_controller::{CameraController, CameraControllerPlugin};
31
use morph_viewer_plugin::MorphViewerPlugin;
32
use scene_viewer_plugin::{SceneHandle, SceneViewerPlugin};
33
34
/// A simple glTF scene viewer made with Bevy
35
#[derive(FromArgs, Resource)]
36
struct Args {
37
/// the path to the glTF scene
38
#[argh(
39
positional,
40
default = "\"assets/models/FlightHelmet/FlightHelmet.gltf\".to_string()"
41
)]
42
scene_path: String,
43
/// enable a depth prepass
44
#[argh(switch)]
45
depth_prepass: Option<bool>,
46
/// enable occlusion culling
47
#[argh(switch)]
48
occlusion_culling: Option<bool>,
49
/// enable deferred shading
50
#[argh(switch)]
51
deferred: Option<bool>,
52
/// spawn a light even if the scene already has one
53
#[argh(switch)]
54
add_light: Option<bool>,
55
/// enable `GltfPlugin::use_model_forward_direction`
56
#[argh(switch)]
57
use_model_forward_direction: Option<bool>,
58
}
59
60
impl Args {
61
fn rotation(&self) -> Quat {
62
if self.use_model_forward_direction == Some(true) {
63
// If the scene is converted then rotate everything else to match. This
64
// makes comparisons easier - the scene will always face the same way
65
// relative to the camera.
66
Quat::from_xyzw(0.0, 1.0, 0.0, 0.0)
67
} else {
68
Quat::IDENTITY
69
}
70
}
71
}
72
73
fn main() {
74
#[cfg(not(target_arch = "wasm32"))]
75
let args: Args = argh::from_env();
76
#[cfg(target_arch = "wasm32")]
77
let args: Args = Args::from_args(&[], &[]).unwrap();
78
79
let deferred = args.deferred;
80
81
let mut app = App::new();
82
app.add_plugins((
83
DefaultPlugins
84
.set(WindowPlugin {
85
primary_window: Some(Window {
86
title: "bevy scene viewer".to_string(),
87
..default()
88
}),
89
..default()
90
})
91
.set(AssetPlugin {
92
file_path: std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string()),
93
// Allow scenes to be loaded from anywhere on disk
94
unapproved_path_mode: UnapprovedPathMode::Allow,
95
..default()
96
})
97
.set(GltfPlugin {
98
use_model_forward_direction: args.use_model_forward_direction.unwrap_or(false),
99
..default()
100
}),
101
CameraControllerPlugin,
102
SceneViewerPlugin,
103
MorphViewerPlugin,
104
))
105
.insert_resource(args)
106
.add_systems(Startup, setup)
107
.add_systems(PreUpdate, setup_scene_after_load);
108
109
// If deferred shading was requested, turn it on.
110
if deferred == Some(true) {
111
app.insert_resource(DefaultOpaqueRendererMethod::deferred());
112
}
113
114
#[cfg(feature = "animation")]
115
app.add_plugins(animation_plugin::AnimationManipulationPlugin);
116
117
app.run();
118
}
119
120
fn parse_scene(scene_path: String) -> (String, usize) {
121
if scene_path.contains('#') {
122
let gltf_and_scene = scene_path.split('#').collect::<Vec<_>>();
123
if let Some((last, path)) = gltf_and_scene.split_last()
124
&& let Some(index) = last
125
.strip_prefix("Scene")
126
.and_then(|index| index.parse::<usize>().ok())
127
{
128
return (path.join("#"), index);
129
}
130
}
131
(scene_path, 0)
132
}
133
134
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
135
let scene_path = &args.scene_path;
136
info!("Loading {}", scene_path);
137
let (file_path, scene_index) = parse_scene((*scene_path).clone());
138
139
commands.insert_resource(SceneHandle::new(asset_server.load(file_path), scene_index));
140
}
141
142
fn setup_scene_after_load(
143
mut commands: Commands,
144
mut setup: Local<bool>,
145
mut scene_handle: ResMut<SceneHandle>,
146
asset_server: Res<AssetServer>,
147
args: Res<Args>,
148
meshes: Query<(&GlobalTransform, Option<&Aabb>), With<Mesh3d>>,
149
) {
150
if scene_handle.is_loaded && !*setup {
151
*setup = true;
152
// Find an approximate bounding box of the scene from its meshes
153
if meshes.iter().any(|(_, maybe_aabb)| maybe_aabb.is_none()) {
154
return;
155
}
156
157
let mut min = Vec3A::splat(f32::MAX);
158
let mut max = Vec3A::splat(f32::MIN);
159
for (transform, maybe_aabb) in &meshes {
160
let aabb = maybe_aabb.unwrap();
161
// If the Aabb had not been rotated, applying the non-uniform scale would produce the
162
// correct bounds. However, it could very well be rotated and so we first convert to
163
// a Sphere, and then back to an Aabb to find the conservative min and max points.
164
let sphere = Sphere {
165
center: Vec3A::from(transform.transform_point(Vec3::from(aabb.center))),
166
radius: transform.radius_vec3a(aabb.half_extents),
167
};
168
let aabb = Aabb::from(sphere);
169
min = min.min(aabb.min());
170
max = max.max(aabb.max());
171
}
172
173
let size = (max - min).length();
174
let aabb = Aabb::from_min_max(Vec3::from(min), Vec3::from(max));
175
176
info!("Spawning a controllable 3D perspective camera");
177
let mut projection = PerspectiveProjection::default();
178
projection.far = projection.far.max(size * 10.0);
179
180
let walk_speed = size * 3.0;
181
let camera_controller = CameraController {
182
walk_speed,
183
run_speed: 3.0 * walk_speed,
184
..default()
185
};
186
187
// Display the controls of the scene viewer
188
info!("{}", camera_controller);
189
info!("{}", *scene_handle);
190
191
let mut camera = commands.spawn((
192
Camera3d::default(),
193
Projection::from(projection),
194
Transform::from_translation(
195
Vec3::from(aabb.center) + size * (args.rotation() * Vec3::new(0.5, 0.25, 0.5)),
196
)
197
.looking_at(Vec3::from(aabb.center), Vec3::Y),
198
Camera {
199
is_active: false,
200
..default()
201
},
202
EnvironmentMapLight {
203
diffuse_map: asset_server
204
.load("assets/environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
205
specular_map: asset_server
206
.load("assets/environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
207
intensity: 150.0,
208
rotation: args.rotation(),
209
..default()
210
},
211
camera_controller,
212
));
213
214
// If occlusion culling was requested, include the relevant components.
215
// The Z-prepass is currently required.
216
if args.occlusion_culling == Some(true) {
217
camera.insert((DepthPrepass, OcclusionCulling));
218
}
219
220
// If the depth prepass was requested, include it.
221
if args.depth_prepass == Some(true) {
222
camera.insert(DepthPrepass);
223
}
224
225
// If deferred shading was requested, include the prepass.
226
if args.deferred == Some(true) {
227
camera
228
.insert(Msaa::Off)
229
.insert(DepthPrepass)
230
.insert(DeferredPrepass);
231
}
232
233
// Spawn a default light if the scene does not have one
234
if !scene_handle.has_light || args.add_light == Some(true) {
235
info!("Spawning a directional light");
236
let mut light = commands.spawn((
237
DirectionalLight::default(),
238
Transform::from_translation(args.rotation() * Vec3::new(1.0, 1.0, 0.0))
239
.looking_at(Vec3::ZERO, Vec3::Y),
240
));
241
if args.occlusion_culling == Some(true) {
242
light.insert(OcclusionCulling);
243
}
244
245
scene_handle.has_light = true;
246
}
247
}
248
}
249
250