veloren_common/util/
find_dist.rs

1/// Calculate the shortest distance between the surfaces of two shapes
2use vek::*;
3
4pub trait FindDist<T> {
5    /// Compute roughly whether the other shape is out of range
6    /// Meant to be a cheap method for initial filtering
7    /// Must return true if the shape could be within the supplied distance but
8    /// is allowed to return true if the shape is actually just out of
9    /// range
10    fn approx_in_range(self, other: T, range: f32) -> bool;
11    /// Find the smallest distance between the two shapes
12    fn min_distance(self, other: T) -> f32;
13}
14
15/// A z-axis aligned cylinder
16#[derive(Clone, Copy, Debug)]
17pub struct Cylinder {
18    /// Center of the cylinder
19    pub center: Vec3<f32>,
20    /// Radius of the cylinder
21    pub radius: f32,
22    /// Height of the cylinder
23    pub height: f32,
24}
25
26impl Cylinder {
27    fn aabb(&self) -> Aabb<f32> {
28        Aabb {
29            min: self.center - Vec3::new(self.radius, self.radius, self.height / 2.0),
30            max: self.center + Vec3::new(self.radius, self.radius, self.height / 2.0),
31        }
32    }
33
34    #[inline]
35    pub fn from_components(
36        pos: Vec3<f32>,
37        scale: Option<crate::comp::Scale>,
38        collider: Option<&crate::comp::Collider>,
39        char_state: Option<&crate::comp::CharacterState>,
40    ) -> Self {
41        let scale = scale.map_or(1.0, |s| s.0);
42        let radius = collider.as_ref().map_or(0.5, |c| c.bounding_radius()) * scale;
43        let z_limit_modifier = char_state
44            .filter(|char_state| char_state.is_dodge())
45            .map_or(1.0, |_| 0.5)
46            * scale;
47        let (z_bottom, z_top) = collider
48            .map(|c| c.get_z_limits(z_limit_modifier))
49            .unwrap_or((-0.5 * z_limit_modifier, 0.5 * z_limit_modifier));
50
51        Self {
52            center: pos + Vec3::unit_z() * (z_top + z_bottom) / 2.0,
53            radius,
54            height: z_top - z_bottom,
55        }
56    }
57}
58
59/// An axis aligned cube
60#[derive(Clone, Copy, Debug)]
61pub struct Cube {
62    /// The position of min corner of the cube
63    pub min: Vec3<f32>,
64    /// The side length of the cube
65    pub side_length: f32,
66}
67
68impl FindDist<Cylinder> for Cube {
69    #[inline]
70    fn approx_in_range(self, other: Cylinder, range: f32) -> bool {
71        let cube_plus_range_aabb = Aabb {
72            min: self.min - range,
73            max: self.min + self.side_length + range,
74        };
75        let cylinder_aabb = other.aabb();
76
77        cube_plus_range_aabb.collides_with_aabb(cylinder_aabb)
78    }
79
80    #[inline]
81    fn min_distance(self, other: Cylinder) -> f32 {
82        // Distance between centers along the z-axis
83        let z_center_dist = (self.min.z + self.side_length / 2.0 - other.center.z).abs();
84        // Distance between surfaces projected onto the z-axis
85        let z_dist = (z_center_dist - (self.side_length + other.height) / 2.0).max(0.0);
86        // Distance between shapes projected onto the xy plane as a square/circle
87        let square_aabr = Aabr {
88            min: self.min.xy(),
89            max: self.min.xy() + self.side_length,
90        };
91        let xy_dist = (square_aabr.distance_to_point(other.center.xy()) - other.radius).max(0.0);
92        // Overall distance by pythagoras
93        (z_dist.powi(2) + xy_dist.powi(2)).sqrt()
94    }
95}
96
97impl FindDist<Cube> for Cylinder {
98    #[inline]
99    fn approx_in_range(self, other: Cube, range: f32) -> bool { other.approx_in_range(self, range) }
100
101    #[inline]
102    fn min_distance(self, other: Cube) -> f32 { other.min_distance(self) }
103}
104
105impl FindDist<Cylinder> for Cylinder {
106    #[inline]
107    fn approx_in_range(self, other: Cylinder, range: f32) -> bool {
108        let mut aabb = self.aabb();
109        aabb.min -= range;
110        aabb.max += range;
111
112        aabb.collides_with_aabb(other.aabb())
113    }
114
115    #[inline]
116    fn min_distance(self, other: Cylinder) -> f32 {
117        // Distance between centers along the z-axis
118        let z_center_dist = (self.center.z - other.center.z).abs();
119        // Distance between surfaces projected onto the z-axis
120        let z_dist = (z_center_dist - (self.height + other.height) / 2.0).max(0.0);
121        // Distance between shapes projected onto the xy plane as a circles
122        let xy_dist =
123            (self.center.xy().distance(other.center.xy()) - self.radius - other.radius).max(0.0);
124        // Overall distance by pythagoras
125        (z_dist.powi(2) + xy_dist.powi(2)).sqrt()
126    }
127}
128
129impl FindDist<Vec3<f32>> for Cylinder {
130    #[inline]
131    fn approx_in_range(self, other: Vec3<f32>, range: f32) -> bool {
132        let mut aabb = self.aabb();
133        aabb.min -= range;
134        aabb.max += range;
135
136        aabb.contains_point(other)
137    }
138
139    #[inline]
140    fn min_distance(self, other: Vec3<f32>) -> f32 {
141        // Distance between center and point along the z-axis
142        let z_center_dist = (self.center.z - other.z).abs();
143        // Distance between surface and point projected onto the z-axis
144        let z_dist = (z_center_dist - self.height / 2.0).max(0.0);
145        // Distance between shapes projected onto the xy plane
146        let xy_dist = (self.center.xy().distance(other.xy()) - self.radius).max(0.0);
147        // Overall distance by pythagoras
148        (z_dist.powi(2) + xy_dist.powi(2)).sqrt()
149    }
150}
151
152impl FindDist<Cylinder> for Vec3<f32> {
153    #[inline]
154    fn approx_in_range(self, other: Cylinder, range: f32) -> bool {
155        other.approx_in_range(self, range)
156    }
157
158    #[inline]
159    fn min_distance(self, other: Cylinder) -> f32 { other.min_distance(self) }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn cylinder_vs_cube() {
168        //let offset = Vec3::new(1213.323, 5424.0, -231.0);
169        let offset = Vec3::zero();
170        let cylinder = Cylinder {
171            center: Vec3::new(0.0, 0.0, 0.0) + offset,
172            radius: 2.0,
173            height: 4.0,
174        };
175
176        let cube = Cube {
177            min: Vec3::new(-0.5, -0.5, -0.5) + offset,
178            side_length: 1.0,
179        };
180
181        assert!(cube.approx_in_range(cylinder, 0.0));
182        assert!(cube.min_distance(cylinder).abs() < f32::EPSILON);
183        assert!((cube.min_distance(cylinder) - cylinder.min_distance(cube)).abs() < 0.001);
184
185        let cube = Cube {
186            min: cube.min + Vec3::unit_x() * 50.0,
187            side_length: 1.0,
188        };
189
190        assert!(!cube.approx_in_range(cylinder, 5.0)); // Note: technically it is not breaking any promises if this returns true but this will be useful as a warning if the filtering is not tight as we were expecting
191        assert!(cube.approx_in_range(cylinder, 47.51));
192        assert!((cube.min_distance(cylinder) - 47.5).abs() < 0.001);
193        assert!((cube.min_distance(cylinder) - cylinder.min_distance(cube)).abs() < 0.001);
194    }
195
196    #[test]
197    fn zero_size_cylinder() {
198        let cylinder = Cylinder {
199            center: Vec3::new(1.0, 2.0, 3.0),
200            radius: 0.0,
201            height: 0.0,
202        };
203
204        let point = Vec3::new(1.0, 2.5, 3.5);
205
206        assert!(cylinder.approx_in_range(point, 0.71));
207        assert!(cylinder.min_distance(point) < 0.71);
208        assert!(cylinder.min_distance(point) > 0.70);
209
210        let cube = Cube {
211            min: Vec3::new(0.5, 1.9, 2.1),
212            side_length: 1.0,
213        };
214
215        assert!(cylinder.approx_in_range(cube, 0.0));
216        assert!(cylinder.min_distance(cube) < f32::EPSILON);
217
218        let cube = Cube {
219            min: Vec3::new(1.0, 2.0, 4.5),
220            side_length: 1.0,
221        };
222
223        assert!(cylinder.approx_in_range(cube, 1.51));
224        assert!(cylinder.approx_in_range(cube, 100.51));
225        assert!(cylinder.min_distance(cube) < 1.501);
226        assert!(cylinder.min_distance(cube) > 1.499);
227    }
228}