1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
use bevy::{ecs::system::SystemParam, prelude::Entity};
use de_index::SpatialQuery;
use de_terrain::TerrainCollider;
use parry3d::query::Ray;
#[derive(SystemParam)]
pub(crate) struct LineOfSight<'w, 's> {
terrain: TerrainCollider<'w, 's>,
entities: SpatialQuery<'w, 's, Entity>,
}
impl<'w, 's> LineOfSight<'w, 's> {
/// Looks into a direction up until some furthest point.
///
/// # Arguments
///
/// * `ray` - gives the direction to look.
///
/// * `max_toi` - limits the maximum observable distance. The furthest
/// observable point is given by `ray.origin * max_toi`.
///
/// * `observer` - the entity making the observation. This is needed so the
/// entity doesn't observe itself.
pub(crate) fn sight(&self, ray: &Ray, max_toi: f32, observer: Entity) -> Observation {
// It is more efficient to calculate the terrain hit. Do it first so
// max_toi can be lowered in case of a hit.
let hit = match self.terrain.cast_ray(ray, max_toi) {
Some(intersection) => Observation::new(intersection.toi, None),
None => Observation::new(max_toi, None),
};
self.entities
.cast_ray(ray, hit.toi(), Some(observer))
.map(|i| Observation::new(i.toi(), Some(i.entity())))
.unwrap_or(hit)
}
}
pub(crate) struct Observation {
toi: f32,
entity: Option<Entity>,
}
impl Observation {
fn new(toi: f32, entity: Option<Entity>) -> Self {
Self { entity, toi }
}
/// Returns visibility along the ray. `toi * direction` is the point of
/// first obstacle in that direction or the furthest point within range
/// limited by a max toi parameter.
pub(crate) fn toi(&self) -> f32 {
self.toi
}
/// The entity observed, if any, along a given ray.
pub(crate) fn entity(&self) -> Option<Entity> {
self.entity
}
}