Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/tools/scene_viewer/main.rs
9334 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
camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
16
core_pipeline::prepass::{DeferredPrepass, DepthPrepass},
17
gltf::{convert_coordinates::GltfConvertCoordinates, GltfPlugin},
18
pbr::DefaultOpaqueRendererMethod,
19
prelude::*,
20
render::occlusion_culling::OcclusionCulling,
21
};
22
23
#[cfg(feature = "gltf_animation")]
24
mod animation_plugin;
25
mod morph_viewer_plugin;
26
mod scene_viewer_plugin;
27
28
use morph_viewer_plugin::MorphViewerPlugin;
29
use scene_viewer_plugin::{SceneHandle, SceneViewerPlugin};
30
31
/// A simple glTF scene viewer made with Bevy
32
#[derive(FromArgs, Resource)]
33
struct Args {
34
/// the path to the glTF scene
35
#[argh(
36
positional,
37
default = "\"assets/models/FlightHelmet/FlightHelmet.gltf\".to_string()"
38
)]
39
scene_path: String,
40
/// enable a depth prepass
41
#[argh(switch)]
42
depth_prepass: Option<bool>,
43
/// enable occlusion culling
44
#[argh(switch)]
45
occlusion_culling: Option<bool>,
46
/// enable deferred shading
47
#[argh(switch)]
48
deferred: Option<bool>,
49
/// spawn a light even if the scene already has one
50
#[argh(switch)]
51
add_light: Option<bool>,
52
/// enable `GltfPlugin::convert_coordinates::scenes`
53
#[argh(switch)]
54
convert_scene_coordinates: Option<bool>,
55
/// enable `GltfPlugin::convert_coordinates::meshes`
56
#[argh(switch)]
57
convert_mesh_coordinates: Option<bool>,
58
}
59
60
impl Args {
61
fn rotation(&self) -> Quat {
62
if self.convert_scene_coordinates == 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 cameras and lights.
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
convert_coordinates: GltfConvertCoordinates {
99
rotate_scene_entity: args.convert_scene_coordinates == Some(true),
100
rotate_meshes: args.convert_mesh_coordinates == Some(true),
101
},
102
..default()
103
}),
104
FreeCameraPlugin,
105
SceneViewerPlugin,
106
MorphViewerPlugin,
107
))
108
.insert_resource(args)
109
.add_systems(Startup, setup)
110
.add_systems(PreUpdate, setup_scene_after_load);
111
112
// If deferred shading was requested, turn it on.
113
if deferred == Some(true) {
114
app.insert_resource(DefaultOpaqueRendererMethod::deferred());
115
}
116
117
#[cfg(feature = "gltf_animation")]
118
app.add_plugins(animation_plugin::AnimationManipulationPlugin);
119
120
app.run();
121
}
122
123
fn parse_scene(scene_path: String) -> (String, usize) {
124
if scene_path.contains('#') {
125
let gltf_and_scene = scene_path.split('#').collect::<Vec<_>>();
126
if let Some((last, path)) = gltf_and_scene.split_last()
127
&& let Some(index) = last
128
.strip_prefix("Scene")
129
.and_then(|index| index.parse::<usize>().ok())
130
{
131
return (path.join("#"), index);
132
}
133
}
134
(scene_path, 0)
135
}
136
137
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
138
let scene_path = &args.scene_path;
139
info!("Loading {}", scene_path);
140
let (file_path, scene_index) = parse_scene((*scene_path).clone());
141
142
commands.insert_resource(SceneHandle::new(asset_server.load(file_path), scene_index));
143
}
144
145
fn setup_scene_after_load(
146
mut commands: Commands,
147
mut setup: Local<bool>,
148
mut scene_handle: ResMut<SceneHandle>,
149
asset_server: Res<AssetServer>,
150
args: Res<Args>,
151
meshes: Query<(&GlobalTransform, Option<&Aabb>), With<Mesh3d>>,
152
) {
153
if scene_handle.is_loaded && !*setup {
154
*setup = true;
155
// Find an approximate bounding box of the scene from its meshes
156
if meshes.iter().any(|(_, maybe_aabb)| maybe_aabb.is_none()) {
157
return;
158
}
159
160
let mut min = Vec3A::splat(f32::MAX);
161
let mut max = Vec3A::splat(f32::MIN);
162
for (transform, maybe_aabb) in &meshes {
163
let aabb = maybe_aabb.unwrap();
164
// If the Aabb had not been rotated, applying the non-uniform scale would produce the
165
// correct bounds. However, it could very well be rotated and so we first convert to
166
// a Sphere, and then back to an Aabb to find the conservative min and max points.
167
let sphere = Sphere {
168
center: Vec3A::from(transform.transform_point(Vec3::from(aabb.center))),
169
radius: transform.radius_vec3a(aabb.half_extents),
170
};
171
let aabb = Aabb::from(sphere);
172
min = min.min(aabb.min());
173
max = max.max(aabb.max());
174
}
175
176
let size = (max - min).length();
177
let aabb = Aabb::from_min_max(Vec3::from(min), Vec3::from(max));
178
179
info!("Spawning a controllable 3D perspective camera");
180
let mut projection = PerspectiveProjection::default();
181
projection.far = projection.far.max(size * 10.0);
182
183
let walk_speed = size * 3.0;
184
let camera_controller = FreeCamera {
185
walk_speed,
186
run_speed: 3.0 * walk_speed,
187
..default()
188
};
189
190
// Display the controls of the scene viewer
191
info!("{}", camera_controller);
192
info!("{}", *scene_handle);
193
194
let mut camera = commands.spawn((
195
Camera3d::default(),
196
Projection::from(projection),
197
Transform::from_translation(
198
Vec3::from(aabb.center) + size * (args.rotation() * Vec3::new(0.5, 0.25, 0.5)),
199
)
200
.looking_at(Vec3::from(aabb.center), Vec3::Y),
201
Camera {
202
is_active: false,
203
..default()
204
},
205
EnvironmentMapLight {
206
diffuse_map: asset_server
207
.load("assets/environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
208
specular_map: asset_server
209
.load("assets/environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
210
intensity: 150.0,
211
rotation: args.rotation(),
212
..default()
213
},
214
camera_controller,
215
));
216
217
// If occlusion culling was requested, include the relevant components.
218
// The Z-prepass is currently required.
219
if args.occlusion_culling == Some(true) {
220
camera.insert((DepthPrepass, OcclusionCulling));
221
}
222
223
// If the depth prepass was requested, include it.
224
if args.depth_prepass == Some(true) {
225
camera.insert(DepthPrepass);
226
}
227
228
// If deferred shading was requested, include the prepass.
229
if args.deferred == Some(true) {
230
camera
231
.insert(Msaa::Off)
232
.insert(DepthPrepass)
233
.insert(DeferredPrepass);
234
}
235
236
// Spawn a default light if the scene does not have one
237
if !scene_handle.has_light || args.add_light == Some(true) {
238
info!("Spawning a directional light");
239
let mut light = commands.spawn((
240
DirectionalLight::default(),
241
Transform::from_translation(args.rotation() * Vec3::new(1.0, 1.0, 0.0))
242
.looking_at(Vec3::ZERO, Vec3::Y),
243
));
244
if args.occlusion_culling == Some(true) {
245
light.insert(OcclusionCulling);
246
}
247
248
scene_handle.has_light = true;
249
}
250
}
251
}
252
253