//! Viewport shading modes: Lit, Unlit (base color), and Collider-only. use bevy::prelude::*; use crate::infra::EditorOnly; use crate::selection_outline::SelectionOutlineShell; use crate::state::scene_tools_active; use crate::visualizers::EditorVisualizerProxy; /// Viewport display mode (editor-only; does not affect saved scenes or runtime rendering). #[derive(Resource, Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum EditorViewportMode { #[default] Lit, /// Base-color albedo without lighting response (debug). Unlit, /// Hide scene meshes; emphasize collider gizmos. Collider, } #[derive(Component)] struct ViewportMaterialOverride { original: Handle, } #[derive(Component)] struct ViewportMeshHiddenByMode; pub struct EditorViewportModePlugin; impl Plugin for EditorViewportModePlugin { fn build(&self, app: &mut App) { app.init_resource::().add_systems( Update, ( apply_viewport_mode_on_change, apply_viewport_mode_for_new_meshes, reconcile_lit_material_overrides, reconcile_lit_mode_hidden_meshes, ) .chain() .run_if(scene_tools_active), ); } } fn apply_viewport_mode_on_change( mode: Res, mut last: Local>, mut commands: Commands, mut materials: ResMut>, meshes: Query< Entity, ( With, Without, Without, Without, ), >, mesh_materials: Query<(Entity, &MeshMaterial3d)>, overrides: Query<(Entity, &ViewportMaterialOverride)>, hidden: Query>, mut visibility: Query<&mut Visibility>, ) { let mode = *mode; if *last == Some(mode) { return; } *last = Some(mode); restore_lit_state(&mut commands, &overrides, &hidden, &mut visibility); match mode { EditorViewportMode::Lit => {} EditorViewportMode::Unlit => { for (entity, material) in &mesh_materials { apply_unlit_override(entity, material, &mut commands, &mut materials); } } EditorViewportMode::Collider => { for entity in meshes.iter() { hide_mesh_for_collider_mode(entity, &mut commands, &mut visibility); } } } } fn apply_viewport_mode_for_new_meshes( mode: Res, mut commands: Commands, mut materials: ResMut>, refreshed_materials: Query< (Entity, &MeshMaterial3d), ( Changed>, With, ), >, added_meshes: Query< Entity, ( Added, Without, Without, Without, Without, ), >, added_materials: Query< (Entity, &MeshMaterial3d), ( Or<( Added>, Changed>, )>, Without, ), >, mut visibility: Query<&mut Visibility>, ) { match *mode { EditorViewportMode::Lit => {} EditorViewportMode::Unlit => { for (entity, material) in &refreshed_materials { commands.entity(entity).remove::(); apply_unlit_override(entity, material, &mut commands, &mut materials); } for (entity, material) in &added_materials { apply_unlit_override(entity, material, &mut commands, &mut materials); } } EditorViewportMode::Collider => { for entity in added_meshes.iter() { hide_mesh_for_collider_mode(entity, &mut commands, &mut visibility); } } } } fn restore_lit_state( commands: &mut Commands, overrides: &Query<(Entity, &ViewportMaterialOverride)>, hidden: &Query>, visibility: &mut Query<&mut Visibility>, ) { for (entity, backup) in overrides.iter() { commands .entity(entity) .insert(MeshMaterial3d(backup.original.clone())); commands.entity(entity).remove::(); } for entity in hidden.iter() { if let Ok(mut vis) = visibility.get_mut(entity) { *vis = Visibility::Inherited; } commands.entity(entity).remove::(); } } fn apply_unlit_override( entity: Entity, material: &MeshMaterial3d, commands: &mut Commands, materials: &mut Assets, ) { let Some(source) = materials.get(&material.0) else { return; }; let unlit = materials.add(StandardMaterial { base_color: source.base_color, base_color_texture: source.base_color_texture.clone(), unlit: true, metallic: 0.0, perceptual_roughness: 1.0, ..default() }); let original = material.0.clone(); commands .entity(entity) .insert((ViewportMaterialOverride { original }, MeshMaterial3d(unlit))); } fn hide_mesh_for_collider_mode( entity: Entity, commands: &mut Commands, visibility: &mut Query<&mut Visibility>, ) { if let Ok(mut vis) = visibility.get_mut(entity) { *vis = Visibility::Hidden; } commands.entity(entity).insert(ViewportMeshHiddenByMode); } /// Drops stale unlit overrides after material re-hydration while Lit is active. fn reconcile_lit_material_overrides( mode: Res, mut commands: Commands, stale: Query< Entity, ( With, Changed>, ), >, ) { if *mode != EditorViewportMode::Lit { return; } for entity in &stale { commands.entity(entity).remove::(); } } /// Clears stale Collider-mode hides while Lit is active (e.g. after hot reload). fn reconcile_lit_mode_hidden_meshes( mode: Res, mut commands: Commands, overrides: Query<(Entity, &ViewportMaterialOverride)>, hidden: Query>, mut visibility: Query<&mut Visibility>, ) { if *mode != EditorViewportMode::Lit { return; } if overrides.is_empty() && hidden.is_empty() { return; } restore_lit_state(&mut commands, &overrides, &hidden, &mut visibility); }