parry2d/query/shape_cast/
shape_cast_heightfield_shape.rs1use crate::bounding_volume::BoundingVolume;
2use crate::math::{Pose, Real, Vector};
3use crate::query::details::ShapeCastOptions;
4use crate::query::{QueryDispatcher, Ray, ShapeCastHit, Unsupported};
5use crate::shape::{HeightField, Shape};
6#[cfg(feature = "dim3")]
7use crate::{bounding_volume::Aabb, query::RayCast};
8
9#[cfg(feature = "dim2")]
11pub fn cast_shapes_heightfield_shape<D: ?Sized + QueryDispatcher>(
12 dispatcher: &D,
13 pos12: &Pose,
14 vel12: Vector,
15 heightfield1: &HeightField,
16 g2: &dyn Shape,
17 options: ShapeCastOptions,
18) -> Result<Option<ShapeCastHit>, Unsupported> {
19 let aabb2_1 = g2.compute_aabb(pos12).loosened(options.target_distance);
20 let ray = Ray::new(aabb2_1.center(), vel12);
21
22 let mut curr_range = heightfield1.unclamped_elements_range_in_local_aabb(&aabb2_1);
23 let right = ray.dir.x > 0.0;
25
26 if right {
27 curr_range.end += 1;
28 } else {
29 curr_range.start -= 1;
30 }
31
32 let mut best_hit = None::<ShapeCastHit>;
33
34 let clamped_curr_range = curr_range.start.clamp(0, heightfield1.num_cells() as isize) as usize
38 ..curr_range.end.clamp(0, heightfield1.num_cells() as isize) as usize;
39 for curr in clamped_curr_range {
40 if let Some(seg) = heightfield1.segment_at(curr) {
41 if let Some(hit) = dispatcher.cast_shapes(pos12, vel12, &seg, g2, options)? {
43 if hit.time_of_impact < best_hit.map(|h| h.time_of_impact).unwrap_or(Real::MAX) {
44 best_hit = Some(hit);
45 }
46 }
47 }
48 }
49
50 if ray.dir.x == 0.0 {
54 return Ok(best_hit);
55 }
56
57 let cell_width = heightfield1.cell_width();
58 let start_x = heightfield1.start_x();
59
60 let mut curr_elt = if right {
61 (curr_range.end - 1).max(0)
62 } else {
63 curr_range.start.min(heightfield1.num_cells() as isize - 1)
64 };
65
66 while (right && curr_elt < heightfield1.num_cells() as isize - 1) || (!right && curr_elt > 0) {
67 let curr_param;
68
69 if right {
70 curr_elt += 1;
71 curr_param = (cell_width * (curr_elt as Real) + start_x - ray.origin.x) / ray.dir.x;
72 } else {
73 curr_param = (ray.origin.x - cell_width * (curr_elt as Real) - start_x) / ray.dir.x;
74 curr_elt -= 1;
75 }
76
77 if curr_param >= options.max_time_of_impact {
78 break;
79 }
80
81 if let Some(seg) = heightfield1.segment_at(curr_elt as usize) {
82 if let Some(hit) = dispatcher.cast_shapes(pos12, vel12, &seg, g2, options)? {
84 if hit.time_of_impact < best_hit.map(|h| h.time_of_impact).unwrap_or(Real::MAX) {
85 best_hit = Some(hit);
86 }
87 }
88 }
89 }
90
91 Ok(best_hit)
92}
93
94#[cfg(feature = "dim3")]
96pub fn cast_shapes_heightfield_shape<D: ?Sized + QueryDispatcher>(
97 dispatcher: &D,
98 pos12: &Pose,
99 vel12: Vector,
100 heightfield1: &HeightField,
101 g2: &dyn Shape,
102 options: ShapeCastOptions,
103) -> Result<Option<ShapeCastHit>, Unsupported> {
104 let aabb1 = heightfield1.local_aabb();
105 let mut aabb2_1 = g2.compute_aabb(pos12).loosened(options.target_distance);
106 let ray = Ray::new(aabb2_1.center(), vel12);
107
108 let hext2_1 = aabb2_1.half_extents();
110 let msum = Aabb::new(aabb1.mins - hext2_1, aabb1.maxs + hext2_1);
111 if let Some(time_of_impact) = msum.cast_local_ray(&ray, options.max_time_of_impact, true) {
112 aabb2_1.mins += ray.dir * time_of_impact;
114 aabb2_1.maxs += ray.dir * time_of_impact;
115 } else {
116 return Ok(None);
117 }
118
119 let (mut curr_range_i, mut curr_range_j) =
120 heightfield1.unclamped_elements_range_in_local_aabb(&aabb2_1);
121 let (ncells_i, ncells_j) = heightfield1.num_cells_ij();
122 let mut best_hit = None::<ShapeCastHit>;
123
124 if ray.dir.z > 0.0 {
128 curr_range_i.end += 1;
129 } else if ray.dir.z < 0.0 {
130 curr_range_i.start -= 1;
131 }
132
133 if ray.dir.x > 0.0 {
134 curr_range_j.end += 1;
135 } else if ray.dir.x < 0.0 {
136 curr_range_j.start -= 1;
137 }
138
139 let clamped_curr_range_i = curr_range_i.start.clamp(0, ncells_i as isize)
143 ..curr_range_i.end.clamp(0, ncells_i as isize);
144 let clamped_curr_range_j = curr_range_j.start.clamp(0, ncells_j as isize)
145 ..curr_range_j.end.clamp(0, ncells_j as isize);
146
147 let mut hit_triangles = |i, j| {
148 if i >= 0 && j >= 0 {
149 let (tri_a, tri_b) = heightfield1.triangles_at(i as usize, j as usize);
150 for tri in [tri_a, tri_b].into_iter().flatten() {
151 if let Some(hit) = dispatcher.cast_shapes(pos12, vel12, &tri, g2, options)? {
153 if hit.time_of_impact < best_hit.map(|h| h.time_of_impact).unwrap_or(Real::MAX)
154 {
155 best_hit = Some(hit);
156 }
157 }
158 }
159 }
160
161 Ok(())
162 };
163
164 for i in clamped_curr_range_i {
165 for j in clamped_curr_range_j.clone() {
166 hit_triangles(i, j)?;
167 }
168 }
169
170 if ray.dir.y == 0.0 {
171 return Ok(best_hit);
172 }
173
174 let mut cell = heightfield1.unclamped_cell_at_point(aabb2_1.center());
175
176 loop {
177 let prev_cell = cell;
178
179 let toi_x = if ray.dir.x > 0.0 {
183 let x = heightfield1.signed_x_at(cell.1 + 1);
184 (x - ray.origin.x) / ray.dir.x
185 } else if ray.dir.x < 0.0 {
186 let x = heightfield1.signed_x_at(cell.1);
187 (x - ray.origin.x) / ray.dir.x
188 } else {
189 Real::MAX
190 };
191
192 let toi_z = if ray.dir.z > 0.0 {
193 let z = heightfield1.signed_z_at(cell.0 + 1);
194 (z - ray.origin.z) / ray.dir.z
195 } else if ray.dir.z < 0.0 {
196 let z = heightfield1.signed_z_at(cell.0);
197 (z - ray.origin.z) / ray.dir.z
198 } else {
199 Real::MAX
200 };
201
202 if toi_x > options.max_time_of_impact && toi_z > options.max_time_of_impact {
203 break;
204 }
205
206 if toi_x >= 0.0 && toi_x <= toi_z {
207 cell.1 += ray.dir.x.signum() as isize;
208 }
209
210 if toi_z >= 0.0 && toi_z <= toi_x {
211 cell.0 += ray.dir.z.signum() as isize;
212 }
213
214 if cell == prev_cell {
215 break;
216 }
217
218 let cell_diff = (cell.0 - prev_cell.0, cell.1 - prev_cell.1);
219 curr_range_i.start += cell_diff.0;
220 curr_range_i.end += cell_diff.0;
221 curr_range_j.start += cell_diff.1;
222 curr_range_j.end += cell_diff.1;
223
224 let new_line_i = if cell_diff.0 > 0 {
225 curr_range_i.end
226 } else {
227 curr_range_i.start
228 };
229
230 let new_line_j = if cell_diff.1 > 0 {
231 curr_range_j.end
232 } else {
233 curr_range_j.start
234 };
235
236 let ignore_line_i = new_line_i < 0 || new_line_i >= ncells_i as isize;
237 let ignore_line_j = new_line_j < 0 || new_line_j >= ncells_j as isize;
238
239 if ignore_line_i && ignore_line_j {
240 break;
241 }
242
243 if !ignore_line_i && cell_diff.0 != 0 {
244 for j in curr_range_j.clone() {
245 hit_triangles(new_line_i, j)?;
246 }
247 }
248
249 if !ignore_line_j && cell_diff.1 != 0 {
250 for i in curr_range_i.clone() {
251 hit_triangles(i, new_line_j)?;
252 }
253 }
254 }
255
256 Ok(best_hit)
257}
258
259pub fn cast_shapes_shape_heightfield<D: ?Sized + QueryDispatcher>(
261 dispatcher: &D,
262 pos12: &Pose,
263 vel12: Vector,
264 g1: &dyn Shape,
265 heightfield2: &HeightField,
266 options: ShapeCastOptions,
267) -> Result<Option<ShapeCastHit>, Unsupported> {
268 Ok(cast_shapes_heightfield_shape(
269 dispatcher,
270 &pos12.inverse(),
271 -(pos12.rotation.inverse() * vel12),
272 heightfield2,
273 g1,
274 options,
275 )?
276 .map(|hit| hit.swapped()))
277}