1#![doc = include_str!("../README.md")]
2
3use std::{marker::PhantomData, sync::Arc};
4
5use bevy_app::{FixedPreUpdate, Plugin};
6use bevy_asset::{Asset, AssetApp, Handle};
7use bevy_ecs::{
8 component::Component,
9 entity::Entity,
10 intern::Interned,
11 schedule::{IntoScheduleConfigs, ScheduleLabel, SystemSet},
12 system::{Query, Res},
13};
14use bevy_platform::collections::HashMap;
15use bevy_reflect::TypePath;
16use bevy_time::Time;
17use coords::{CoordinateSystem, ThreeD, TwoD};
18use landmass::{AgentId, AnimationLinkId, CharacterId, IslandId};
19
20mod agent;
21mod character;
22mod island;
23mod landmass_structs;
24mod link;
25
26pub use landmass::{
27 ArchipelagoOptions, FindPathError, FromAgentRadius, HeightNavigationMesh,
28 HeightPolygon, NavigationMesh, PathStep, PointSampleDistance3d,
29 SamplePointError, SetTypeIndexCostError, ValidNavigationMesh,
30 ValidationError,
31};
32
33pub use agent::*;
34pub use character::*;
35pub use island::*;
36pub use landmass_structs::*;
37pub use link::*;
38
39pub mod coords;
40pub mod debug;
41
42#[cfg(feature = "mesh-utils")]
43pub mod nav_mesh;
44
45pub mod prelude {
46 pub use crate::Agent2dBundle;
47 pub use crate::Agent3dBundle;
48 pub use crate::AgentDesiredVelocity2d;
49 pub use crate::AgentDesiredVelocity3d;
50 pub use crate::AgentSettings;
51 pub use crate::AgentState;
52 pub use crate::AgentTarget2d;
53 pub use crate::AgentTarget3d;
54 pub use crate::AnimationLink2d;
55 pub use crate::AnimationLink2dBundle;
56 pub use crate::AnimationLink3d;
57 pub use crate::AnimationLink3dBundle;
58 pub use crate::Archipelago2d;
59 pub use crate::Archipelago3d;
60 pub use crate::ArchipelagoOptions;
61 pub use crate::ArchipelagoRef2d;
62 pub use crate::ArchipelagoRef3d;
63 pub use crate::Character2dBundle;
64 pub use crate::Character3dBundle;
65 pub use crate::CharacterSettings;
66 pub use crate::FromAgentRadius;
67 pub use crate::HeightNavigationMesh2d;
68 pub use crate::HeightNavigationMesh3d;
69 pub use crate::Island;
70 pub use crate::Island2dBundle;
71 pub use crate::Island3dBundle;
72 pub use crate::Landmass2dPlugin;
73 pub use crate::Landmass3dPlugin;
74 pub use crate::LandmassSystems;
75 pub use crate::NavMesh2d;
76 pub use crate::NavMesh3d;
77 pub use crate::NavMeshHandle2d;
78 pub use crate::NavMeshHandle3d;
79 pub use crate::NavigationMesh2d;
80 pub use crate::NavigationMesh3d;
81 pub use crate::ReachedAnimationLink2d;
82 pub use crate::ReachedAnimationLink3d;
83 pub use crate::ValidNavigationMesh2d;
84 pub use crate::ValidNavigationMesh3d;
85 pub use crate::Velocity2d;
86 pub use crate::Velocity3d;
87 pub use crate::coords::CoordinateSystem;
88 pub use crate::coords::ThreeD;
89 pub use crate::coords::TwoD;
90}
91
92pub struct LandmassPlugin<CS: CoordinateSystem> {
93 schedule: Interned<dyn ScheduleLabel>,
94 _marker: PhantomData<CS>,
95}
96
97impl<CS: CoordinateSystem> Default for LandmassPlugin<CS> {
98 fn default() -> Self {
99 Self { schedule: FixedPreUpdate.intern(), _marker: Default::default() }
100 }
101}
102
103impl<CS: CoordinateSystem> LandmassPlugin<CS> {
104 #[must_use]
107 pub fn in_schedule(mut self, schedule: impl ScheduleLabel) -> Self {
108 self.schedule = schedule.intern();
109 self
110 }
111}
112
113pub type Landmass2dPlugin = LandmassPlugin<TwoD>;
114pub type Landmass3dPlugin = LandmassPlugin<ThreeD>;
115
116impl<CS: CoordinateSystem> Plugin for LandmassPlugin<CS> {
117 fn build(&self, app: &mut bevy_app::App) {
118 app.init_asset::<NavMesh<CS>>();
119 app.configure_sets(
120 self.schedule,
121 (
122 LandmassSystems::SyncExistence,
123 LandmassSystems::SyncValues,
124 LandmassSystems::Update,
125 LandmassSystems::Output,
126 )
127 .chain(),
128 );
129 app.add_systems(
130 self.schedule,
131 (
132 add_agents_to_archipelagos::<CS>,
133 sync_islands_to_archipelago::<CS>,
134 add_characters_to_archipelago::<CS>,
135 update_animation_links_to_archipelagos::<CS>,
136 )
137 .in_set(LandmassSystems::SyncExistence),
138 );
139 app.add_systems(
140 self.schedule,
141 (sync_agent_input_state::<CS>, sync_character_state::<CS>)
142 .in_set(LandmassSystems::SyncValues),
143 );
144 app.add_systems(
145 self.schedule,
146 update_archipelagos::<CS>.in_set(LandmassSystems::Update),
147 );
148 app.add_systems(
149 self.schedule,
150 (
151 sync_agent_state::<CS>,
152 sync_desired_velocity::<CS>,
153 sync_agent_reached_animation_link::<CS>,
154 )
155 .in_set(LandmassSystems::Output),
156 );
157
158 app.add_observer(on_remove_animation_link::<CS>);
159 app.add_observer(on_replace_archipelago_ref_from_animation_link::<CS>);
160 }
161}
162
163#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
165pub enum LandmassSystems {
166 SyncExistence,
170 SyncValues,
173 Update,
175 Output,
179}
180
181#[derive(Component)]
183pub struct Archipelago<CS: CoordinateSystem> {
184 archipelago: landmass::Archipelago<CS>,
186 islands: HashMap<Entity, IslandId>,
189 reverse_islands: HashMap<IslandId, Entity>,
192 animation_links: HashMap<Entity, AnimationLinkId>,
195 reverse_animation_links: HashMap<AnimationLinkId, Entity>,
199 agents: HashMap<Entity, AgentId>,
202 reverse_agents: HashMap<AgentId, Entity>,
205 characters: HashMap<Entity, CharacterId>,
208}
209
210pub type Archipelago2d = Archipelago<TwoD>;
211pub type Archipelago3d = Archipelago<ThreeD>;
212
213impl<CS: CoordinateSystem> Archipelago<CS> {
214 pub fn new(agent_options: ArchipelagoOptions<CS>) -> Self {
216 Self {
217 archipelago: landmass::Archipelago::new(agent_options),
218 islands: HashMap::new(),
219 reverse_islands: HashMap::new(),
220 animation_links: HashMap::new(),
221 reverse_animation_links: HashMap::new(),
222 agents: HashMap::new(),
223 reverse_agents: HashMap::new(),
224 characters: HashMap::new(),
225 }
226 }
227
228 pub fn get_agent_options(&self) -> &ArchipelagoOptions<CS> {
230 &self.archipelago.archipelago_options
231 }
232
233 pub fn get_agent_options_mut(&mut self) -> &mut ArchipelagoOptions<CS> {
235 &mut self.archipelago.archipelago_options
236 }
237
238 pub fn set_type_index_cost(
242 &mut self,
243 type_index: usize,
244 cost: f32,
245 ) -> Result<(), SetTypeIndexCostError> {
246 self.archipelago.set_type_index_cost(type_index, cost)
247 }
248
249 pub fn get_type_index_cost(&self, type_index: usize) -> Option<f32> {
251 self.archipelago.get_type_index_cost(type_index)
252 }
253
254 pub fn get_type_index_costs(
256 &self,
257 ) -> impl Iterator<Item = (usize, f32)> + '_ {
258 self.archipelago.get_type_index_costs()
259 }
260
261 pub fn sample_point(
264 &self,
265 point: CS::Coordinate,
266 point_sample_distance: &CS::SampleDistance,
267 ) -> Result<SampledPoint<'_, CS>, SamplePointError> {
268 let sampled_point =
269 self.archipelago.sample_point(point, point_sample_distance)?;
270 Ok(SampledPoint {
271 island: *self
272 .reverse_islands
273 .get(&sampled_point.island())
274 .expect("The island hasn't been removed from the archipelago."),
275 sampled_point,
276 })
277 }
278
279 pub fn find_path(
285 &self,
286 start_point: &SampledPoint<'_, CS>,
287 end_point: &SampledPoint<'_, CS>,
288 override_type_index_costs: &std::collections::HashMap<usize, f32>,
289 permitted_animation_links: PermittedAnimationLinks,
290 ) -> Result<Vec<PathStep<CS>>, FindPathError> {
291 self.archipelago.find_path(
292 &start_point.sampled_point,
293 &end_point.sampled_point,
294 override_type_index_costs,
295 permitted_animation_links.to_landmass(),
296 )
297 }
298
299 fn get_agent(&self, entity: Entity) -> Option<&landmass::Agent<CS>> {
301 self
302 .agents
303 .get(&entity)
304 .and_then(|&agent_id| self.archipelago.get_agent(agent_id))
305 }
306
307 fn get_agent_mut(
309 &mut self,
310 entity: Entity,
311 ) -> Option<&mut landmass::Agent<CS>> {
312 self
313 .agents
314 .get(&entity)
315 .and_then(|&agent_id| self.archipelago.get_agent_mut(agent_id))
316 }
317
318 #[allow(unused)] fn get_character(&self, entity: Entity) -> Option<&landmass::Character<CS>> {
321 self
322 .characters
323 .get(&entity)
324 .and_then(|&character_id| self.archipelago.get_character(character_id))
325 }
326
327 fn get_character_mut(
329 &mut self,
330 entity: Entity,
331 ) -> Option<&mut landmass::Character<CS>> {
332 self.characters.get(&entity).and_then(|&character_id| {
333 self.archipelago.get_character_mut(character_id)
334 })
335 }
336
337 #[allow(unused)] fn get_island(&self, entity: Entity) -> Option<&landmass::Island<CS>> {
340 self
341 .islands
342 .get(&entity)
343 .and_then(|&island_id| self.archipelago.get_island(island_id))
344 }
345
346 fn get_island_mut(
348 &mut self,
349 entity: Entity,
350 ) -> Option<landmass::IslandMut<'_, CS>> {
351 self
352 .islands
353 .get(&entity)
354 .and_then(|&island_id| self.archipelago.get_island_mut(island_id))
355 }
356}
357
358fn update_archipelagos<CS: CoordinateSystem>(
360 time: Res<Time>,
361 mut archipelago_query: Query<&mut Archipelago<CS>>,
362) {
363 for mut archipelago in archipelago_query.iter_mut() {
364 archipelago.archipelago.update(time.delta_secs());
365 }
366}
367
368#[derive(Component)]
370pub struct ArchipelagoRef<CS: CoordinateSystem> {
371 pub entity: Entity,
372 pub marker: PhantomData<CS>,
373}
374
375pub type ArchipelagoRef2d = ArchipelagoRef<TwoD>;
376pub type ArchipelagoRef3d = ArchipelagoRef<ThreeD>;
377
378impl<CS: CoordinateSystem<Coordinate: std::fmt::Debug>> std::fmt::Debug
379 for ArchipelagoRef<CS>
380{
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 f.debug_struct("ArchipelagoRef")
383 .field("entity", &self.entity)
384 .field("marker", &self.marker)
385 .finish()
386 }
387}
388
389impl<CS: CoordinateSystem> ArchipelagoRef<CS> {
390 pub fn new(entity: Entity) -> Self {
391 Self { entity, marker: Default::default() }
392 }
393}
394
395pub type NavigationMesh2d = NavigationMesh<TwoD>;
400pub type NavigationMesh3d = NavigationMesh<ThreeD>;
408
409pub type HeightNavigationMesh2d = HeightNavigationMesh<TwoD>;
410pub type HeightNavigationMesh3d = HeightNavigationMesh<ThreeD>;
411
412pub type ValidNavigationMesh2d = ValidNavigationMesh<TwoD>;
413pub type ValidNavigationMesh3d = ValidNavigationMesh<ThreeD>;
414
415#[derive(Asset, TypePath)]
417pub struct NavMesh<CS: CoordinateSystem> {
418 pub nav_mesh: Arc<ValidNavigationMesh<CS>>,
420}
421
422pub type NavMesh2d = NavMesh<TwoD>;
423pub type NavMesh3d = NavMesh<ThreeD>;
424
425#[derive(Component, Clone, Debug)]
427pub struct NavMeshHandle<CS: CoordinateSystem>(pub Handle<NavMesh<CS>>);
428
429impl<CS: CoordinateSystem> Default for NavMeshHandle<CS> {
430 fn default() -> Self {
431 Self(Default::default())
432 }
433}
434
435pub type NavMeshHandle2d = NavMeshHandle<TwoD>;
436pub type NavMeshHandle3d = NavMeshHandle<ThreeD>;
437
438pub struct SampledPoint<'archipelago, CS: CoordinateSystem> {
440 sampled_point: landmass::SampledPoint<'archipelago, CS>,
442 island: Entity,
444}
445
446impl<CS: CoordinateSystem> Clone for SampledPoint<'_, CS> {
448 fn clone(&self) -> Self {
449 Self { sampled_point: self.sampled_point.clone(), island: self.island }
450 }
451}
452
453impl<CS: CoordinateSystem> SampledPoint<'_, CS> {
454 pub fn point(&self) -> CS::Coordinate {
456 self.sampled_point.point()
457 }
458
459 pub fn island(&self) -> Entity {
461 self.island
462 }
463
464 pub fn type_index(&self) -> usize {
466 self.sampled_point.type_index()
467 }
468}
469
470#[cfg(test)]
471#[path = "lib_test.rs"]
472mod test;