 54006b107b
			
		
	
	
		54006b107b
		
			
		
	
	
	
	
		
			
			# Objective A big step in the migration to required components: meshes and materials! ## Solution As per the [selected proposal](https://hackmd.io/@bevy/required_components/%2Fj9-PnF-2QKK0on1KQ29UWQ): - Deprecate `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle`. - Add `Mesh2d` and `Mesh3d` components, which wrap a `Handle<Mesh>`. - Add `MeshMaterial2d<M: Material2d>` and `MeshMaterial3d<M: Material>`, which wrap a `Handle<M>`. - Meshes *without* a mesh material should be rendered with a default material. The existence of a material is determined by `HasMaterial2d`/`HasMaterial3d`, which is required by `MeshMaterial2d`/`MeshMaterial3d`. This gets around problems with the generics. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, previously nothing was rendered. Now, it renders a white default `ColorMaterial` in 2D and a `StandardMaterial` in 3D (this can be overridden). Below, only every other entity has a material:   Why white? This is still open for discussion, but I think white makes sense for a *default* material, while *invalid* asset handles pointing to nothing should have something like a pink material to indicate that something is broken (I don't handle that in this PR yet). This is kind of a mix of Godot and Unity: Godot just renders a white material for non-existent materials, while Unity renders nothing when no materials exist, but renders pink for invalid materials. I can also change the default material to pink if that is preferable though. ## Testing I ran some 2D and 3D examples to test if anything changed visually. I have not tested all examples or features yet however. If anyone wants to test more extensively, it would be appreciated! ## Implementation Notes - The relationship between `bevy_render` and `bevy_pbr` is weird here. `bevy_render` needs `Mesh3d` for its own systems, but `bevy_pbr` has all of the material logic, and `bevy_render` doesn't depend on it. I feel like the two crates should be refactored in some way, but I think that's out of scope for this PR. - I didn't migrate meshlets to required components yet. That can probably be done in a follow-up, as this is already a huge PR. - It is becoming increasingly clear to me that we really, *really* want to disallow raw asset handles as components. They caused me a *ton* of headache here already, and it took me a long time to find every place that queried for them or inserted them directly on entities, since there were no compiler errors for it. If we don't remove the `Component` derive, I expect raw asset handles to be a *huge* footgun for users as we transition to wrapper components, especially as handles as components have been the norm so far. I personally consider this to be a blocker for 0.15: we need to migrate to wrapper components for asset handles everywhere, and remove the `Component` derive. Also see https://github.com/bevyengine/bevy/issues/14124. --- ## Migration Guide Asset handles for meshes and mesh materials must now be wrapped in the `Mesh2d` and `MeshMaterial2d` or `Mesh3d` and `MeshMaterial3d` components for 2D and 3D respectively. Raw handles as components no longer render meshes. Additionally, `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle` have been deprecated. Instead, use the mesh and material components directly. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, a white default material is now used. Previously, nothing was rendered if the material was missing. The `WithMesh2d` and `WithMesh3d` query filter type aliases have also been removed. Simply use `With<Mesh2d>` or `With<Mesh3d>`. --------- Co-authored-by: Tim Blackbird <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
		
			
				
	
	
		
			165 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
		
			5.7 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::{
 | |
|     math::Vec3A,
 | |
|     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((
 | |
|             Camera3dBundle {
 | |
|                 projection: projection.into(),
 | |
|                 transform: 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: Camera {
 | |
|                     is_active: false,
 | |
|                     ..default()
 | |
|                 },
 | |
|                 ..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;
 | |
|         }
 | |
|     }
 | |
| }
 |