├── .gitignore
├── .idea
├── .gitignore
├── vcs.xml
├── modules.xml
└── rg3d-physics.iml
├── README.md
├── Cargo.toml
├── LICENSE.md
└── src
├── contact.rs
├── static_geometry.rs
├── rigid_body.rs
├── convex_shape.rs
├── lib.rs
└── gjk_epa.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | **/*.rs.bk
3 | Cargo.lock
4 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Default ignored files
3 | /workspace.xml
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Contents of this repository was moved to [rg3d/rg3d-physics](https://github.com/mrDIMAS/rg3d/tree/master/rg3d-physics) and development will be continued there.
2 |
3 | # rg3d-physics
4 |
5 | Tiny physics library for rg3d-engine.
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rg3d-physics"
3 | version = "0.8.0"
4 | authors = ["Dmitry Stepanov "]
5 | edition = "2018"
6 | license = "MIT"
7 | description = "Physics library for rg3d-engine."
8 | keywords = ["physics", "game"]
9 | include = ["/src/**/*", "/Cargo.toml", "/LICENSE", "/README.md"]
10 | readme = "README.md"
11 |
12 | [dependencies]
13 | rg3d-core = { path = "../rg3d-core", version = "0.10.0" }
14 | bitflags = "1.2.1"
15 |
16 | [features]
17 | enable_profiler = ["rg3d-core/enable_profiler"]
--------------------------------------------------------------------------------
/.idea/rg3d-physics.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2019
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/src/contact.rs:
--------------------------------------------------------------------------------
1 | use rg3d_core::{
2 | math::vec3::Vec3,
3 | pool::Handle,
4 | visitor::{Visit, VisitResult, Visitor}
5 | };
6 | use crate::{
7 | rigid_body::RigidBody,
8 | static_geometry::StaticGeometry
9 | };
10 |
11 | #[derive(Debug, Clone)]
12 | pub struct Contact {
13 | pub body: Handle,
14 | pub position: Vec3,
15 | pub normal: Vec3,
16 | pub triangle_index: u32,
17 | pub static_geom: Handle
18 | }
19 |
20 | impl Default for Contact {
21 | fn default() -> Self {
22 | Self {
23 | body: Handle::NONE,
24 | position: Vec3::ZERO,
25 | normal: Vec3::new(0.0, 1.0, 0.0),
26 | triangle_index: 0,
27 | static_geom: Handle::NONE,
28 | }
29 | }
30 | }
31 |
32 | impl Visit for Contact {
33 | fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
34 | visitor.enter_region(name)?;
35 |
36 | self.body.visit("Body", visitor)?;
37 | self.position.visit("Position", visitor)?;
38 | self.normal.visit("Normal", visitor)?;
39 | self.triangle_index.visit("TriangleIndex", visitor)?;
40 |
41 | visitor.leave_region()
42 | }
43 | }
--------------------------------------------------------------------------------
/src/static_geometry.rs:
--------------------------------------------------------------------------------
1 | use rg3d_core::{
2 | math::{
3 | vec3::Vec3,
4 | plane::Plane
5 | },
6 | visitor::{Visit, VisitResult, Visitor, VisitError},
7 | octree::Octree
8 | };
9 |
10 | #[derive(Default, Clone, Debug)]
11 | pub struct StaticGeometry {
12 | pub(in crate) triangles: Vec,
13 | pub(in crate) octree: Octree,
14 | save_triangles: bool
15 | }
16 |
17 | impl StaticGeometry {
18 | pub const OCTREE_THRESHOLD: usize = 64;
19 |
20 | pub fn new(triangles: Vec, save_triangles: bool) -> Self {
21 | let raw_triangles: Vec<[Vec3; 3]> = triangles.iter().map(|st| st.points).collect();
22 |
23 | Self {
24 | octree: Octree::new(&raw_triangles, Self::OCTREE_THRESHOLD),
25 | triangles,
26 | save_triangles
27 | }
28 | }
29 |
30 | pub fn set_save_triangles(&mut self, save_triangles: bool) {
31 | self.save_triangles = save_triangles;
32 | }
33 |
34 | pub fn save_triangles(&self) -> bool {
35 | self.save_triangles
36 | }
37 | }
38 |
39 | impl Visit for StaticGeometry {
40 | fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
41 | visitor.enter_region(name)?;
42 |
43 | if !visitor.is_reading() {
44 | if self.save_triangles {
45 | self.triangles.visit("Triangles", visitor)?;
46 | } else {
47 | let mut empty: Vec = Vec::new();
48 | empty.visit("Triangles", visitor)?;
49 | }
50 | } else {
51 | self.triangles.visit("Triangles", visitor)?;
52 | }
53 |
54 | if visitor.is_reading() {
55 | let raw_triangles: Vec<[Vec3; 3]> = self.triangles.iter().map(|st| st.points).collect();
56 | self.octree = Octree::new(&raw_triangles, Self::OCTREE_THRESHOLD);
57 | }
58 |
59 | let _ = self.save_triangles.visit("SaveTriangles", visitor);
60 |
61 | visitor.leave_region()
62 | }
63 | }
64 |
65 | #[derive(Clone, Debug)]
66 | pub struct StaticTriangle {
67 | pub points: [Vec3; 3],
68 | pub ca: Vec3,
69 | pub ba: Vec3,
70 | pub ca_dot_ca: f32,
71 | pub ca_dot_ba: f32,
72 | pub ba_dot_ba: f32,
73 | pub inv_denom: f32,
74 | pub plane: Plane,
75 | }
76 |
77 | impl Default for StaticTriangle {
78 | fn default() -> Self {
79 | Self {
80 | points: Default::default(),
81 | ca: Default::default(),
82 | ba: Default::default(),
83 | ca_dot_ca: 0.0,
84 | ca_dot_ba: 0.0,
85 | ba_dot_ba: 0.0,
86 | inv_denom: 0.0,
87 | plane: Default::default(),
88 | }
89 | }
90 | }
91 |
92 | impl Visit for StaticTriangle {
93 | fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
94 | visitor.enter_region(name)?;
95 |
96 | let mut a = self.points[0];
97 | a.visit("A", visitor)?;
98 |
99 | let mut b = self.points[1];
100 | b.visit("B", visitor)?;
101 |
102 | let mut c = self.points[2];
103 | c.visit("C", visitor)?;
104 |
105 | *self = match Self::from_points(&a, &b, &c) {
106 | None => return Err(VisitError::User(String::from("invalid triangle"))),
107 | Some(triangle) => triangle,
108 | };
109 |
110 | visitor.leave_region()
111 | }
112 | }
113 |
114 | impl StaticTriangle {
115 | ///
116 | /// Creates static triangle from tree points and precomputes some data
117 | /// to speedup collision detection in runtime. This function may fail
118 | /// if degenerated triangle was passed into.
119 | ///
120 | pub fn from_points(a: &Vec3, b: &Vec3, c: &Vec3) -> Option {
121 | let ca = *c - *a;
122 | let ba = *b - *a;
123 | let ca_dot_ca = ca.dot(&ca);
124 | let ca_dot_ba = ca.dot(&ba);
125 | let ba_dot_ba = ba.dot(&ba);
126 | if let Ok(plane) = Plane::from_normal_and_point(&ba.cross(&ca), a) {
127 | return Some(StaticTriangle {
128 | points: [*a, *b, *c],
129 | ba,
130 | ca: *c - *a,
131 | ca_dot_ca,
132 | ca_dot_ba,
133 | ba_dot_ba,
134 | inv_denom: 1.0 / (ca_dot_ca * ba_dot_ba - ca_dot_ba * ca_dot_ba),
135 | plane,
136 | });
137 | }
138 |
139 | None
140 | }
141 |
142 | /// Checks if point lies inside or at edge of triangle. Uses a lot of precomputed data.
143 | pub fn contains_point(&self, p: Vec3) -> bool {
144 | let vp = p - self.points[0];
145 | let dot02 = self.ca.dot(&vp);
146 | let dot12 = self.ba.dot(&vp);
147 | let u = (self.ba_dot_ba * dot02 - self.ca_dot_ba * dot12) * self.inv_denom;
148 | let v = (self.ca_dot_ca * dot12 - self.ca_dot_ba * dot02) * self.inv_denom;
149 | u >= 0.0 && v >= 0.0 && u + v < 1.0
150 | }
151 | }
--------------------------------------------------------------------------------
/src/rigid_body.rs:
--------------------------------------------------------------------------------
1 | use rg3d_core::{
2 | math::vec3::Vec3,
3 | visitor::{Visit, VisitResult, Visitor},
4 | pool::Handle
5 | };
6 | use crate::{
7 | contact::Contact,
8 | convex_shape::{ConvexShape, TriangleShape},
9 | gjk_epa,
10 | static_geometry::{
11 | StaticTriangle,
12 | StaticGeometry
13 | }
14 | };
15 |
16 | bitflags! {
17 | pub struct CollisionFlags: u8 {
18 | const NONE = 0;
19 | /// Collision response will be disabled but body still will gather contact information.
20 | const DISABLE_COLLISION_RESPONSE = 1;
21 | }
22 | }
23 |
24 | #[derive(Debug)]
25 | pub struct RigidBody {
26 | pub(in crate) position: Vec3,
27 | pub(in crate) shape: ConvexShape,
28 | pub(in crate) last_position: Vec3,
29 | pub(in crate) acceleration: Vec3,
30 | pub(in crate) contacts: Vec,
31 | pub(in crate) friction: Vec3,
32 | pub(in crate) gravity: Vec3,
33 | pub(in crate) speed_limit: f32,
34 | pub(in crate) lifetime: Option,
35 | pub user_flags: u64,
36 | pub collision_group: u64,
37 | pub collision_mask: u64,
38 | pub collision_flags: CollisionFlags,
39 | }
40 |
41 | impl Default for RigidBody {
42 | fn default() -> Self {
43 | Self::new(ConvexShape::Dummy)
44 | }
45 | }
46 |
47 | impl Visit for RigidBody {
48 | fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
49 | visitor.enter_region(name)?;
50 |
51 | let mut id = self.shape.id();
52 | id.visit("ShapeKind", visitor)?;
53 | if visitor.is_reading() {
54 | self.shape = ConvexShape::new(id)?;
55 | }
56 | self.shape.visit("Shape", visitor)?;
57 |
58 | self.position.visit("Position", visitor)?;
59 | self.last_position.visit("LastPosition", visitor)?;
60 | self.acceleration.visit("Acceleration", visitor)?;
61 | self.contacts.visit("Contacts", visitor)?;
62 | self.friction.visit("Friction", visitor)?;
63 | self.gravity.visit("Gravity", visitor)?;
64 | self.speed_limit.visit("SpeedLimit", visitor)?;
65 | self.user_flags.visit("UserFlags", visitor)?;
66 | self.collision_group.visit("CollisionGroup", visitor)?;
67 | self.collision_mask.visit("CollisionMask", visitor)?;
68 |
69 | let mut collision_flags = self.collision_flags.bits;
70 | collision_flags.visit("CollisionFlags", visitor)?;
71 | if visitor.is_reading() {
72 | self.collision_flags = CollisionFlags::from_bits(collision_flags).unwrap();
73 | }
74 |
75 | visitor.leave_region()
76 | }
77 | }
78 |
79 | impl Clone for RigidBody {
80 | fn clone(&self) -> Self {
81 | Self {
82 | position: self.position,
83 | last_position: self.last_position,
84 | acceleration: self.acceleration,
85 | contacts: Vec::new(),
86 | friction: self.friction,
87 | gravity: self.gravity,
88 | shape: self.shape.clone(),
89 | speed_limit: self.speed_limit,
90 | lifetime: self.lifetime,
91 | user_flags: self.user_flags,
92 | collision_group: self.collision_group,
93 | collision_mask: self.collision_mask,
94 | collision_flags: CollisionFlags::NONE
95 | }
96 | }
97 | }
98 |
99 | impl RigidBody {
100 | pub fn new(shape: ConvexShape) -> Self {
101 | Self {
102 | position: Vec3::ZERO,
103 | last_position: Vec3::ZERO,
104 | acceleration: Vec3::ZERO,
105 | friction: Vec3::new(0.2, 0.2, 0.2),
106 | gravity: Vec3::new(0.0, -9.81, 0.0),
107 | shape,
108 | contacts: Vec::new(),
109 | speed_limit: 0.75,
110 | lifetime: None,
111 | user_flags: 0,
112 | collision_group: 1,
113 | collision_mask: std::u64::MAX,
114 | collision_flags: CollisionFlags::NONE
115 | }
116 | }
117 |
118 | #[inline]
119 | pub fn get_position(&self) -> Vec3 {
120 | self.position
121 | }
122 |
123 | #[inline]
124 | pub fn set_position(&mut self, p: Vec3) -> &mut Self {
125 | self.position = p;
126 | self.last_position = p;
127 | self
128 | }
129 |
130 | #[inline]
131 | pub fn move_by(&mut self, v: Vec3) -> &mut Self {
132 | self.position += v;
133 | self
134 | }
135 |
136 | pub fn offset_by(&mut self, v: Vec3) -> &mut Self {
137 | self.position += v;
138 | self.last_position = self.position;
139 | self
140 | }
141 |
142 | #[inline]
143 | pub fn set_shape(&mut self, shape: ConvexShape) -> &mut Self {
144 | self.shape = shape;
145 | self
146 | }
147 |
148 | #[inline]
149 | pub fn get_shape(&self) -> &ConvexShape {
150 | &self.shape
151 | }
152 |
153 | #[inline]
154 | pub fn get_shape_mut(&mut self) -> &mut ConvexShape {
155 | &mut self.shape
156 | }
157 |
158 | #[inline]
159 | pub fn set_friction(&mut self, friction: Vec3) -> &mut Self {
160 | self.friction.x = friction.x.max(0.0).min(1.0);
161 | self.friction.y = friction.y.max(0.0).min(1.0);
162 | self.friction.z = friction.z.max(0.0).min(1.0);
163 | self
164 | }
165 |
166 | #[inline]
167 | pub fn get_friction(&self) -> Vec3 {
168 | self.friction
169 | }
170 |
171 | #[inline]
172 | pub fn set_x_velocity(&mut self, x: f32) -> &mut Self {
173 | self.last_position.x = self.position.x - x;
174 | self
175 | }
176 |
177 | #[inline]
178 | pub fn set_y_velocity(&mut self, y: f32) -> &mut Self {
179 | self.last_position.y = self.position.y - y;
180 | self
181 | }
182 |
183 | #[inline]
184 | pub fn set_z_velocity(&mut self, z: f32) -> &mut Self {
185 | self.last_position.z = self.position.z - z;
186 | self
187 | }
188 |
189 | #[inline]
190 | pub fn set_velocity(&mut self, v: Vec3) -> &mut Self {
191 | self.last_position = self.position - v;
192 | self
193 | }
194 |
195 | #[inline]
196 | pub fn get_velocity(&self) -> Vec3 {
197 | self.position - self.last_position
198 | }
199 |
200 | #[inline]
201 | pub fn get_contacts(&self) -> &[Contact] {
202 | self.contacts.as_slice()
203 | }
204 |
205 | #[inline]
206 | pub fn set_gravity(&mut self, gravity: Vec3) -> &mut Self {
207 | self.gravity = gravity;
208 | self
209 | }
210 |
211 | #[inline]
212 | pub fn get_gravity(&self) -> Vec3 {
213 | self.gravity
214 | }
215 |
216 | #[inline]
217 | pub fn set_lifetime(&mut self, time_seconds: f32) -> &mut Self {
218 | self.lifetime = Some(time_seconds);
219 | self
220 | }
221 |
222 | #[inline]
223 | pub fn get_lifetime(&self) -> Option {
224 | self.lifetime
225 | }
226 |
227 | pub fn verlet(&mut self, sqr_delta_time: f32, air_friction: f32) {
228 | let friction =
229 | if !self.collision_flags.contains(CollisionFlags::DISABLE_COLLISION_RESPONSE) && !self.contacts.is_empty() {
230 | self.friction
231 | } else {
232 | Vec3::new(air_friction, air_friction, air_friction)
233 | };
234 |
235 | let last_position = self.position;
236 |
237 | // Verlet integration
238 | self.position = Vec3 {
239 | x: (2.0 - friction.x) * self.position.x - (1.0 - friction.x) * self.last_position.x + self.acceleration.x * sqr_delta_time,
240 | y: (2.0 - friction.y) * self.position.y - (1.0 - friction.y) * self.last_position.y + self.acceleration.y * sqr_delta_time,
241 | z: (2.0 - friction.z) * self.position.z - (1.0 - friction.z) * self.last_position.z + self.acceleration.z * sqr_delta_time,
242 | };
243 |
244 | self.last_position = last_position;
245 |
246 | self.acceleration = Vec3::ZERO;
247 |
248 | let velocity = self.last_position - self.position;
249 | let sqr_speed = velocity.sqr_len();
250 | if sqr_speed > self.speed_limit * self.speed_limit {
251 | if let Some(direction) = velocity.normalized() {
252 | self.last_position = self.position - direction.scale(self.speed_limit);
253 | }
254 | }
255 | }
256 |
257 | pub fn solve_triangle_collision(&mut self, triangle: &StaticTriangle, triangle_index: usize, static_geom: Handle) {
258 | let triangle_shape = ConvexShape::Triangle(TriangleShape {
259 | vertices: triangle.points
260 | });
261 |
262 | if let Some(simplex) = gjk_epa::gjk_is_intersects(&self.shape, self.position, &triangle_shape, Vec3::ZERO) {
263 | if let Some(penetration_info) = gjk_epa::epa_get_penetration_info(simplex, &self.shape, self.position, &triangle_shape, Vec3::ZERO) {
264 | self.position -= penetration_info.penetration_vector;
265 |
266 | self.contacts.push(Contact {
267 | static_geom,
268 | body: Handle::NONE,
269 | position: penetration_info.contact_point,
270 | normal: (-penetration_info.penetration_vector).normalized().unwrap_or(Vec3::ZERO),
271 | triangle_index: triangle_index as u32,
272 | })
273 | }
274 | }
275 | }
276 |
277 | pub fn solve_rigid_body_collision(&mut self, self_handle: Handle, other: &mut Self, other_handle: Handle) {
278 | if let Some(simplex) = gjk_epa::gjk_is_intersects(&self.shape, self.position, &other.shape, other.position) {
279 | if let Some(penetration_info) = gjk_epa::epa_get_penetration_info(simplex, &self.shape, self.position, &other.shape, other.position) {
280 | let half_push = penetration_info.penetration_vector.scale(0.5);
281 | let response_disabled =
282 | self.collision_flags.contains(CollisionFlags::DISABLE_COLLISION_RESPONSE) ||
283 | other.collision_flags.contains(CollisionFlags::DISABLE_COLLISION_RESPONSE);
284 |
285 | if !response_disabled {
286 | self.position -= half_push;
287 | }
288 | self.contacts.push(Contact {
289 | body: other_handle,
290 | position: penetration_info.contact_point,
291 | // TODO: WRONG NORMAL
292 | normal: (-penetration_info.penetration_vector).normalized().unwrap_or(Vec3::UP),
293 | triangle_index: 0,
294 | static_geom: Default::default()
295 | });
296 | if !response_disabled {
297 | other.position += half_push;
298 | }
299 | other.contacts.push(Contact {
300 | body: self_handle,
301 | position: penetration_info.contact_point,
302 | // TODO: WRONG NORMAL
303 | normal: (penetration_info.penetration_vector).normalized().unwrap_or(Vec3::UP),
304 | triangle_index: 0,
305 | static_geom: Default::default()
306 | })
307 | }
308 | }
309 | }
310 | }
--------------------------------------------------------------------------------
/src/convex_shape.rs:
--------------------------------------------------------------------------------
1 | use 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)]
12 | pub struct SphereShape {
13 | pub radius: f32
14 | }
15 |
16 | pub trait CircumRadius {
17 | fn circumradius(&self) -> f32;
18 | }
19 |
20 | impl 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 |
30 | impl CircumRadius for SphereShape {
31 | fn circumradius(&self) -> f32 {
32 | self.radius
33 | }
34 | }
35 |
36 | impl Default for SphereShape {
37 | fn default() -> Self {
38 | Self {
39 | radius: 0.5,
40 | }
41 | }
42 | }
43 |
44 | impl 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)]
66 | pub struct TriangleShape {
67 | pub vertices: [Vec3; 3]
68 | }
69 |
70 | impl CircumRadius for TriangleShape {
71 | fn circumradius(&self) -> f32 {
72 | AxisAlignedBoundingBox::from_points(&self.vertices).half_extents().max_value()
73 | }
74 | }
75 |
76 | impl 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 |
88 | impl 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 |
99 | impl TriangleShape {
100 | pub fn new(vertices: [Vec3; 3]) -> Self {
101 | Self {
102 | vertices
103 | }
104 | }
105 |
106 | pub fn get_normal(&self) -> Option {
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)]
116 | pub struct BoxShape {
117 | half_extents: Vec3,
118 | }
119 |
120 | impl CircumRadius for BoxShape {
121 | fn circumradius(&self) -> f32 {
122 | self.half_extents.max_value()
123 | }
124 | }
125 |
126 | impl 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 |
136 | impl Default for BoxShape {
137 | fn default() -> Self {
138 | Self {
139 | half_extents: Vec3::new(0.5, 0.5, 0.5)
140 | }
141 | }
142 | }
143 |
144 | impl 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)]
177 | pub struct PointCloudShape {
178 | points: Vec
179 | }
180 |
181 | impl 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 |
188 | impl 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 |
198 | impl Default for PointCloudShape {
199 | fn default() -> Self {
200 | Self {
201 | points: Vec::new(),
202 | }
203 | }
204 | }
205 |
206 | impl PointCloudShape {
207 | pub fn new(points: Vec) -> 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)]
219 | pub enum Axis {
220 | X = 0,
221 | Y = 1,
222 | Z = 2,
223 | }
224 |
225 | impl 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)]
242 | pub struct CapsuleShape {
243 | axis: Axis,
244 | radius: f32,
245 | height: f32,
246 | }
247 |
248 | impl CircumRadius for CapsuleShape {
249 | fn circumradius(&self) -> f32 {
250 | self.radius.max(self.height)
251 | }
252 | }
253 |
254 | impl 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 |
264 | impl 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 |
276 | impl 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)]
354 | pub enum ConvexShape {
355 | Dummy,
356 | Box(BoxShape),
357 | Sphere(SphereShape),
358 | Capsule(CapsuleShape),
359 | Triangle(TriangleShape),
360 | PointCloud(PointCloudShape),
361 | }
362 |
363 | impl 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 |
376 | impl 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 {
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 |
418 | impl 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 |
432 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate rg3d_core;
2 | #[macro_use]
3 | extern crate bitflags;
4 |
5 | use rg3d_core::{
6 | math::{
7 | vec3::Vec3,
8 | ray::Ray,
9 | },
10 | pool::{
11 | Pool,
12 | Handle,
13 | },
14 | visitor::{
15 | Visit,
16 | VisitResult,
17 | Visitor,
18 | },
19 | };
20 | use std::{
21 | cmp::Ordering,
22 | cell::RefCell
23 | };
24 | use crate::{
25 | rigid_body::RigidBody,
26 | static_geometry::StaticGeometry,
27 | convex_shape::{
28 | ConvexShape,
29 | CircumRadius
30 | }
31 | };
32 | use rg3d_core::pool::Ticket;
33 |
34 | pub mod gjk_epa;
35 | pub mod convex_shape;
36 | pub mod rigid_body;
37 | pub mod contact;
38 | pub mod static_geometry;
39 |
40 | pub enum HitKind {
41 | Body(Handle),
42 | StaticTriangle {
43 | static_geometry: Handle,
44 | triangle_index: usize,
45 | },
46 | }
47 |
48 | pub struct RayCastOptions {
49 | pub ignore_bodies: bool,
50 | pub ignore_static_geometries: bool,
51 | pub sort_results: bool,
52 | }
53 |
54 | impl Default for RayCastOptions {
55 | fn default() -> Self {
56 | Self {
57 | ignore_bodies: false,
58 | ignore_static_geometries: false,
59 | sort_results: true,
60 | }
61 | }
62 | }
63 |
64 | pub struct RayCastResult {
65 | pub kind: HitKind,
66 | pub position: Vec3,
67 | pub normal: Vec3,
68 | pub sqr_distance: f32,
69 | }
70 |
71 | #[derive(Debug)]
72 | pub struct Physics {
73 | bodies: Pool,
74 | static_geoms: Pool,
75 | query_buffer: RefCell>,
76 | enabled: bool
77 | }
78 |
79 | impl Visit for Physics {
80 | fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
81 | visitor.enter_region(name)?;
82 |
83 | self.bodies.visit("Bodies", visitor)?;
84 | self.static_geoms.visit("StaticGeoms", visitor)?;
85 | let _ = self.enabled.visit("Enabled", visitor); // let _ for backward compatibility.
86 |
87 | visitor.leave_region()
88 | }
89 | }
90 |
91 | impl Default for Physics {
92 | fn default() -> Self {
93 | Self::new()
94 | }
95 | }
96 |
97 | impl Clone for Physics {
98 | fn clone(&self) -> Self {
99 | Self {
100 | bodies: self.bodies.clone(),
101 | static_geoms: self.static_geoms.clone(),
102 | query_buffer: Default::default(),
103 | enabled: self.enabled
104 | }
105 | }
106 | }
107 |
108 | impl Physics {
109 | pub fn new() -> Self {
110 | Self {
111 | bodies: Pool::new(),
112 | static_geoms: Pool::new(),
113 | query_buffer: Default::default(),
114 | enabled: true
115 | }
116 | }
117 |
118 | pub fn add_body(&mut self, body: RigidBody) -> Handle {
119 | self.bodies.spawn(body)
120 | }
121 |
122 | pub fn remove_body(&mut self, body_handle: Handle) {
123 | self.bodies.free(body_handle);
124 | }
125 |
126 | pub fn add_static_geometry(&mut self, static_geom: StaticGeometry) -> Handle {
127 | self.static_geoms.spawn(static_geom)
128 | }
129 |
130 | pub fn borrow_static_geometry(&self, static_geom: Handle) -> &StaticGeometry {
131 | &self.static_geoms[static_geom]
132 | }
133 |
134 | pub fn borrow_static_geometry_mut(&mut self, static_geom: Handle) -> &mut StaticGeometry {
135 | &mut self.static_geoms[static_geom]
136 | }
137 |
138 | pub fn is_static_geometry_handle_valid(&self, static_geom: Handle) -> bool {
139 | self.static_geoms.is_valid_handle(static_geom)
140 | }
141 |
142 | pub fn remove_static_geometry(&mut self, static_geom: Handle) {
143 | self.static_geoms.free(static_geom);
144 | }
145 |
146 | pub fn borrow_body(&self, handle: Handle) -> &RigidBody {
147 | self.bodies.borrow(handle)
148 | }
149 |
150 | pub fn borrow_body_mut(&mut self, handle: Handle) -> &mut RigidBody {
151 | self.bodies.borrow_mut(handle)
152 | }
153 |
154 | pub fn is_valid_body_handle(&self, handle: Handle) -> bool {
155 | self.bodies.is_valid_handle(handle)
156 | }
157 |
158 | pub fn take_reserve_body(&mut self, handle: Handle) -> (Ticket, RigidBody) {
159 | self.bodies.take_reserve(handle)
160 | }
161 |
162 | pub fn put_body_back(&mut self, ticket: Ticket, body: RigidBody) -> Handle {
163 | self.bodies.put_back(ticket, body)
164 | }
165 |
166 | pub fn forget_body_ticket(&mut self, ticket: Ticket) {
167 | self.bodies.forget_ticket(ticket)
168 | }
169 |
170 | pub fn take_reserve_static_geometry(&mut self, handle: Handle) -> (Ticket, StaticGeometry) {
171 | self.static_geoms.take_reserve(handle)
172 | }
173 |
174 | pub fn put_static_geometry_back(&mut self, ticket: Ticket, geom: StaticGeometry) -> Handle {
175 | self.static_geoms.put_back(ticket, geom)
176 | }
177 |
178 | pub fn forget_static_geometry_ticket(&mut self, ticket: Ticket) {
179 | self.static_geoms.forget_ticket(ticket)
180 | }
181 |
182 | pub fn set_enabled(&mut self, enabled: bool) {
183 | self.enabled = enabled;
184 | }
185 |
186 | pub fn enabled(&self) -> bool {
187 | self.enabled
188 | }
189 |
190 | pub fn step(&mut self, delta_time: f32) {
191 | if !self.enabled {
192 | return;
193 | }
194 |
195 | let dt2 = delta_time * delta_time;
196 | let air_friction = 0.003;
197 |
198 | // Take second mutable reference to bodies, this is safe because:
199 | // 1. We won't modify collection while iterating over it.
200 | // 2. Simultaneous access to a body won't happen because of
201 | // pointer equality check down below.
202 | let other_bodies = unsafe { &mut *(&mut self.bodies as *mut Pool) };
203 |
204 | for (body_handle, body) in self.bodies.pair_iter_mut() {
205 | if let Some(ref mut lifetime) = body.lifetime {
206 | *lifetime -= delta_time;
207 | }
208 |
209 | body.acceleration += body.gravity;
210 | body.verlet(dt2, air_friction);
211 |
212 | body.contacts.clear();
213 |
214 | for (other_body_handle, other_body) in other_bodies.pair_iter_mut() {
215 | // Enforce borrowing rules at runtime.
216 | if !std::ptr::eq(body, other_body) &&
217 | ((other_body.collision_group & body.collision_mask) != 0) &&
218 | ((body.collision_group & other_body.collision_mask) != 0) {
219 | body.solve_rigid_body_collision(body_handle,other_body, other_body_handle);
220 | }
221 | }
222 |
223 | for (handle, static_geometry) in self.static_geoms.pair_iter() {
224 | let mut query_buffer = self.query_buffer.borrow_mut();
225 | static_geometry.octree.sphere_query(body.position, body.shape.circumradius(), &mut query_buffer);
226 |
227 | for n in query_buffer.iter().map(|i| *i as usize) {
228 | let triangle = static_geometry.triangles.get(n).unwrap();
229 | body.solve_triangle_collision(&triangle, n, handle);
230 | }
231 | }
232 | }
233 |
234 | self.bodies.retain(|body| body.lifetime.is_none() || body.lifetime.unwrap() > 0.0);
235 | }
236 |
237 | pub fn ray_cast(&self, ray: &Ray, options: RayCastOptions, result: &mut Vec) -> bool {
238 | result.clear();
239 |
240 | // Check bodies
241 | if !options.ignore_bodies {
242 | for body_index in 0..self.bodies.get_capacity() {
243 | let body = if let Some(body) = self.bodies.at(body_index) {
244 | body
245 | } else {
246 | continue;
247 | };
248 |
249 | let body_handle = self.bodies.handle_from_index(body_index);
250 |
251 | match &body.shape {
252 | ConvexShape::Dummy => {}
253 | ConvexShape::Box(box_shape) => {
254 | if let Some(points) = ray.box_intersection_points(&box_shape.get_min(), &box_shape.get_max()) {
255 | for point in points.iter() {
256 | result.push(RayCastResult {
257 | kind: HitKind::Body(body_handle),
258 | position: *point,
259 | normal: *point - body.position, // TODO: Fix normal
260 | sqr_distance: point.sqr_distance(&ray.origin),
261 | })
262 | }
263 | }
264 | }
265 | ConvexShape::Sphere(sphere_shape) => {
266 | if let Some(points) = ray.sphere_intersection_points(&body.position, sphere_shape.radius) {
267 | for point in points.iter() {
268 | result.push(RayCastResult {
269 | kind: HitKind::Body(body_handle),
270 | position: *point,
271 | normal: *point - body.position,
272 | sqr_distance: point.sqr_distance(&ray.origin),
273 | })
274 | }
275 | }
276 | }
277 | ConvexShape::Capsule(capsule_shape) => {
278 | let (pa, pb) = capsule_shape.get_cap_centers();
279 | let pa = pa + body.position;
280 | let pb = pb + body.position;
281 |
282 | if let Some(points) = ray.capsule_intersection(&pa, &pb, capsule_shape.get_radius()) {
283 | for point in points.iter() {
284 | result.push(RayCastResult {
285 | kind: HitKind::Body(body_handle),
286 | position: *point,
287 | normal: *point - body.position,
288 | sqr_distance: point.sqr_distance(&ray.origin),
289 | })
290 | }
291 | }
292 | }
293 | ConvexShape::Triangle(triangle_shape) => {
294 | if let Some(point) = ray.triangle_intersection(&triangle_shape.vertices) {
295 | result.push(RayCastResult {
296 | kind: HitKind::Body(body_handle),
297 | position: point,
298 | normal: triangle_shape.get_normal().unwrap(),
299 | sqr_distance: point.sqr_distance(&ray.origin),
300 | })
301 | }
302 | }
303 | ConvexShape::PointCloud(_point_cloud) => {
304 | // TODO: Implement this. This requires to build convex hull from point cloud first
305 | // i.e. by gift wrapping algorithm or some other more efficient algorithms -
306 | // https://dccg.upc.edu/people/vera/wp-content/uploads/2014/11/GA2014-ConvexHulls3D-Roger-Hernando.pdf
307 | }
308 | }
309 | }
310 | }
311 |
312 | // Check static geometries
313 | if !options.ignore_static_geometries {
314 | for (handle, geom) in self.static_geoms.pair_iter() {
315 | let mut query_buffer = self.query_buffer.borrow_mut();
316 | geom.octree.ray_query(ray, &mut query_buffer);
317 |
318 | for triangle_index in query_buffer.iter().map(|i| *i as usize) {
319 | let triangle = geom.triangles.get(triangle_index).unwrap();
320 | if let Some(point) = ray.triangle_intersection(&triangle.points) {
321 | result.push(RayCastResult {
322 | kind: HitKind::StaticTriangle {
323 | static_geometry: handle,
324 | triangle_index,
325 | },
326 | position: point,
327 | normal: triangle.plane.normal,
328 | sqr_distance: point.sqr_distance(&ray.origin),
329 | })
330 | }
331 | }
332 | }
333 | }
334 |
335 | if options.sort_results {
336 | result.sort_by(|a, b| {
337 | if a.sqr_distance > b.sqr_distance {
338 | Ordering::Greater
339 | } else if a.sqr_distance < b.sqr_distance {
340 | Ordering::Less
341 | } else {
342 | Ordering::Equal
343 | }
344 | })
345 | }
346 |
347 | !result.is_empty()
348 | }
349 | }
350 |
--------------------------------------------------------------------------------
/src/gjk_epa.rs:
--------------------------------------------------------------------------------
1 | /// Gilbert-Johnson-Keerthi (GJK) intersection test + Expanding Polytope
2 | /// Algorithm (EPA) implementations.
3 | ///
4 | /// "Implementing GJK", Casey Muratori:
5 | /// https://www.youtube.com/watch?v=Qupqu1xe7Io
6 | ///
7 | /// "GJK + Expanding Polytope Algorithm - Implementation and Visualization"
8 | /// https://www.youtube.com/watch?v=6rgiPrzqt9w
9 | ///
10 | /// Some ideas about contact point generation were taken from here
11 | /// https://www.gamedev.net/forums/topic/598678-contact-points-with-epa/
12 |
13 | use rg3d_core::{
14 | math::vec3::Vec3,
15 | math,
16 | };
17 | use crate::convex_shape::ConvexShape;
18 |
19 | pub const GJK_MAX_ITERATIONS: usize = 64;
20 | pub const EPA_TOLERANCE: f32 = 0.0001;
21 | pub const EPA_MAX_ITERATIONS: usize = 64;
22 | pub const EPA_MAX_LOOSE_EDGES: usize = 32;
23 | pub const EPA_MAX_FACES: usize = 64;
24 |
25 | /// Vertex in space of Minkowski sum
26 | #[derive(Copy, Clone)]
27 | pub struct MinkowskiVertex {
28 | /// World space position of vertex of shape A. This position will be used
29 | /// to compute contact point in world space.
30 | shape_a_world_space: Vec3,
31 |
32 | /// Minkowski difference between point in shape A and point in shape B.
33 | /// This position will be used to do all simplex and polytope operations.
34 | /// https://en.wikipedia.org/wiki/Minkowski_addition
35 | minkowski_dif: Vec3,
36 | }
37 |
38 | impl Default for MinkowskiVertex {
39 | fn default() -> Self {
40 | Self {
41 | shape_a_world_space: Default::default(),
42 | minkowski_dif: Default::default(),
43 | }
44 | }
45 | }
46 |
47 | #[derive(Copy, Clone)]
48 | struct PolytopeTriangle {
49 | vertices: [MinkowskiVertex; 3],
50 | normal: Vec3,
51 | }
52 |
53 | impl Default for PolytopeTriangle {
54 | fn default() -> Self {
55 | Self {
56 | vertices: Default::default(),
57 | normal: Default::default(),
58 | }
59 | }
60 | }
61 |
62 | #[derive(Copy, Clone)]
63 | struct PolytopeEdge {
64 | begin: MinkowskiVertex,
65 | end: MinkowskiVertex,
66 | }
67 |
68 | impl Default for PolytopeEdge {
69 | fn default() -> Self {
70 | Self {
71 | begin: Default::default(),
72 | end: Default::default(),
73 | }
74 | }
75 | }
76 |
77 | impl PolytopeEdge {
78 | /// Returns true if edges are equal in combination like this:
79 | /// ab = ba
80 | fn eq_ccw(&self, other: &PolytopeEdge) -> bool {
81 | self.begin.minkowski_dif == other.end.minkowski_dif &&
82 | self.end.minkowski_dif == other.begin.minkowski_dif
83 | }
84 | }
85 |
86 | pub struct Simplex {
87 | /// Vertices of simplex.
88 | /// Important: a is most recently added point (closest to origin)!
89 | a: MinkowskiVertex,
90 | b: MinkowskiVertex,
91 | c: MinkowskiVertex,
92 | d: MinkowskiVertex,
93 | /// Rank of simplex
94 | /// 1 - point
95 | /// 2 - line
96 | /// 3 - triangle
97 | /// 4 - tetrahedron
98 | rank: usize,
99 | }
100 |
101 | impl Default for Simplex {
102 | fn default() -> Self {
103 | Self {
104 | a: Default::default(),
105 | b: Default::default(),
106 | c: Default::default(),
107 | d: Default::default(),
108 | rank: 0,
109 | }
110 | }
111 | }
112 |
113 | impl Simplex {
114 | fn update_triangle(&mut self) -> Vec3 {
115 | let ca = self.c.minkowski_dif - self.a.minkowski_dif;
116 | let ba = self.b.minkowski_dif - self.a.minkowski_dif;
117 |
118 | // Direction to origin
119 | let ao = -self.a.minkowski_dif;
120 |
121 | self.rank = 2;
122 |
123 | let triangle_normal = ba.cross(&ca);
124 |
125 | if ba.cross(&triangle_normal).is_same_direction_as(&ao) {
126 | // Closest to edge AB
127 | self.c = self.a;
128 | return ba.cross(&ao).cross(&ba);
129 | }
130 |
131 | if triangle_normal.cross(&ca).is_same_direction_as(&ao) {
132 | // Closest to edge AC
133 | self.b = self.a;
134 | return ca.cross(&ao).cross(&ca);
135 | }
136 |
137 | self.rank = 3;
138 |
139 | if triangle_normal.is_same_direction_as(&ao) {
140 | // Above triangle
141 | self.d = self.c;
142 | self.c = self.b;
143 | self.b = self.a;
144 | return triangle_normal;
145 | }
146 |
147 | // Below triangle
148 | self.d = self.b;
149 | self.b = self.a;
150 |
151 | -triangle_normal
152 | }
153 |
154 | fn update_tetrahedron(&mut self) -> Result<(), Vec3> {
155 | // Point a is tip of pyramid, BCD is the base (counterclockwise winding order)
156 |
157 | // Direction to origin
158 | let ao = -self.a.minkowski_dif;
159 |
160 | // Plane-test origin with 3 faces. This is very inaccurate approach and
161 | // it would be better to add additional checks for each face of tetrahedron
162 | // to select search direction more precisely. In this case we assume that
163 | //we always will produce triangles as final simplex.
164 | self.rank = 3;
165 |
166 | let ba = self.b.minkowski_dif - self.a.minkowski_dif;
167 | let ca = self.c.minkowski_dif - self.a.minkowski_dif;
168 |
169 | let abc_normal = ba.cross(&ca);
170 | if abc_normal.is_same_direction_as(&ao) {
171 | // In front of ABC
172 | self.d = self.c;
173 | self.c = self.b;
174 | self.b = self.a;
175 | return Err(abc_normal);
176 | }
177 |
178 | let da = self.d.minkowski_dif - self.a.minkowski_dif;
179 | let acd_normal = ca.cross(&da);
180 | if acd_normal.is_same_direction_as(&ao) {
181 | // In front of ACD
182 | self.b = self.a;
183 | return Err(acd_normal);
184 | }
185 |
186 | let adb_normal = da.cross(&ba);
187 | if adb_normal.is_same_direction_as(&ao) {
188 | // In front of ADB
189 | self.c = self.d;
190 | self.d = self.b;
191 | self.b = self.a;
192 | return Err(adb_normal);
193 | }
194 |
195 | // Otherwise origin is inside tetrahedron
196 | Ok(())
197 | }
198 | }
199 |
200 | fn de_gjk_support(shape1: &ConvexShape, shape1_position: Vec3, shape2: &ConvexShape, shape2_position: Vec3, dir: &Vec3) -> MinkowskiVertex
201 | {
202 | let shape_a_world_space = shape1.get_farthest_point(shape1_position, *dir);
203 | let b = shape2.get_farthest_point(shape2_position, -*dir);
204 |
205 | MinkowskiVertex {
206 | shape_a_world_space,
207 | minkowski_dif: shape_a_world_space - b,
208 | }
209 | }
210 |
211 | pub fn gjk_is_intersects(shape1: &ConvexShape, shape1_position: Vec3, shape2: &ConvexShape, shape2_position: Vec3) -> Option {
212 | // This is good enough heuristic to choose initial search direction
213 | let mut search_dir = shape1_position - shape2_position;
214 |
215 | if search_dir.sqr_len() == 0.0 {
216 | search_dir.x = 1.0;
217 | }
218 |
219 | // Get initial point for simplex
220 | let mut simplex = Simplex::default();
221 | simplex.c = de_gjk_support(shape1, shape1_position, shape2, shape2_position, &search_dir);
222 | search_dir = -simplex.c.minkowski_dif; // Search in direction of origin
223 |
224 | // Get second point for a line segment simplex
225 | simplex.b = de_gjk_support(shape1, shape1_position, shape2, shape2_position, &search_dir);
226 |
227 | if !simplex.b.minkowski_dif.is_same_direction_as(&search_dir) {
228 | return None;
229 | }
230 |
231 | let cb = simplex.c.minkowski_dif - simplex.b.minkowski_dif;
232 |
233 | // Search perpendicular to line segment towards origin
234 | search_dir = cb.cross(&(-simplex.b.minkowski_dif)).cross(&cb);
235 |
236 | // Origin is on this line segment - fix search direction.
237 | if search_dir.sqr_len() == 0.0 {
238 | // Perpendicular with x-axis
239 | search_dir = cb.cross(&Vec3::new(1.0, 0.0, 0.0));
240 | if search_dir.sqr_len() == 0.0 {
241 | // Perpendicular with z-axis
242 | search_dir = cb.cross(&Vec3::new(0.0, 0.0, -1.0));
243 | }
244 | }
245 |
246 | simplex.rank = 2;
247 | for _ in 0..GJK_MAX_ITERATIONS {
248 | simplex.a = de_gjk_support(shape1, shape1_position, shape2, shape2_position, &search_dir);
249 |
250 | if !simplex.a.minkowski_dif.is_same_direction_as(&search_dir) {
251 | return None;
252 | }
253 |
254 | simplex.rank += 1;
255 | if simplex.rank == 3 {
256 | search_dir = simplex.update_triangle();
257 | } else {
258 | match simplex.update_tetrahedron() {
259 | Ok(_) => return Some(simplex),
260 | Err(dir) => search_dir = dir,
261 | }
262 | }
263 | }
264 |
265 | // No convergence - no intersection
266 | None
267 | }
268 |
269 | fn epa_compute_contact_point(closest_triangle: PolytopeTriangle) -> Vec3 {
270 | // Project origin onto triangle's plane
271 | let proj = closest_triangle.normal.scale(
272 | closest_triangle.vertices[0].minkowski_dif.dot(&closest_triangle.normal));
273 |
274 | // Find barycentric coordinates of the projection in Minkowski difference space
275 | let (u, v, w) = math::get_barycentric_coords(
276 | &proj, &closest_triangle.vertices[0].minkowski_dif,
277 | &closest_triangle.vertices[1].minkowski_dif,
278 | &closest_triangle.vertices[2].minkowski_dif);
279 |
280 | // Use barycentric coordinates to get projection in world space and sum all
281 | // vectors to get world space contact point
282 | closest_triangle.vertices[0].shape_a_world_space.scale(u) +
283 | closest_triangle.vertices[1].shape_a_world_space.scale(v) +
284 | closest_triangle.vertices[2].shape_a_world_space.scale(w)
285 | }
286 |
287 | pub struct PenetrationInfo {
288 | pub penetration_vector: Vec3,
289 | pub contact_point: Vec3,
290 | }
291 |
292 | pub fn epa_get_penetration_info(simplex: Simplex, shape1: &ConvexShape, shape1_position: Vec3,
293 | shape2: &ConvexShape, shape2_position: Vec3) -> Option {
294 | let mut triangles = [PolytopeTriangle::default(); EPA_MAX_FACES];
295 |
296 | // Reconstruct polytope from tetrahedron simplex points.
297 |
298 | // ABC
299 | let ba = simplex.b.minkowski_dif - simplex.a.minkowski_dif;
300 | let ca = simplex.c.minkowski_dif - simplex.a.minkowski_dif;
301 |
302 | let abc_normal = ba.cross(&ca);
303 | if let Some(abc_normal) = abc_normal.normalized() {
304 | triangles[0] = PolytopeTriangle {
305 | vertices: [simplex.a, simplex.b, simplex.c],
306 | normal: abc_normal,
307 | };
308 | } else {
309 | return None;
310 | }
311 |
312 | // ACD
313 | let da = simplex.d.minkowski_dif - simplex.a.minkowski_dif;
314 |
315 | let acd_normal = ca.cross(&da);
316 | if let Some(acd_normal) = acd_normal.normalized() {
317 | triangles[1] = PolytopeTriangle {
318 | vertices: [simplex.a, simplex.c, simplex.d],
319 | normal: acd_normal,
320 | };
321 | } else {
322 | return None;
323 | }
324 |
325 | // ADB
326 | let adb_normal = da.cross(&ba);
327 | if let Some(adb_normal) = adb_normal.normalized() {
328 | triangles[2] = PolytopeTriangle {
329 | vertices: [simplex.a, simplex.d, simplex.b],
330 | normal: adb_normal,
331 | };
332 | } else {
333 | return None;
334 | }
335 |
336 | // BDC
337 | let db = simplex.d.minkowski_dif - simplex.b.minkowski_dif;
338 | let cb = simplex.c.minkowski_dif - simplex.b.minkowski_dif;
339 |
340 | let bdc_normal = db.cross(&cb);
341 | if let Some(bdc_normal) = bdc_normal.normalized() {
342 | triangles[3] = PolytopeTriangle {
343 | vertices: [simplex.b, simplex.d, simplex.c],
344 | normal: bdc_normal,
345 | };
346 | } else {
347 | return None;
348 | }
349 |
350 | let mut triangle_count = 4;
351 | let mut closest_triangle_index = 0;
352 |
353 | for _ in 0..EPA_MAX_ITERATIONS {
354 | // Find triangle that is closest to origin
355 | let mut min_dist = triangles[0].vertices[0].minkowski_dif.dot(&triangles[0].normal);
356 | closest_triangle_index = 0;
357 | for (i, triangle) in triangles.iter().enumerate().take(triangle_count).skip(1) {
358 | let dist = triangle.vertices[0].minkowski_dif.dot(&triangle.normal);
359 | if dist < min_dist {
360 | min_dist = dist;
361 | closest_triangle_index = i;
362 | }
363 | }
364 |
365 | // Search normal to triangle that's closest to origin
366 | let closest_triangle = triangles[closest_triangle_index];
367 | let search_dir = closest_triangle.normal;
368 | let new_point = de_gjk_support(shape1, shape1_position, shape2, shape2_position, &search_dir);
369 |
370 | let distance_to_origin = new_point.minkowski_dif.dot(&search_dir);
371 | if distance_to_origin - min_dist < EPA_TOLERANCE {
372 | return Some(PenetrationInfo {
373 | penetration_vector: closest_triangle.normal.scale(distance_to_origin),
374 | contact_point: epa_compute_contact_point(closest_triangle),
375 | });
376 | }
377 |
378 | // Loose edges after we remove triangle must give us list of edges we have
379 | // to stitch with new point to keep polytope convex.
380 | let mut loose_edge_count = 0;
381 | let mut loose_edges = [PolytopeEdge::default(); EPA_MAX_LOOSE_EDGES];
382 |
383 | // Find all triangles that are facing new point and remove them
384 | let mut i = 0;
385 | while i < triangle_count {
386 | let triangle = triangles[i];
387 |
388 | // If triangle i faces new point, remove it. Also search for adjacent edges of it
389 | // and remove them too to maintain loose edge list in correct state (see below).
390 | let to_new_point = new_point.minkowski_dif - triangle.vertices[0].minkowski_dif;
391 |
392 | if triangle.normal.dot(&to_new_point) > 0.0 {
393 | for j in 0..3 {
394 | let current_edge = PolytopeEdge {
395 | begin: triangle.vertices[j],
396 | end: triangle.vertices[(j + 1) % 3],
397 | };
398 |
399 | let mut already_in_list = false;
400 | // Check if current edge is already in list
401 | let last_loose_edge = loose_edge_count;
402 | for k in 0..last_loose_edge {
403 | if loose_edges[k].eq_ccw(¤t_edge) {
404 | // If we found that current edge is same as other loose edge
405 | // but in reverse order, then we need to replace the loose
406 | // edge by the last loose edge. Lets see at this drawing and
407 | // follow it step-by-step. This is pyramid with tip below A
408 | // and bottom BCDE divided into 4 triangles by point A. All
409 | // triangles given in CCW order.
410 | //
411 | // B
412 | // /|\
413 | // / | \
414 | // / | \
415 | // / | \
416 | // / | \
417 | // E-----A-----C
418 | // \ | /
419 | // \ | /
420 | // \ | /
421 | // \ | /
422 | // \|/
423 | // D
424 | //
425 | // We found that triangles we want to remove are ACB (1), ADC (2),
426 | // AED (3), and ABE (4). Lets start removing them from triangle ACB.
427 | // Also we have to keep list of loose edges for futher linking with
428 | // new point.
429 | //
430 | // 1. AC, CB, BA - just edges of ACB triangle in CCW order.
431 | //
432 | // 2. AC, CB, BA, AD, DC, (CA) - we see that CA already in list
433 | // but in reverse order. Do not add it
434 | // but move DC onto AC position.
435 | // DC, CB, BA, AD - loose edge list for 2nd triangle
436 | //
437 | // 3. DC, CB, BA, AD, AE, ED, (DA) - again we already have DA in list as AD
438 | // move ED to AD position.
439 | // DC, CB, BA, ED, AE
440 | //
441 | // 4. DC, CB, BA, ED, AE, (AB) - same AB already here as BA, move AE to BA.
442 | // continue adding rest of edges
443 | // DC, CB, AE, ED, BE, (EA) - EA already here as AE, move BE to AE
444 | //
445 | // DC, CB, BE, ED - final list of loose edges which gives us
446 | //
447 | // B
448 | // / \
449 | // / \
450 | // / \
451 | // / \
452 | // / \
453 | // E C
454 | // \ /
455 | // \ /
456 | // \ /
457 | // \ /
458 | // \ /
459 | // D
460 | //
461 | // Viola! We now have contour which we have to patch using new point.
462 | loose_edge_count -= 1;
463 | loose_edges.swap(k, loose_edge_count);
464 |
465 | already_in_list = true;
466 | break;
467 | }
468 | }
469 |
470 | if !already_in_list {
471 | // Add current edge to list
472 | if loose_edge_count >= EPA_MAX_LOOSE_EDGES {
473 | break;
474 | }
475 | loose_edges[loose_edge_count] = current_edge;
476 | loose_edge_count += 1;
477 | }
478 | }
479 |
480 | // Replace current triangle with last in list and discard last, so we will continue
481 | // processing last triangle and then next to removed. This will effectively reduce
482 | // amount of triangles in polytope.
483 | triangle_count -= 1;
484 | triangles.swap(i, triangle_count);
485 | } else {
486 | i += 1;
487 | }
488 | }
489 |
490 | // Reconstruct polytope with new point added
491 | for loose_edge in loose_edges[0..loose_edge_count].iter() {
492 | if triangle_count >= EPA_MAX_FACES {
493 | break;
494 | }
495 | let new_triangle = triangles.get_mut(triangle_count).unwrap();
496 | new_triangle.vertices = [loose_edge.begin, loose_edge.end, new_point];
497 |
498 | let edge_vector = loose_edge.begin.minkowski_dif - loose_edge.end.minkowski_dif;
499 | let begin_to_point = loose_edge.begin.minkowski_dif - new_point.minkowski_dif;
500 |
501 | new_triangle.normal = edge_vector.cross(&begin_to_point).normalized().unwrap_or(Vec3::UP);
502 |
503 | // Check for wrong normal to maintain CCW winding
504 | let bias = 2.0 * std::f32::EPSILON;
505 | if new_triangle.vertices[0].minkowski_dif.dot(&new_triangle.normal) + bias < 0.0 {
506 | // Swap vertices to make CCW winding and flip normal.
507 | new_triangle.vertices.swap(0, 1);
508 | new_triangle.normal = -new_triangle.normal;
509 | }
510 | triangle_count += 1;
511 | }
512 | }
513 |
514 | // Return most recent closest point - this is still valid result but less accurate than
515 | // if we would have total convergence.
516 | let closest_triangle = triangles[closest_triangle_index];
517 | Some(PenetrationInfo {
518 | penetration_vector: closest_triangle.normal.scale(closest_triangle.vertices[0].minkowski_dif.dot(&closest_triangle.normal)),
519 | contact_point: epa_compute_contact_point(closest_triangle),
520 | })
521 | }
--------------------------------------------------------------------------------