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::<MyRaycastSet>::default(),
))
.add_systems(Startup, (setup_scene, setup_ui))
.add_systems(First, update_status)
.add_systems(Update, (update_fps, make_scene_pickable))
.run();
}
#[derive(Reflect)]
struct MyRaycastSet;
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.insert_resource(RaycastPluginState::<MyRaycastSet>::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::<MyRaycastSet>::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<Entity, (With<Handle<Mesh>>, Without<RaycastMesh<MyRaycastSet>>)>,
) {
for entity in &mesh_query {
commands
.entity(entity)
.insert(RaycastMesh::<MyRaycastSet>::default()); }
}
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;
fn update_status(
mut commands: Commands,
keyboard: Res<ButtonInput<KeyCode>>,
mut enabled: Local<Option<(bool, bool)>>,
mut bound_status: Query<&mut Text, (With<BoundVolStatus>, Without<EarlyExitStatus>)>,
mut aabbs: Query<(Entity, &mut Aabb), With<RaycastMesh<MyRaycastSet>>>,
mut exit_status: Query<&mut Text, (Without<BoundVolStatus>, With<EarlyExitStatus>)>,
mut sources: Query<&mut RaycastSource<MyRaycastSet>>,
) {
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 {
commands.entity(entity).remove::<Aabb>();
} else {
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<DiagnosticsStore>, mut query: Query<&mut Text, With<FpsText>>) {
for mut text in &mut query {
if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
if let Some(average) = fps.smoothed() {
text.sections[1].value = format!("{:.2}", average);
}
}
}
}