parry3d/query/ray/
ray_voxels.rs

1use crate::math::{Real, Vector};
2use crate::query::{Ray, RayCast, RayIntersection};
3use crate::shape::{FeatureId, Voxels};
4
5impl RayCast for Voxels {
6    #[inline]
7    fn cast_local_ray_and_get_normal(
8        &self,
9        ray: &Ray,
10        max_time_of_impact: Real,
11        solid: bool,
12    ) -> Option<RayIntersection> {
13        use num_traits::Bounded;
14
15        let aabb = self.local_aabb();
16        let (min_t, mut max_t) = aabb.clip_ray_parameters(ray)?;
17
18        #[cfg(feature = "dim2")]
19        let ii = [0, 1];
20        #[cfg(feature = "dim3")]
21        let ii = [0, 1, 2];
22
23        if min_t > max_time_of_impact {
24            return None;
25        }
26
27        max_t = max_t.min(max_time_of_impact);
28        let clip_ray_a = ray.point_at(min_t);
29        let voxel_key_signed = self.voxel_at_point_unchecked(clip_ray_a);
30        let mut voxel_key = self.clamp_voxel(voxel_key_signed);
31        let [domain_mins, domain_maxs] = self.domain();
32
33        loop {
34            let voxel = self.voxel_state(voxel_key);
35            let aabb = self.voxel_aabb(voxel_key);
36
37            if !voxel.is_empty() {
38                // We hit a voxel!
39                // TODO: if `solid` is false, and we started hitting from the first iteration,
40                //       then we should continue the ray propagation until we reach empty space again.
41                let hit = aabb.cast_local_ray_and_get_normal(ray, max_t, solid);
42
43                if let Some(mut hit) = hit {
44                    // TODO: have the feature id be based on the voxel type?
45                    hit.feature = FeatureId::Face(self.linear_index(voxel_key));
46                    return Some(hit);
47                }
48            }
49
50            /*
51             * Find the next voxel to cast the ray on.
52             */
53            let toi = ii.map(|i| {
54                if ray.dir[i] > 0.0 {
55                    let t = (aabb.maxs[i] - ray.origin[i]) / ray.dir[i];
56                    if t < 0.0 {
57                        (Real::max_value(), true)
58                    } else {
59                        (t, true)
60                    }
61                } else if ray.dir[i] < 0.0 {
62                    let t = (aabb.mins[i] - ray.origin[i]) / ray.dir[i];
63                    if t < 0.0 {
64                        (Real::max_value(), false)
65                    } else {
66                        (t, false)
67                    }
68                } else {
69                    (Real::max_value(), false)
70                }
71            });
72
73            #[cfg(feature = "dim2")]
74            if toi[0].0 > max_t && toi[1].0 > max_t {
75                break;
76            }
77
78            #[cfg(feature = "dim3")]
79            if toi[0].0 > max_t && toi[1].0 > max_t && toi[2].0 > max_t {
80                break;
81            }
82
83            let imin = Vector::from(toi.map(|t| t.0)).imin();
84
85            if toi[imin].1 {
86                if voxel_key[imin] < domain_maxs[imin] - 1 {
87                    voxel_key[imin] += 1;
88                } else {
89                    // Leaving the shape’s bounds.
90                    break;
91                }
92            } else if voxel_key[imin] > domain_mins[imin] {
93                voxel_key[imin] -= 1;
94            } else {
95                // Leaving the shape’s bounds.
96                break;
97            }
98        }
99
100        None
101    }
102}