bevy/examples/tools/scene_viewer/main.rs
Joona Aalto 3ada15ee1c
Add more Glam types and constructors to prelude (#16261)
# Objective

Glam has some common and useful types and helpers that are not in the
prelude of `bevy_math`. This includes shorthand constructors like
`vec3`, or even `Vec3A`, the aligned version of `Vec3`.

```rust
// The "normal" way to create a 3D vector
let vec = Vec3::new(2.0, 1.0, -3.0);

// Shorthand version
let vec = vec3(2.0, 1.0, -3.0);
```

## Solution

Add the following types and methods to the prelude:

- `vec2`, `vec3`, `vec3a`, `vec4`
- `uvec2`, `uvec3`, `uvec4`
- `ivec2`, `ivec3`, `ivec4`
- `bvec2`, `bvec3`, `bvec3a`, `bvec4`, `bvec4a`
- `mat2`, `mat3`, `mat3a`, `mat4`
- `quat` (not sure if anyone uses this, but for consistency)
- `Vec3A`
- `BVec3A`, `BVec4A`
- `Mat3A`

I did not add the u16, i16, or f64 variants like `dvec2`, since there
are currently no existing types like those in the prelude.

The shorthand constructors are currently used a lot in some places in
Bevy, and not at all in others. In a follow-up, we might want to
consider if we have a preference for the shorthand, and make a PR to
change the codebase to use it more consistently.
2024-11-11 18:47:16 +00:00

159 lines
5.5 KiB
Rust

//! A simple glTF scene viewer made with Bevy.
//!
//! Just run `cargo run --release --example scene_viewer /path/to/model.gltf`,
//! replacing the path as appropriate.
//! In case of multiple scenes, you can select which to display by adapting the file path: `/path/to/model.gltf#Scene1`.
//! With no arguments it will load the `FlightHelmet` glTF model from the repository assets subdirectory.
//!
//! If you want to hot reload asset changes, enable the `file_watcher` cargo feature.
use bevy::{
prelude::*,
render::primitives::{Aabb, Sphere},
};
#[path = "../../helpers/camera_controller.rs"]
mod camera_controller;
#[cfg(feature = "animation")]
mod animation_plugin;
mod morph_viewer_plugin;
mod scene_viewer_plugin;
use camera_controller::{CameraController, CameraControllerPlugin};
use morph_viewer_plugin::MorphViewerPlugin;
use scene_viewer_plugin::{SceneHandle, SceneViewerPlugin};
fn main() {
let mut app = App::new();
app.add_plugins((
DefaultPlugins
.set(WindowPlugin {
primary_window: Some(Window {
title: "bevy scene viewer".to_string(),
..default()
}),
..default()
})
.set(AssetPlugin {
file_path: std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string()),
..default()
}),
CameraControllerPlugin,
SceneViewerPlugin,
MorphViewerPlugin,
))
.add_systems(Startup, setup)
.add_systems(PreUpdate, setup_scene_after_load);
#[cfg(feature = "animation")]
app.add_plugins(animation_plugin::AnimationManipulationPlugin);
app.run();
}
fn parse_scene(scene_path: String) -> (String, usize) {
if scene_path.contains('#') {
let gltf_and_scene = scene_path.split('#').collect::<Vec<_>>();
if let Some((last, path)) = gltf_and_scene.split_last() {
if let Some(index) = last
.strip_prefix("Scene")
.and_then(|index| index.parse::<usize>().ok())
{
return (path.join("#"), index);
}
}
}
(scene_path, 0)
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let scene_path = std::env::args()
.nth(1)
.unwrap_or_else(|| "assets/models/FlightHelmet/FlightHelmet.gltf".to_string());
info!("Loading {}", scene_path);
let (file_path, scene_index) = parse_scene(scene_path);
commands.insert_resource(SceneHandle::new(asset_server.load(file_path), scene_index));
}
fn setup_scene_after_load(
mut commands: Commands,
mut setup: Local<bool>,
mut scene_handle: ResMut<SceneHandle>,
asset_server: Res<AssetServer>,
meshes: Query<(&GlobalTransform, Option<&Aabb>), With<Mesh3d>>,
) {
if scene_handle.is_loaded && !*setup {
*setup = true;
// Find an approximate bounding box of the scene from its meshes
if meshes.iter().any(|(_, maybe_aabb)| maybe_aabb.is_none()) {
return;
}
let mut min = Vec3A::splat(f32::MAX);
let mut max = Vec3A::splat(f32::MIN);
for (transform, maybe_aabb) in &meshes {
let aabb = maybe_aabb.unwrap();
// If the Aabb had not been rotated, applying the non-uniform scale would produce the
// correct bounds. However, it could very well be rotated and so we first convert to
// a Sphere, and then back to an Aabb to find the conservative min and max points.
let sphere = Sphere {
center: Vec3A::from(transform.transform_point(Vec3::from(aabb.center))),
radius: transform.radius_vec3a(aabb.half_extents),
};
let aabb = Aabb::from(sphere);
min = min.min(aabb.min());
max = max.max(aabb.max());
}
let size = (max - min).length();
let aabb = Aabb::from_min_max(Vec3::from(min), Vec3::from(max));
info!("Spawning a controllable 3D perspective camera");
let mut projection = PerspectiveProjection::default();
projection.far = projection.far.max(size * 10.0);
let walk_speed = size * 3.0;
let camera_controller = CameraController {
walk_speed,
run_speed: 3.0 * walk_speed,
..default()
};
// Display the controls of the scene viewer
info!("{}", camera_controller);
info!("{}", *scene_handle);
commands.spawn((
Camera3d::default(),
Projection::from(projection),
Transform::from_translation(Vec3::from(aabb.center) + size * Vec3::new(0.5, 0.25, 0.5))
.looking_at(Vec3::from(aabb.center), Vec3::Y),
Camera {
is_active: false,
..default()
},
EnvironmentMapLight {
diffuse_map: asset_server
.load("assets/environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server
.load("assets/environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 150.0,
..default()
},
camera_controller,
));
// Spawn a default light if the scene does not have one
if !scene_handle.has_light {
info!("Spawning a directional light");
commands.spawn((
DirectionalLight::default(),
Transform::from_xyz(1.0, 1.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
));
scene_handle.has_light = true;
}
}
}