use avian3d::prelude::*; use bevy::{ asset::LoadState, color::palettes, gltf::{Gltf, GltfMesh}, math::Vec3Swizzles, pbr::NotShadowCaster, prelude::*, time::common_conditions::on_timer, }; use polyanya::Triangulation; use rand::Rng; use std::{f32::consts::FRAC_PI_2, time::Duration}; use vleue_navigator::{ prelude::{NavMeshBundle, NavMeshSettings, NavMeshUpdateMode, NavmeshUpdaterPlugin}, NavMesh, NavMeshDebug, VleueNavigatorPlugin, }; #[derive(Component)] struct Obstacle(Timer); fn main() { let mut app = App::new(); app.insert_resource(Msaa::default()) .insert_resource(ClearColor(Color::srgb(0., 0., 0.01))) .add_plugins(( DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { title: "Navmesh with Polyanya".to_string(), fit_canvas_to_parent: true, ..default() }), ..default() }), PhysicsPlugins::default().with_length_unit(20.0), VleueNavigatorPlugin, NavmeshUpdaterPlugin::::default(), )) .init_state::() .add_systems(OnEnter(AppState::Setup), setup) .add_systems(Update, check_textures.run_if(in_state(AppState::Setup))) .add_systems(OnExit(AppState::Setup), setup_scene) .add_systems( Update, ( give_target_auto, move_object, move_hover, target_activity, display_navigator_path, despawn_obstacles, ) .run_if(in_state(AppState::Playing)), ) .add_systems( Update, spawn_obstacles.run_if(on_timer(Duration::from_secs_f32(0.5))), ) .add_systems( Update, refresh_path.run_if(on_timer(Duration::from_secs_f32(0.1))), ); let mut config_store = app .world_mut() .get_resource_mut::() .unwrap(); for (_, config, _) in config_store.iter_mut() { config.depth_bias = -1.0; } app.run(); } #[derive(Debug, Clone, PartialEq, Eq, Hash, States, Default)] enum AppState { #[default] Setup, Playing, } #[derive(Resource, Default, Deref)] struct GltfHandle(Handle); fn setup(mut commands: Commands, asset_server: Res) { commands.insert_resource(GltfHandle(asset_server.load("meshes/navmesh.glb"))); commands.spawn(DirectionalLightBundle { directional_light: DirectionalLight { illuminance: 3000.0, shadows_enabled: true, ..default() }, transform: Transform::default().looking_at(Vec3::new(-1.0, -2.5, -1.5), Vec3::Y), ..default() }); commands.spawn(Camera3dBundle { camera: Camera { #[cfg(not(target_arch = "wasm32"))] hdr: true, ..default() }, transform: Transform::from_xyz(0.0, 70.0, 5.0) .looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), ..Default::default() }); } fn check_textures( mut next_state: ResMut>, gltf: ResMut, asset_server: Res, ) { if let Some(LoadState::Loaded) = asset_server.get_load_state(gltf.id()) { next_state.set(AppState::Playing); } } #[derive(Component)] struct Path { current: Vec3, next: Vec, } #[derive(Component)] struct Object(Option); #[derive(Component)] struct Target; #[derive(Component)] struct Hover(Vec2); fn setup_scene( mut commands: Commands, gltf: Res, gltfs: Res>, gltf_meshes: Res>, mut meshes: ResMut>, mut materials: ResMut>, ) { let mut material: StandardMaterial = Color::Srgba(palettes::css::ALICE_BLUE).into(); material.perceptual_roughness = 1.0; let ground_material = materials.add(material); if let Some(gltf) = gltfs.get(gltf.id()) { let mesh = gltf_meshes.get(&gltf.named_meshes["obstacles"]).unwrap(); let mut material: StandardMaterial = Color::Srgba(palettes::css::GRAY).into(); material.perceptual_roughness = 1.0; commands.spawn(( PbrBundle { mesh: mesh.primitives[0].mesh.clone(), material: materials.add(material), ..default() }, RigidBody::Static, ColliderConstructor::TrimeshFromMesh, )); let mesh = gltf_meshes.get(&gltf.named_meshes["plane"]).unwrap(); commands.spawn(PbrBundle { mesh: mesh.primitives[0].mesh.clone(), transform: Transform::from_xyz(0.0, 0.01, 0.0), material: ground_material.clone(), ..default() }); } if let Some(gltf) = gltfs.get(gltf.id()) { { let navmesh = vleue_navigator::NavMesh::from_bevy_mesh( meshes .get( &gltf_meshes .get(&gltf.named_meshes["navmesh"]) .unwrap() .primitives[0] .mesh, ) .unwrap(), ); let mut material: StandardMaterial = Color::Srgba(palettes::css::ANTIQUE_WHITE).into(); material.unlit = true; commands.spawn(( NavMeshBundle { settings: NavMeshSettings { fixed: Triangulation::from_mesh(navmesh.get().as_ref(), 0), build_timeout: Some(5.0), upward_shift: 0.5, ..default() }, transform: Transform::from_rotation(Quat::from_rotation_x(FRAC_PI_2)), update_mode: NavMeshUpdateMode::Direct, ..NavMeshBundle::with_default_id() }, NavMeshDebug(palettes::tailwind::RED_400.into()), )); } commands .spawn(( PbrBundle { mesh: meshes.add(Mesh::from(Capsule3d { ..default() })), material: materials.add(StandardMaterial { base_color: palettes::css::BLUE.into(), emissive: (palettes::css::BLUE * 5.0).into(), ..default() }), transform: Transform::from_xyz(-1.0, 0.0, -2.0), ..Default::default() }, Object(None), NotShadowCaster, )) .with_children(|object| { object.spawn(PointLightBundle { point_light: PointLight { color: palettes::css::BLUE.into(), range: 500.0, intensity: 100000.0, shadows_enabled: true, ..default() }, transform: Transform::from_xyz(0.0, 1.2, 0.0), ..default() }); }); } } fn give_target_auto( mut commands: Commands, mut object_query: Query<(Entity, &Transform, &mut Object), Without>, navmeshes: Res>, mut meshes: ResMut>, mut materials: ResMut>, ) { for (entity, transform, mut object) in object_query.iter_mut() { let Some(navmesh) = navmeshes.get(&Handle::default()) else { continue; }; let mut x = 0.0; let mut z = 0.0; for _ in 0..50 { x = rand::thread_rng().gen_range(-50.0..50.0); z = rand::thread_rng().gen_range(-25.0..25.0); if navmesh.transformed_is_in_mesh(Vec3::new(x, 0.0, z)) { break; } } let Some(path) = navmesh.transformed_path(transform.translation, Vec3::new(x, 0.0, z)) else { break; }; if let Some((first, remaining)) = path.path.split_first() { let mut remaining = remaining.to_vec(); remaining.reverse(); let target_id = commands .spawn(( PbrBundle { mesh: meshes.add(Mesh::from(Sphere { radius: 0.5 })), material: materials.add(StandardMaterial { base_color: palettes::css::RED.into(), emissive: (palettes::css::RED * 5.0).into(), ..default() }), transform: Transform::from_xyz(x, 0.0, z), ..Default::default() }, NotShadowCaster, Target, )) .with_children(|target| { target.spawn(PointLightBundle { point_light: PointLight { color: palettes::css::RED.into(), shadows_enabled: true, range: 10.0, ..default() }, transform: Transform::from_xyz(0.0, 1.5, 0.0), ..default() }); }) .id(); commands.entity(entity).insert(Path { current: *first, next: remaining, }); object.0 = Some(target_id); } } } fn refresh_path( mut object_query: Query<(&Transform, &mut Path)>, target: Query<&Transform, With>, navmeshes: Res>, ) { for (transform, mut path) in &mut object_query { let navmesh = navmeshes.get(&Handle::default()).unwrap(); let Some(new_path) = navmesh.transformed_path(transform.translation, target.single().translation) else { break; }; if let Some((first, remaining)) = new_path.path.split_first() { let mut remaining = remaining.to_vec(); remaining.reverse(); *path = Path { current: *first, next: remaining, }; } } } fn move_object( mut commands: Commands, mut object_query: Query<(&mut Transform, &mut Path, Entity, &mut Object)>, time: Res