Skip to main content

rg3d_physics/
convex_shape.rs

1use rg3d_core::{
2    define_is_as,
3    math::{
4        vec3::Vec3,
5        self,
6        aabb::AxisAlignedBoundingBox
7    },
8    visitor::{Visit, VisitResult, Visitor, VisitError},
9};
10
11#[derive(Clone, Debug)]
12pub struct SphereShape {
13    pub radius: f32
14}
15
16pub trait CircumRadius {
17    fn circumradius(&self) -> f32;
18}
19
20impl Visit for SphereShape {
21    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
22        visitor.enter_region(name)?;
23
24        self.radius.visit("Radius", visitor)?;
25
26        visitor.leave_region()
27    }
28}
29
30impl CircumRadius for SphereShape {
31    fn circumradius(&self) -> f32 {
32        self.radius
33    }
34}
35
36impl Default for SphereShape {
37    fn default() -> Self {
38        Self {
39            radius: 0.5,
40        }
41    }
42}
43
44impl SphereShape {
45    pub fn new(radius: f32) -> Self {
46        Self {
47            radius
48        }
49    }
50
51    pub fn set_radius(&mut self, radius: f32) {
52        self.radius = radius;
53    }
54
55    pub fn get_radius(&self) -> f32 {
56        self.radius
57    }
58
59    pub fn get_farthest_point(&self, direction: Vec3) -> Vec3 {
60        let norm_dir = direction.normalized().unwrap_or_else(|| Vec3::new(1.0, 0.0, 0.0));
61        norm_dir.scale(self.radius)
62    }
63}
64
65#[derive(Clone, Debug)]
66pub struct TriangleShape {
67    pub vertices: [Vec3; 3]
68}
69
70impl CircumRadius for TriangleShape {
71    fn circumradius(&self) -> f32 {
72        AxisAlignedBoundingBox::from_points(&self.vertices).half_extents().max_value()
73    }
74}
75
76impl Visit for TriangleShape {
77    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
78        visitor.enter_region(name)?;
79
80        self.vertices[0].visit("A", visitor)?;
81        self.vertices[1].visit("B", visitor)?;
82        self.vertices[2].visit("C", visitor)?;
83
84        visitor.leave_region()
85    }
86}
87
88impl Default for TriangleShape {
89    fn default() -> Self {
90        Self {
91            vertices: [
92                Vec3::new(0.0, 0.0, 0.0),
93                Vec3::new(1.0, 0.0, 0.0),
94                Vec3::new(0.5, 1.0, 0.0)]
95        }
96    }
97}
98
99impl TriangleShape {
100    pub fn new(vertices: [Vec3; 3]) -> Self {
101        Self {
102            vertices
103        }
104    }
105
106    pub fn get_normal(&self) -> Option<Vec3> {
107        (self.vertices[2] - self.vertices[0]).cross(&(self.vertices[1] - self.vertices[0])).normalized()
108    }
109
110    pub fn get_farthest_point(&self, direction: Vec3) -> Vec3 {
111        math::get_farthest_point(&self.vertices, direction)
112    }
113}
114
115#[derive(Clone, Debug)]
116pub struct BoxShape {
117    half_extents: Vec3,
118}
119
120impl CircumRadius for BoxShape {
121    fn circumradius(&self) -> f32 {
122        self.half_extents.max_value()
123    }
124}
125
126impl Visit for BoxShape {
127    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
128        visitor.enter_region(name)?;
129
130        self.half_extents.visit("HalfExtents", visitor)?;
131
132        visitor.leave_region()
133    }
134}
135
136impl Default for BoxShape {
137    fn default() -> Self {
138        Self {
139            half_extents: Vec3::new(0.5, 0.5, 0.5)
140        }
141    }
142}
143
144impl BoxShape {
145    pub fn new(half_extents: Vec3) -> Self {
146        Self {
147            half_extents
148        }
149    }
150
151    pub fn get_farthest_point(&self, direction: Vec3) -> Vec3 {
152        Vec3 {
153            x: if direction.x >= 0.0 { self.half_extents.x } else { -self.half_extents.x },
154            y: if direction.y >= 0.0 { self.half_extents.y } else { -self.half_extents.y },
155            z: if direction.z >= 0.0 { self.half_extents.z } else { -self.half_extents.z },
156        }
157    }
158
159    pub fn get_min(&self) -> Vec3 {
160        Vec3 {
161            x: -self.half_extents.x,
162            y: -self.half_extents.y,
163            z: -self.half_extents.z,
164        }
165    }
166
167    pub fn get_max(&self) -> Vec3 {
168        Vec3 {
169            x: self.half_extents.x,
170            y: self.half_extents.y,
171            z: self.half_extents.z,
172        }
173    }
174}
175
176#[derive(Clone, Debug)]
177pub struct PointCloudShape {
178    points: Vec<Vec3>
179}
180
181impl CircumRadius for PointCloudShape {
182    fn circumradius(&self) -> f32 {
183        // TODO: Unoptimal, value should be cached.
184        AxisAlignedBoundingBox::from_points(&self.points).half_extents().max_value()
185    }
186}
187
188impl Visit for PointCloudShape {
189    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
190        visitor.enter_region(name)?;
191
192        self.points.visit("Points", visitor)?;
193
194        visitor.leave_region()
195    }
196}
197
198impl Default for PointCloudShape {
199    fn default() -> Self {
200        Self {
201            points: Vec::new(),
202        }
203    }
204}
205
206impl PointCloudShape {
207    pub fn new(points: Vec<Vec3>) -> Self {
208        Self {
209            points
210        }
211    }
212
213    pub fn get_farthest_point(&self, direction: Vec3) -> Vec3 {
214        math::get_farthest_point(&self.points, direction)
215    }
216}
217
218#[derive(Copy, Clone, Debug)]
219pub enum Axis {
220    X = 0,
221    Y = 1,
222    Z = 2,
223}
224
225impl Visit for Axis {
226    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
227        let mut id = *self as u8;
228        id.visit(name, visitor)?;
229        if visitor.is_reading() {
230            *self = match id {
231                0 => Self::X,
232                1 => Self::Y,
233                2 => Self::Z,
234                _ => return Err(VisitError::User("Invalid axis".to_owned()))
235            }
236        }
237        Ok(())
238    }
239}
240
241#[derive(Clone, Debug)]
242pub struct CapsuleShape {
243    axis: Axis,
244    radius: f32,
245    height: f32,
246}
247
248impl CircumRadius for CapsuleShape {
249    fn circumradius(&self) -> f32 {
250        self.radius.max(self.height)
251    }
252}
253
254impl Default for CapsuleShape {
255    fn default() -> Self {
256        Self {
257            axis: Axis::X,
258            radius: 0.0,
259            height: 0.0,
260        }
261    }
262}
263
264impl Visit for CapsuleShape {
265    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
266        visitor.enter_region(name)?;
267
268        self.radius.visit("Radius", visitor)?;
269        self.axis.visit("Axis", visitor)?;
270        self.height.visit("Height", visitor)?;
271
272        visitor.leave_region()
273    }
274}
275
276impl CapsuleShape {
277    pub fn new(radius: f32, height: f32, axis: Axis) -> Self {
278        Self {
279            axis,
280            radius,
281            height
282        }
283    }
284
285    pub fn set_radius(&mut self, radius: f32) {
286        self.radius = radius.abs()
287    }
288
289    pub fn get_radius(&self) -> f32 {
290        self.radius
291    }
292
293    pub fn set_height(&mut self, height: f32) {
294        self.height = height.abs()
295    }
296
297    pub fn get_height(&self) -> f32 {
298        self.height
299    }
300
301    pub fn set_axis(&mut self, axis: Axis) {
302        self.axis = axis;
303    }
304
305    pub fn get_axis(&self) -> Axis {
306        self.axis
307    }
308
309    pub fn get_cap_centers(&self) -> (Vec3, Vec3) {
310        let half_height = self.height * 0.5;
311
312        match self.axis {
313            Axis::X => {
314                (Vec3::new(half_height, 0.0, 0.0),
315                 Vec3::new(-half_height, 0.0, 0.0))
316            }
317            Axis::Y => {
318                (Vec3::new(0.0, half_height, 0.0),
319                 Vec3::new(0.0, -half_height, 0.0))
320            }
321            Axis::Z => {
322                (Vec3::new(0.0, 0.0, half_height),
323                 Vec3::new(0.0, 0.0, -half_height))
324            }
325        }
326    }
327
328    pub fn get_farthest_point(&self, direction: Vec3) -> Vec3 {
329        let norm_dir = direction.normalized().unwrap_or_else(|| Vec3::new(1.0, 0.0, 0.0));
330        let half_height = self.height * 0.5;
331
332        let positive_cap_position = match self.axis {
333            Axis::X => Vec3::new(half_height, 0.0, 0.0),
334            Axis::Y => Vec3::new(0.0, half_height, 0.0),
335            Axis::Z => Vec3::new(0.0, 0.0, half_height),
336        };
337
338        let mut max = -std::f32::MAX;
339        let mut farthest = Vec3::ZERO;
340        for cap_center in [positive_cap_position, -positive_cap_position].iter() {
341            let vertex = *cap_center + norm_dir.scale(self.radius);
342            let dot = norm_dir.dot(&vertex);
343            if dot > max {
344                max = dot;
345                farthest = vertex;
346            }
347        }
348
349        farthest
350    }
351}
352
353#[derive(Clone, Debug)]
354pub enum ConvexShape {
355    Dummy,
356    Box(BoxShape),
357    Sphere(SphereShape),
358    Capsule(CapsuleShape),
359    Triangle(TriangleShape),
360    PointCloud(PointCloudShape),
361}
362
363impl CircumRadius for ConvexShape {
364    fn circumradius(&self) -> f32 {
365        match self {
366            Self::Dummy => 0.0,
367            Self::Box(box_shape) => box_shape.circumradius(),
368            Self::Sphere(sphere) => sphere.circumradius(),
369            Self::Capsule(capsule) => capsule.circumradius(),
370            Self::Triangle(triangle) => triangle.circumradius(),
371            Self::PointCloud(point_cloud) => point_cloud.circumradius(),
372        }
373    }
374}
375
376impl ConvexShape {
377    pub fn get_farthest_point(&self, position: Vec3, direction: Vec3) -> Vec3 {
378        position + match self {
379            Self::Dummy => Vec3::ZERO,
380            Self::Box(box_shape) => box_shape.get_farthest_point(direction),
381            Self::Sphere(sphere) => sphere.get_farthest_point(direction),
382            Self::Capsule(capsule) => capsule.get_farthest_point(direction),
383            Self::Triangle(triangle) => triangle.get_farthest_point(direction),
384            Self::PointCloud(point_cloud) => point_cloud.get_farthest_point(direction),
385        }
386    }
387
388    pub fn id(&self) -> i32 {
389        match self {
390            Self::Dummy => 0,
391            Self::Box(_) => 1,
392            Self::Sphere(_) => 2,
393            Self::Capsule(_) => 3,
394            Self::Triangle(_) => 4,
395            Self::PointCloud(_) => 5,
396        }
397    }
398
399    pub fn new(id: i32) -> Result<Self, String> {
400        match id {
401            0 => Ok(Self::Dummy),
402            1 => Ok(Self::Box(Default::default())),
403            2 => Ok(Self::Sphere(Default::default())),
404            3 => Ok(Self::Capsule(Default::default())),
405            4 => Ok(Self::Triangle(Default::default())),
406            5 => Ok(Self::PointCloud(Default::default())),
407            _ => Err("Invalid shape id!".to_owned())
408        }
409    }
410
411    define_is_as!(ConvexShape : Box -> ref BoxShape => fn is_box, fn as_box, fn as_box_mut);
412    define_is_as!(ConvexShape : Capsule -> ref CapsuleShape => fn is_capsule, fn as_capsule, fn as_capsule_mut);
413    define_is_as!(ConvexShape : Sphere -> ref SphereShape => fn is_sphere, fn as_sphere, fn as_sphere_mut);
414    define_is_as!(ConvexShape : Triangle -> ref TriangleShape => fn is_triangle, fn as_triangle, fn as_triangle_mut);
415    define_is_as!(ConvexShape : PointCloud -> ref PointCloudShape => fn is_point_cloud, fn as_point_cloud, fn as_point_cloud_mut);
416}
417
418impl Visit for ConvexShape {
419    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
420        match self {
421            Self::Dummy => Ok(()),
422            Self::Box(box_shape) => box_shape.visit(name, visitor),
423            Self::Sphere(sphere) => sphere.visit(name, visitor),
424            Self::Capsule(capsule) => capsule.visit(name, visitor),
425            Self::Triangle(triangle) => triangle.visit(name, visitor),
426            Self::PointCloud(point_cloud) => point_cloud.visit(name, visitor),
427        }
428    }
429}
430
431