Skip to main content

parry3d/query/ray/
ray_heightfield.rs

1use crate::math::Real;
2#[cfg(feature = "dim2")]
3use crate::query;
4use crate::query::{Ray, RayCast, RayIntersection};
5#[cfg(feature = "dim2")]
6use crate::shape::FeatureId;
7use crate::shape::HeightField;
8
9#[cfg(feature = "dim2")]
10impl RayCast for HeightField {
11    #[inline]
12    fn cast_local_ray_and_get_normal(
13        &self,
14        ray: &Ray,
15        max_time_of_impact: Real,
16        _: bool,
17    ) -> Option<RayIntersection> {
18        let aabb = self.local_aabb();
19        let (min_t, mut max_t) = aabb.clip_ray_parameters(ray)?;
20
21        if min_t > max_time_of_impact {
22            return None;
23        }
24
25        max_t = max_t.min(max_time_of_impact);
26
27        let clip_ray_a = ray.point_at(min_t);
28
29        // None may happen due to slight numerical errors.
30        let mut curr = self.cell_at_point(clip_ray_a).unwrap_or_else(|| {
31            if ray.origin.x > 0.0 {
32                self.num_cells() - 1
33            } else {
34                0_usize
35            }
36        });
37
38        /*
39         * Test the segment under the ray.
40         */
41        if let Some(seg) = self.segment_at(curr) {
42            let (s, t) = query::details::closest_points_line_line_parameters(
43                ray.origin,
44                ray.dir,
45                seg.a,
46                seg.scaled_direction(),
47            );
48            if s >= 0.0 && t >= 0.0 && t <= 1.0 {
49                // Cast succeeded on the first element!
50                let n = seg.normal().unwrap();
51                // The feature is the hit segment's own side; `subshape` says which segment.
52                let fid = if n.dot(ray.dir) > 0.0 { 1 } else { 0 };
53
54                return Some(
55                    RayIntersection::new(s, n, FeatureId::Face(fid)).with_subshape(curr as u32),
56                );
57            }
58        }
59
60        /*
61         * Test other segments in the path of the ray.
62         */
63        if ray.dir.x == 0.0 {
64            return None;
65        }
66
67        let right = ray.dir.x > 0.0;
68        let cell_width = self.cell_width();
69        let start_x = self.start_x();
70
71        while (right && curr < self.num_cells()) || (!right && curr > 0) {
72            let curr_param;
73
74            if right {
75                curr += 1;
76                curr_param = (cell_width * (curr as Real) + start_x - ray.origin.x) / ray.dir.x;
77            } else {
78                curr_param = (ray.origin.x - cell_width * (curr as Real) - start_x) / ray.dir.x;
79                curr -= 1;
80            }
81
82            if curr_param >= max_t {
83                // The part of the ray after max_t is outside of the heightfield Aabb.
84                return None;
85            }
86
87            if let Some(seg) = self.segment_at(curr) {
88                // TODO: test the y-coordinates (equivalent to an Aabb test) before actually computing the intersection.
89                let (s, t) = query::details::closest_points_line_line_parameters(
90                    ray.origin,
91                    ray.dir,
92                    seg.a,
93                    seg.scaled_direction(),
94                );
95
96                if t >= 0.0 && t <= 1.0 && s <= max_time_of_impact {
97                    let n = seg.normal().unwrap();
98                    // The feature is the hit segment's own side; `subshape` says which segment.
99                    let fid = if n.dot(ray.dir) > 0.0 { 1 } else { 0 };
100                    return Some(
101                        RayIntersection::new(s, n, FeatureId::Face(fid)).with_subshape(curr as u32),
102                    );
103                }
104            }
105        }
106
107        None
108    }
109}
110
111#[cfg(feature = "dim3")]
112impl RayCast for HeightField {
113    #[inline]
114    fn cast_local_ray_and_get_normal(
115        &self,
116        ray: &Ray,
117        max_time_of_impact: Real,
118        solid: bool,
119    ) -> Option<RayIntersection> {
120        use num_traits::Bounded;
121
122        let aabb = self.local_aabb();
123        let (min_t, mut max_t) = aabb.clip_ray_parameters(ray)?;
124        max_t = max_t.min(max_time_of_impact);
125        let clip_ray_a = ray.point_at(min_t);
126        let mut cell = match self.cell_at_point(clip_ray_a) {
127            Some(cell) => cell,
128            // None may happen due to slight numerical errors.
129            None => {
130                let i = if ray.origin.z > 0.0 {
131                    self.nrows() - 1
132                } else {
133                    0
134                };
135
136                let j = if ray.origin.x > 0.0 {
137                    self.ncols() - 1
138                } else {
139                    0
140                };
141
142                (i, j)
143            }
144        };
145
146        loop {
147            let tris = self.triangles_at(cell.0, cell.1);
148            let inter1 = tris
149                .0
150                .and_then(|tri| tri.cast_local_ray_and_get_normal(ray, max_time_of_impact, solid));
151            let inter2 = tris
152                .1
153                .and_then(|tri| tri.cast_local_ray_and_get_normal(ray, max_time_of_impact, solid));
154
155            // The feature stays the one the triangle reported; `subshape` says which triangle it
156            // belongs to.
157            match (inter1, inter2) {
158                (Some(mut inter1), Some(mut inter2)) => {
159                    if inter1.time_of_impact < inter2.time_of_impact {
160                        inter1.subshape = self.triangle_id(cell.0, cell.1, true);
161                        return Some(inter1);
162                    } else {
163                        inter2.subshape = self.triangle_id(cell.0, cell.1, false);
164                        return Some(inter2);
165                    }
166                }
167                (Some(mut inter), None) => {
168                    inter.subshape = self.triangle_id(cell.0, cell.1, true);
169                    return Some(inter);
170                }
171                (None, Some(mut inter)) => {
172                    inter.subshape = self.triangle_id(cell.0, cell.1, false);
173                    return Some(inter);
174                }
175                (None, None) => {}
176            }
177
178            /*
179             * Find the next cell to cast the ray on.
180             */
181            let (toi_x, right) = if ray.dir.x > 0.0 {
182                let x = self.x_at(cell.1 + 1);
183                ((x - ray.origin.x) / ray.dir.x, true)
184            } else if ray.dir.x < 0.0 {
185                let x = self.x_at(cell.1);
186                ((x - ray.origin.x) / ray.dir.x, false)
187            } else {
188                (Real::max_value(), false)
189            };
190
191            let (toi_z, down) = if ray.dir.z > 0.0 {
192                let z = self.z_at(cell.0 + 1);
193                ((z - ray.origin.z) / ray.dir.z, true)
194            } else if ray.dir.z < 0.0 {
195                let z = self.z_at(cell.0);
196                ((z - ray.origin.z) / ray.dir.z, false)
197            } else {
198                (Real::max_value(), false)
199            };
200
201            if toi_x > max_t && toi_z > max_t {
202                break;
203            }
204
205            if toi_x >= 0.0 && toi_x < toi_z {
206                if right {
207                    cell.1 += 1
208                } else if cell.1 > 0 {
209                    cell.1 -= 1
210                } else {
211                    break;
212                }
213            } else if toi_z >= 0.0 {
214                if down {
215                    cell.0 += 1
216                } else if cell.0 > 0 {
217                    cell.0 -= 1
218                } else {
219                    break;
220                }
221            } else {
222                break;
223            }
224
225            if cell.0 >= self.nrows() || cell.1 >= self.ncols() {
226                break;
227            }
228        }
229
230        None
231    }
232}