use std::ops::Sub; use bevy::{ color::palettes::css, diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}, math::Vec3A, prelude::*, render::primitives::Aabb, }; use bevy_mod_raycast::prelude::*; fn main() { App::new() .add_plugins(( DefaultPlugins.set(bevy_mod_raycast::low_latency_window_plugin()), FrameTimeDiagnosticsPlugin, DeferredRaycastingPlugin::::default(), )) .add_systems(Startup, (setup_scene, setup_ui)) .add_systems(First, update_status) .add_systems(Update, (update_fps, make_scene_pickable)) .run(); } // This is a unit struct we will use to mark our generic `RaycastMesh`s and `RaycastSource` as part // of the same group, or "RaycastSet". For more complex use cases, you might use this to associate // some meshes with one ray casting source, and other meshes with a different ray casting source." #[derive(Reflect)] struct MyRaycastSet; fn setup_scene(mut commands: Commands, asset_server: Res) { commands.insert_resource(RaycastPluginState::::default().with_debug_cursor()); commands.spawn(DirectionalLightBundle { transform: Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, 20.0, 20.0, 0.0)), directional_light: DirectionalLight::default(), ..default() }); commands.spawn(( Camera3dBundle::default(), RaycastSource::::new_cursor(), )); let mut i = 0; for x in -2..=2 { for k in -210..-10 { commands.spawn((bevy::prelude::SceneBundle { scene: asset_server.load("models/monkey/Monkey.gltf#Scene0"), transform: Transform::from_translation(Vec3::new( x as f32 * k as f32 * -2.0, 0.0, k as f32 * 3.0, )) .with_scale(Vec3::splat((k as f32).abs().sub(5.0)) * 0.6), ..default() },)); i += 1; } } info!("Raycasting against {i} meshes"); } #[allow(clippy::type_complexity)] fn make_scene_pickable( mut commands: Commands, mesh_query: Query>, Without>)>, ) { for entity in &mesh_query { commands .entity(entity) .insert(RaycastMesh::::default()); // Make this mesh ray cast-able } } // Set up UI to show status of bounding volume fn setup_ui(mut commands: Commands) { let text_section = |text: &'static str| TextSection { value: text.into(), style: TextStyle { font_size: 40.0, color: Color::WHITE, ..default() }, }; commands .spawn(NodeBundle { style: Style { align_self: AlignSelf::FlexStart, flex_direction: FlexDirection::Column, ..default() }, background_color: Color::NONE.into(), ..default() }) .with_children(|ui| { ui.spawn(TextBundle::from_sections([text_section( "Toggle with number keys", )])); ui.spawn(TextBundle::from_sections([ text_section("(1) AABB Culling: "), text_section(""), ])) .insert(BoundVolStatus); ui.spawn(TextBundle::from_sections([ text_section("(2) Early Exit: "), text_section(""), ])) .insert(EarlyExitStatus); ui.spawn(TextBundle::from_sections([ text_section("FPS: "), text_section(""), ])) .insert(FpsText); }); } #[derive(Component)] struct BoundVolStatus; #[derive(Component)] struct EarlyExitStatus; #[derive(Component)] struct FpsText; // Insert or remove aabb components from the meshes being raycasted on. fn update_status( mut commands: Commands, keyboard: Res>, mut enabled: Local>, // Bounding toggle mut bound_status: Query<&mut Text, (With, Without)>, mut aabbs: Query<(Entity, &mut Aabb), With>>, // Early exit toggle mut exit_status: Query<&mut Text, (Without, With)>, mut sources: Query<&mut RaycastSource>, ) { if enabled.is_none() { *enabled = Some((true, true)); } let enabled = enabled.as_mut().unwrap(); let bool_to_text = |is_enabled: bool, text: &mut Text| { if is_enabled { text.sections[1].value = "ON".to_string(); text.sections[1].style.color = css::GREEN.into(); } else { text.sections[1].value = "OFF".to_string(); text.sections[1].style.color = css::RED.into(); } }; if keyboard.just_pressed(KeyCode::Digit1) { enabled.0 = !enabled.0; for (entity, mut aabb) in &mut aabbs { if enabled.0 { // bevy's built in systems will see that the Aabb is missing and make a valid one commands.entity(entity).remove::(); } else { // infinite AABB to make AABB useless aabb.half_extents = Vec3A::ONE * f32::MAX; } } } bool_to_text(enabled.0, bound_status.single_mut().as_mut()); if keyboard.just_pressed(KeyCode::Digit2) { enabled.1 = !enabled.1; for mut source in &mut sources { source.should_early_exit = enabled.1; } } bool_to_text(enabled.1, exit_status.single_mut().as_mut()); } fn update_fps(diagnostics: Res, mut query: Query<&mut Text, With>) { for mut text in &mut query { if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) { if let Some(average) = fps.smoothed() { // Update the value of the second section text.sections[1].value = format!("{:.2}", average); } } } }