//! An FBX scene viewer plugin. Provides controls for directional lighting, material inspection, and scene navigation. //! To use in your own application: //! - Copy the code for the `FbxViewerPlugin` and add the plugin to your App. //! - Insert an initialized `FbxSceneHandle` resource into your App's `AssetServer`. use bevy::{fbx::Fbx, input::common_conditions::input_just_pressed, prelude::*, scene::InstanceId}; use std::{f32::consts::*, fmt}; use super::camera_controller::*; #[derive(Resource)] pub struct FbxSceneHandle { pub fbx_handle: Handle, instance_id: Option, pub is_loaded: bool, pub has_light: bool, } impl FbxSceneHandle { pub fn new(fbx_handle: Handle) -> Self { Self { fbx_handle, instance_id: None, is_loaded: false, has_light: false, } } } const INSTRUCTIONS: &str = r#" FBX Scene Controls: L - animate light direction U - toggle shadows B - toggle bounding boxes C - cycle through the camera controller and any cameras loaded from the scene M - toggle material debug info I - print FBX asset information "#; impl fmt::Display for FbxSceneHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{INSTRUCTIONS}") } } pub struct FbxViewerPlugin; impl Plugin for FbxViewerPlugin { fn build(&self, app: &mut App) { app.init_resource::() .init_resource::() .add_systems(PreUpdate, fbx_load_check) .add_systems( Update, ( update_lights, camera_tracker, toggle_bounding_boxes.run_if(input_just_pressed(KeyCode::KeyB)), toggle_material_debug.run_if(input_just_pressed(KeyCode::KeyM)), print_fbx_info.run_if(input_just_pressed(KeyCode::KeyI)), ), ); } } #[derive(Resource, Default)] struct MaterialDebugInfo { enabled: bool, } fn toggle_bounding_boxes(mut config: ResMut) { config.config_mut::().1.draw_all ^= true; } fn toggle_material_debug(mut debug_info: ResMut) { debug_info.enabled = !debug_info.enabled; if debug_info.enabled { info!("Material debug info enabled - press M again to disable"); } else { info!("Material debug info disabled"); } } fn print_fbx_info( fbx_assets: Res>, scene_handle: Res, materials: Query<&MeshMaterial3d>, meshes: Query<&Mesh3d>, standard_materials: Res>, ) { if let Some(fbx) = fbx_assets.get(&scene_handle.fbx_handle) { info!("=== FBX Asset Information ==="); info!("Meshes: {}", fbx.meshes.len()); info!("Materials: {}", fbx.materials.len()); info!("Nodes: {}", fbx.nodes.len()); info!("Skins: {}", fbx.skins.len()); info!("Animation clips: {}", fbx.animations.len()); // Print material information info!("=== Material Details ==="); for (i, material_handle) in fbx.materials.iter().enumerate() { if let Some(material) = standard_materials.get(material_handle) { info!( "Material {}: base_color={:?}, metallic={}, roughness={}", i, material.base_color, material.metallic, material.perceptual_roughness ); if material.base_color_texture.is_some() { info!(" - Has base color texture"); } if material.normal_map_texture.is_some() { info!(" - Has normal map"); } if material.metallic_roughness_texture.is_some() { info!(" - Has metallic/roughness texture"); } if material.emissive_texture.is_some() { info!(" - Has emissive texture"); } if material.occlusion_texture.is_some() { info!(" - Has occlusion texture"); } } } info!("=== Scene Statistics ==="); info!("Total mesh entities: {}", meshes.iter().count()); info!("Total material entities: {}", materials.iter().count()); } else { info!("FBX asset not yet loaded"); } } fn fbx_load_check( asset_server: Res, mut scenes: ResMut>, fbx_assets: Res>, mut scene_handle: ResMut, mut scene_spawner: ResMut, ) { match scene_handle.instance_id { None => { if asset_server .load_state(&scene_handle.fbx_handle) .is_loaded() { let fbx = fbx_assets.get(&scene_handle.fbx_handle).unwrap(); info!("FBX loaded successfully!"); info!( "Found {} meshes, {} materials, {} nodes", fbx.meshes.len(), fbx.materials.len(), fbx.nodes.len() ); // Check if the FBX scene has lights if let Some(scene_handle_ref) = fbx.scenes.first() { let scene = scenes.get_mut(scene_handle_ref).unwrap(); let mut query = scene .world .query::<(Option<&DirectionalLight>, Option<&PointLight>)>(); scene_handle.has_light = query.iter(&scene.world).any( |(maybe_directional_light, maybe_point_light)| { maybe_directional_light.is_some() || maybe_point_light.is_some() }, ); scene_handle.instance_id = Some(scene_spawner.spawn(scene_handle_ref.clone_weak())); info!("Spawning FBX scene..."); } else { warn!("FBX file contains no scenes!"); } } } Some(instance_id) if !scene_handle.is_loaded => { if scene_spawner.instance_is_ready(instance_id) { info!("FBX scene loaded and ready!"); scene_handle.is_loaded = true; } } Some(_) => {} } } fn update_lights( key_input: Res>, time: Res