#![warn(missing_docs)]
use crate::{
core::{
algebra::{Point3, Vector3},
arrayvec::ArrayVec,
math::{self, plane::Plane, ray::Ray, PositionProvider, TriangleDefinition, Vector3Ext},
reflect::prelude::*,
visitor::{Visit, VisitResult, Visitor},
},
scene::mesh::{
buffer::{VertexAttributeUsage, VertexReadTrait},
Mesh,
},
utils::{
astar::{Graph, PathError, PathKind, VertexData, VertexDataProvider},
raw_mesh::{RawMeshBuilder, RawVertex},
},
};
use fxhash::{FxBuildHasher, FxHashMap};
use fyrox_core::math::octree::{Octree, OctreeNode};
use std::ops::{Deref, DerefMut};
#[derive(Clone, Debug, Default, Visit)]
struct Vertex {
triangle_index: usize,
data: VertexData,
}
impl Deref for Vertex {
type Target = VertexData;
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl DerefMut for Vertex {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
impl PositionProvider for Vertex {
fn position(&self) -> Vector3<f32> {
self.data.position
}
}
impl VertexDataProvider for Vertex {}
#[derive(Clone, Debug, Default, Reflect)]
#[reflect(hide_all)]
pub struct Navmesh {
octree: Octree,
triangles: Vec<TriangleDefinition>,
vertices: Vec<Vector3<f32>>,
graph: Graph<Vertex>,
}
impl PartialEq for Navmesh {
fn eq(&self, other: &Self) -> bool {
self.triangles == other.triangles && self.vertices == other.vertices
}
}
impl Visit for Navmesh {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
self.vertices.visit("Vertices", &mut region)?;
self.triangles.visit("Triangles", &mut region)?;
drop(region);
if visitor.is_reading() {
let raw_triangles = self
.triangles
.iter()
.map(|t| {
[
self.vertices[t[0] as usize],
self.vertices[t[1] as usize],
self.vertices[t[2] as usize],
]
})
.collect::<Vec<[Vector3<f32>; 3]>>();
self.octree = Octree::new(&raw_triangles, 32);
}
let graph = make_graph(&self.triangles, &self.vertices);
self.graph = graph;
Ok(())
}
}
#[derive(Copy, Clone, Debug)]
struct Portal {
left: usize,
right: usize,
}
fn triangle_area_2d(a: Vector3<f32>, b: Vector3<f32>, c: Vector3<f32>) -> f32 {
let abx = b[0] - a[0];
let abz = b[2] - a[2];
let acx = c[0] - a[0];
let acz = c[2] - a[2];
acx * abz - abx * acz
}
#[derive(PartialEq, Clone, Copy, Eq)]
enum Winding {
Clockwise,
CounterClockwise,
}
fn winding(a: Vector3<f32>, b: Vector3<f32>, c: Vector3<f32>) -> Winding {
if triangle_area_2d(a, b, c) > 0.0 {
Winding::Clockwise
} else {
Winding::CounterClockwise
}
}
fn make_graph(triangles: &[TriangleDefinition], vertices: &[Vector3<f32>]) -> Graph<Vertex> {
let mut graph = Graph::new();
for (triangle_index, triangle) in triangles.iter().enumerate() {
let a = vertices[triangle[0] as usize];
let b = vertices[triangle[1] as usize];
let c = vertices[triangle[2] as usize];
let center = (a + b + c).scale(1.0 / 3.0);
graph.add_vertex(Vertex {
triangle_index,
data: VertexData::new(center),
});
}
#[derive(Copy, Clone, PartialEq, Hash, Eq)]
struct Edge {
a: usize,
b: usize,
}
let total_edge_count = triangles.len() * 3;
let mut edge_triangle_map =
FxHashMap::with_capacity_and_hasher(total_edge_count, FxBuildHasher::default());
for (triangle_index, triangle) in triangles.iter().enumerate() {
for edge in triangle.edges() {
edge_triangle_map.insert(
Edge {
a: edge.a as usize,
b: edge.b as usize,
},
triangle_index,
);
}
}
for (triangle_index, triangle) in triangles.iter().enumerate() {
for edge in triangle.edges() {
let adjacent_edge = Edge {
a: edge.b as usize,
b: edge.a as usize,
};
if let Some(adjacent_triangle_index) = edge_triangle_map.get(&adjacent_edge) {
graph.link_bidirect(triangle_index, *adjacent_triangle_index);
}
}
}
graph
}
pub struct NavmeshModificationContext<'a> {
navmesh: &'a mut Navmesh,
}
impl Drop for NavmeshModificationContext<'_> {
fn drop(&mut self) {
let graph = make_graph(&self.navmesh.triangles, &self.navmesh.vertices);
self.navmesh.graph = graph;
}
}
impl NavmeshModificationContext<'_> {
pub fn add_triangle(&mut self, triangle: TriangleDefinition) -> u32 {
let index = self.navmesh.triangles.len();
self.navmesh.triangles.push(triangle);
index as u32
}
pub fn remove_triangle(&mut self, index: usize) -> TriangleDefinition {
self.navmesh.triangles.remove(index)
}
pub fn pop_triangle(&mut self) -> Option<TriangleDefinition> {
if self.navmesh.triangles.is_empty() {
None
} else {
Some(self.remove_triangle(self.navmesh.triangles.len() - 1))
}
}
pub fn remove_vertex(&mut self, index: usize) -> Vector3<f32> {
let mut i = 0;
while i < self.navmesh.triangles.len() {
if self.navmesh.triangles[i]
.indices()
.contains(&(index as u32))
{
self.remove_triangle(i);
} else {
i += 1;
}
}
for triangle in self.navmesh.triangles.iter_mut() {
for other_vertex_index in triangle.indices_mut() {
if *other_vertex_index > index as u32 {
*other_vertex_index -= 1;
}
}
}
self.navmesh.vertices.remove(index)
}
pub fn vertices_mut(&mut self) -> &mut [Vector3<f32>] {
&mut self.navmesh.vertices
}
pub fn add_vertex(&mut self, vertex: Vector3<f32>) -> u32 {
let index = self.navmesh.vertices.len();
self.navmesh.vertices.push(vertex);
index as u32
}
pub fn pop_vertex(&mut self) -> Option<Vector3<f32>> {
if self.navmesh.vertices.is_empty() {
None
} else {
Some(self.remove_vertex(self.navmesh.vertices.len() - 1))
}
}
pub fn insert_vertex(&mut self, index: u32, vertex: Vector3<f32>) {
self.navmesh.vertices.insert(index as usize, vertex);
for triangle in self.navmesh.triangles.iter_mut() {
for other_vertex_index in triangle.indices_mut() {
if *other_vertex_index >= index {
*other_vertex_index += 1;
}
}
}
}
}
impl Navmesh {
pub fn new(triangles: Vec<TriangleDefinition>, vertices: Vec<Vector3<f32>>) -> Self {
let raw_triangles = triangles
.iter()
.map(|t| {
[
vertices[t[0] as usize],
vertices[t[1] as usize],
vertices[t[2] as usize],
]
})
.collect::<Vec<[Vector3<f32>; 3]>>();
Self {
graph: make_graph(&triangles, &vertices),
triangles,
vertices,
octree: Octree::new(&raw_triangles, 32),
}
}
pub fn from_mesh(mesh: &Mesh) -> Self {
let mut builder = RawMeshBuilder::<RawVertex>::default();
let global_transform = mesh.global_transform();
for surface in mesh.surfaces() {
let shared_data = surface.data();
let shared_data = shared_data.data_ref();
let vertex_buffer = &shared_data.vertex_buffer;
for triangle in shared_data.geometry_buffer.iter() {
builder.insert(RawVertex::from(
global_transform
.transform_point(&Point3::from(
vertex_buffer
.get(triangle[0] as usize)
.unwrap()
.read_3_f32(VertexAttributeUsage::Position)
.unwrap(),
))
.coords,
));
builder.insert(RawVertex::from(
global_transform
.transform_point(&Point3::from(
vertex_buffer
.get(triangle[1] as usize)
.unwrap()
.read_3_f32(VertexAttributeUsage::Position)
.unwrap(),
))
.coords,
));
builder.insert(RawVertex::from(
global_transform
.transform_point(&Point3::from(
vertex_buffer
.get(triangle[2] as usize)
.unwrap()
.read_3_f32(VertexAttributeUsage::Position)
.unwrap(),
))
.coords,
));
}
}
let mesh = builder.build();
Navmesh::new(
mesh.triangles,
mesh.vertices
.into_iter()
.map(|v| Vector3::new(v.x, v.y, v.z))
.collect::<Vec<_>>(),
)
}
pub fn query_closest(&self, query_point: Vector3<f32>) -> Option<(Vector3<f32>, usize)> {
let mut closest = None;
let mut closest_distance = f32::MAX;
self.query_closest_internal(
&mut closest,
&mut closest_distance,
0..self.triangles.len(),
query_point,
);
closest
}
fn query_closest_internal(
&self,
closest: &mut Option<(Vector3<f32>, usize)>,
closest_distance: &mut f32,
triangles: impl Iterator<Item = usize>,
query_point: Vector3<f32>,
) {
for triangle_index in triangles {
let triangle = &self.triangles[triangle_index];
let a = self.vertices[triangle[0] as usize];
let b = self.vertices[triangle[1] as usize];
let c = self.vertices[triangle[2] as usize];
let Some(plane) = Plane::from_triangle(&a, &b, &c) else {
continue;
};
let projection = plane.project(&query_point);
if math::is_point_inside_triangle(&projection, &[a, b, c]) {
let sqr_distance = query_point.sqr_distance(&projection);
if sqr_distance < *closest_distance {
*closest_distance = sqr_distance;
*closest = Some((projection, triangle_index));
}
}
for edge in self.triangles[triangle_index].edges() {
let a = self.vertices[edge.a as usize];
let b = self.vertices[edge.b as usize];
let ray = Ray::from_two_points(a, b);
let t = ray.project_point(&query_point);
if (0.0..=1.0).contains(&t) {
let edge_pt_projection = ray.get_point(t);
let sqr_distance = query_point.sqr_distance(&edge_pt_projection);
if sqr_distance < *closest_distance {
*closest_distance = sqr_distance;
*closest = Some((ray.get_point(t), triangle_index));
}
}
}
for pt in [a, b, c] {
let sqr_distance = query_point.sqr_distance(&pt);
if sqr_distance < *closest_distance {
*closest_distance = sqr_distance;
*closest = Some((pt, triangle_index));
}
}
}
}
pub fn modify(&mut self) -> NavmeshModificationContext {
NavmeshModificationContext { navmesh: self }
}
pub fn triangles(&self) -> &[TriangleDefinition] {
&self.triangles
}
pub fn vertices(&self) -> &[Vector3<f32>] {
&self.vertices
}
pub fn octree(&self) -> &Octree {
&self.octree
}
pub fn build_path(
&self,
from: usize,
to: usize,
path: &mut Vec<Vector3<f32>>,
) -> Result<PathKind, PathError> {
self.graph.build_positional_path(from, to, path)
}
pub fn ray_cast(&self, ray: Ray) -> Option<(Vector3<f32>, usize)> {
let mut buffer = ArrayVec::<usize, 128>::new();
self.octree.ray_query_static(&ray, &mut buffer);
let mut closest_distance = f32::MAX;
let mut result = None;
for node in buffer.into_iter() {
if let OctreeNode::Leaf { indices, .. } = self.octree.node(node) {
for &index in indices {
let triangle = self.triangles[index as usize];
let a = self.vertices()[triangle[0] as usize];
let b = self.vertices()[triangle[1] as usize];
let c = self.vertices()[triangle[2] as usize];
if let Some(intersection) = ray.triangle_intersection_point(&[a, b, c]) {
let distance = intersection.metric_distance(&ray.origin);
if distance < closest_distance {
closest_distance = distance;
result = Some((intersection, index as usize));
}
}
}
} else {
unreachable!()
}
}
result
}
fn portal_between(&self, src_triangle: usize, dest_triangle: usize) -> Option<Portal> {
let src_triangle = self.triangles.get(src_triangle)?;
let dest_triangle = self.triangles.get(dest_triangle)?;
for src_triangle_edge in src_triangle.edges() {
for dest_triangle_edge in dest_triangle.edges() {
if src_triangle_edge == dest_triangle_edge {
let a = self.vertices[src_triangle[0] as usize];
let b = self.vertices[src_triangle[1] as usize];
let c = self.vertices[src_triangle[2] as usize];
return if winding(a, b, c) == Winding::Clockwise {
Some(Portal {
left: src_triangle_edge.a as usize,
right: src_triangle_edge.b as usize,
})
} else {
Some(Portal {
left: src_triangle_edge.b as usize,
right: src_triangle_edge.a as usize,
})
};
}
}
}
None
}
}
#[derive(Visit, Clone, Debug)]
#[visit(optional)]
pub struct NavmeshAgent {
path: Vec<Vector3<f32>>,
current: u32,
position: Vector3<f32>,
last_warp_position: Vector3<f32>,
target: Vector3<f32>,
last_target_position: Vector3<f32>,
recalculation_threshold: f32,
speed: f32,
path_dirty: bool,
radius: f32,
interpolator: f32,
}
impl Default for NavmeshAgent {
fn default() -> Self {
Self::new()
}
}
impl NavmeshAgent {
pub fn new() -> Self {
Self {
path: vec![],
current: 0,
position: Default::default(),
last_warp_position: Default::default(),
target: Default::default(),
last_target_position: Default::default(),
recalculation_threshold: 0.25,
speed: 1.5,
path_dirty: true,
radius: 0.2,
interpolator: 0.0,
}
}
pub fn position(&self) -> Vector3<f32> {
self.position
}
pub fn path(&self) -> &[Vector3<f32>] {
&self.path
}
pub fn set_speed(&mut self, speed: f32) {
self.speed = speed;
}
pub fn speed(&self) -> f32 {
self.speed
}
pub fn set_threshold(&mut self, threshold: f32) {
self.recalculation_threshold = threshold;
}
pub fn threshold(&self) -> f32 {
self.recalculation_threshold
}
pub fn set_radius(&mut self, radius: f32) {
self.radius = radius;
}
pub fn radius(&self) -> f32 {
self.radius
}
}
impl NavmeshAgent {
pub fn calculate_path(
&mut self,
navmesh: &Navmesh,
src_point: Vector3<f32>,
dest_point: Vector3<f32>,
) -> Result<PathKind, PathError> {
self.path.clear();
self.current = 0;
self.interpolator = 0.0;
if let Some((src_point_on_navmesh, src_triangle)) = navmesh.query_closest(src_point) {
if let Some((dest_point_on_navmesh, dest_triangle)) = navmesh.query_closest(dest_point)
{
if src_triangle == dest_triangle {
self.path.push(src_point_on_navmesh);
self.path.push(dest_point_on_navmesh);
return Ok(PathKind::Full);
}
let mut path_triangle_indices = Vec::new();
let path_kind = navmesh.graph.build_indexed_path(
src_triangle,
dest_triangle,
&mut path_triangle_indices,
)?;
path_triangle_indices.reverse();
self.straighten_path(
navmesh,
src_point_on_navmesh,
dest_point_on_navmesh,
&path_triangle_indices,
);
return Ok(path_kind);
}
}
Err(PathError::Empty)
}
fn straighten_path(
&mut self,
navmesh: &Navmesh,
src_position: Vector3<f32>,
dest_position: Vector3<f32>,
path_triangles: &[usize],
) {
self.path.push(src_position);
if path_triangles.len() > 1 {
let mut funnel_apex = src_position;
let mut funnel_vertices = [funnel_apex; 2];
let mut side_indices = [0; 2];
let side_signs = [1.0, -1.0];
let mut i = 0;
while i < path_triangles.len() {
let portal_vertices = if i + 1 < path_triangles.len() {
let portal = navmesh
.portal_between(path_triangles[i], path_triangles[i + 1])
.unwrap();
let mut left = navmesh.vertices[portal.left];
let mut right = navmesh.vertices[portal.right];
if self.radius > 0.0 {
let delta = right - left;
let len = delta.norm();
let offset = delta.scale(self.radius.min(len * 0.5) / len);
left += offset;
right -= offset;
}
[left, right]
} else {
[dest_position, dest_position]
};
for current in 0..2 {
let opposite = 1 - current;
let side_sign = side_signs[current];
if side_sign
* triangle_area_2d(
funnel_apex,
funnel_vertices[current],
portal_vertices[current],
)
>= 0.0
{
if funnel_apex == funnel_vertices[current]
|| side_sign
* triangle_area_2d(
funnel_apex,
funnel_vertices[opposite],
portal_vertices[current],
)
< 0.0
{
funnel_vertices[current] = portal_vertices[current];
side_indices[current] = i;
} else {
funnel_apex = funnel_vertices[opposite];
funnel_vertices = [funnel_apex; 2];
self.path.push(funnel_apex);
i = side_indices[opposite];
side_indices[current] = i;
break;
}
}
}
i += 1;
}
}
self.path.push(dest_position);
}
pub fn update(&mut self, dt: f32, navmesh: &Navmesh) -> Result<PathKind, PathError> {
if self.path_dirty {
self.calculate_path(navmesh, self.position, self.target)?;
self.path_dirty = false;
}
if let Some(source) = self.path.get(self.current as usize) {
if let Some(destination) = self.path.get((self.current + 1) as usize) {
let len = destination.metric_distance(source);
self.position = source.lerp(destination, self.interpolator.clamp(0.0, 1.0));
self.interpolator += (self.speed * dt) / len.max(f32::EPSILON);
if self.interpolator >= 1.0 {
self.current += 1;
self.interpolator = 0.0;
} else if self.interpolator < 0.0 {
self.current = self.current.saturating_sub(1);
self.interpolator = 1.0;
}
}
}
Ok(PathKind::Full)
}
pub fn steering_target(&self) -> Option<Vector3<f32>> {
self.path
.get(self.current as usize + 1)
.or_else(|| self.path.last())
.cloned()
}
pub fn set_target(&mut self, new_target: Vector3<f32>) {
if new_target.metric_distance(&self.last_target_position) >= self.recalculation_threshold {
self.path_dirty = true;
self.last_target_position = new_target;
}
self.target = new_target;
}
pub fn target(&self) -> Vector3<f32> {
self.target
}
pub fn set_position(&mut self, new_position: Vector3<f32>) {
if new_position.metric_distance(&self.last_warp_position) >= self.recalculation_threshold {
self.path_dirty = true;
self.last_warp_position = new_position;
}
self.position = new_position;
}
}
pub struct NavmeshAgentBuilder {
position: Vector3<f32>,
target: Vector3<f32>,
recalculation_threshold: f32,
speed: f32,
}
impl Default for NavmeshAgentBuilder {
fn default() -> Self {
Self::new()
}
}
impl NavmeshAgentBuilder {
pub fn new() -> Self {
Self {
position: Default::default(),
target: Default::default(),
recalculation_threshold: 0.25,
speed: 1.5,
}
}
pub fn with_position(mut self, position: Vector3<f32>) -> Self {
self.position = position;
self
}
pub fn with_target(mut self, position: Vector3<f32>) -> Self {
self.target = position;
self
}
pub fn with_recalculation_threshold(mut self, threshold: f32) -> Self {
self.recalculation_threshold = threshold;
self
}
pub fn with_speed(mut self, speed: f32) -> Self {
self.speed = speed;
self
}
pub fn build(self) -> NavmeshAgent {
NavmeshAgent {
position: self.position,
last_warp_position: self.position,
target: self.target,
last_target_position: self.target,
recalculation_threshold: self.recalculation_threshold,
speed: self.speed,
..Default::default()
}
}
}
#[cfg(test)]
mod test {
use crate::{
core::{algebra::Vector3, math::TriangleDefinition},
utils::navmesh::{Navmesh, NavmeshAgent},
};
#[test]
fn test_navmesh() {
let navmesh = Navmesh::new(
vec![
TriangleDefinition([0, 1, 3]),
TriangleDefinition([1, 2, 3]),
TriangleDefinition([2, 5, 3]),
TriangleDefinition([2, 4, 5]),
TriangleDefinition([4, 7, 5]),
TriangleDefinition([4, 6, 7]),
],
vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
Vector3::new(1.0, 0.0, 1.0),
Vector3::new(1.0, 0.0, 0.0),
Vector3::new(2.0, 0.0, 1.0),
Vector3::new(2.0, 0.0, 0.0),
Vector3::new(3.0, 0.0, 1.0),
Vector3::new(3.0, 0.0, 0.0),
],
);
let mut agent = NavmeshAgent::new();
agent.set_target(Vector3::new(3.0, 0.0, 1.0));
agent.update(1.0 / 60.0, &navmesh).unwrap();
let graph = &navmesh.graph;
assert_eq!(graph.vertices.len(), 6);
assert_eq!(graph.vertices[0].neighbours[0], 1);
assert_eq!(graph.vertices[1].neighbours[0], 0);
assert_eq!(graph.vertices[1].neighbours[1], 2);
assert_eq!(graph.vertices[2].neighbours[0], 1);
assert_eq!(graph.vertices[2].neighbours[1], 3);
assert_eq!(graph.vertices[3].neighbours[0], 2);
assert_eq!(graph.vertices[3].neighbours[1], 4);
assert_eq!(graph.vertices[4].neighbours[0], 3);
assert_eq!(graph.vertices[4].neighbours[1], 5);
assert_eq!(graph.vertices[5].neighbours[0], 4);
assert_eq!(
agent.path,
vec![
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(3.0, 0.0, 1.0),
Vector3::new(3.0, 0.0, 1.0)
]
);
}
}