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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Raycast hit information for Detour
//!
//! This module provides the RaycastHit structure that matches the C++ dtRaycastHit
use super::PolyRef;
use glam::Vec3;
/// Options for raycast behavior
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RaycastOptions {
/// Whether to include the cost of the path
pub include_cost: bool,
/// Whether to include the visited polygons in the result
pub include_path: bool,
}
impl Default for RaycastOptions {
fn default() -> Self {
Self {
include_cost: false,
include_path: true,
}
}
}
/// Provides information about raycast hit
/// Matches C++ dtRaycastHit structure
#[derive(Debug, Clone)]
pub struct RaycastHit {
/// The hit parameter (distance along ray as fraction of max_dist)
/// Set to f32::MAX if no wall hit
pub t: f32,
/// The normal of the nearest wall hit
pub hit_normal: Vec3,
/// The index of the edge on the final polygon where the wall was hit
pub hit_edge_index: i32,
/// The visited polygon path (optional)
pub path: Option<Vec<PolyRef>>,
/// The cost of the path until hit (optional)
pub path_cost: Option<f32>,
}
impl RaycastHit {
/// Creates a new RaycastHit with no hit (t = f32::MAX)
pub fn no_hit() -> Self {
Self {
t: f32::MAX,
hit_normal: Vec3::ZERO,
hit_edge_index: -1,
path: None,
path_cost: None,
}
}
/// Creates a new RaycastHit with a wall hit
pub fn wall_hit(t: f32, normal: Vec3, edge_index: i32) -> Self {
Self {
t,
hit_normal: normal,
hit_edge_index: edge_index,
path: None,
path_cost: None,
}
}
pub fn hit_wall(&self) -> bool {
self.t < f32::MAX
}
/// Sets the path information
pub fn with_path(mut self, path: Vec<PolyRef>) -> Self {
self.path = Some(path);
self
}
/// Sets the path cost
pub fn with_cost(mut self, cost: f32) -> Self {
self.path_cost = Some(cost);
self
}
pub fn path_count(&self) -> usize {
self.path.as_ref().map(|p| p.len()).unwrap_or(0)
}
}
/// Result of a raycast query
#[derive(Debug, Clone)]
pub struct RaycastResult {
/// The polygon reference where the ray ends
pub end_ref: PolyRef,
/// The position where the ray ends
pub end_pos: Vec3,
/// Hit information
pub hit: RaycastHit,
}
impl RaycastResult {
/// Creates a new raycast result
pub fn new(end_ref: PolyRef, end_pos: Vec3, hit: RaycastHit) -> Self {
Self {
end_ref,
end_pos,
hit,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_raycast_hit_no_hit() {
let hit = RaycastHit::no_hit();
assert_eq!(hit.t, f32::MAX);
assert!(!hit.hit_wall());
assert_eq!(hit.path_count(), 0);
}
#[test]
fn test_raycast_hit_wall() {
let hit = RaycastHit::wall_hit(0.5, Vec3::new(0.0, 1.0, 0.0), 2);
assert_eq!(hit.t, 0.5);
assert!(hit.hit_wall());
assert_eq!(hit.hit_edge_index, 2);
assert_eq!(hit.hit_normal, Vec3::new(0.0, 1.0, 0.0));
}
#[test]
fn test_raycast_hit_with_path() {
let path = vec![PolyRef::new(1), PolyRef::new(2), PolyRef::new(3)];
let hit = RaycastHit::wall_hit(0.7, Vec3::new(1.0, 0.0, 0.0), 1)
.with_path(path.clone())
.with_cost(15.5);
assert_eq!(hit.path_count(), 3);
assert_eq!(hit.path, Some(path));
assert_eq!(hit.path_cost, Some(15.5));
}
}