avian3d/dynamics/solver/contact/
mod.rs1mod normal_part;
4mod tangent_part;
5
6pub use normal_part::ContactNormalPart;
7pub use tangent_part::ContactTangentPart;
8
9use crate::prelude::*;
10use bevy::{
11 ecs::entity::{Entity, EntityMapper, MapEntities},
12 reflect::Reflect,
13 utils::default,
14};
15
16#[derive(Clone, Debug, PartialEq, Reflect)]
19pub struct ContactConstraintPoint {
20 pub normal_part: ContactNormalPart,
22
23 pub tangent_part: Option<ContactTangentPart>,
27
28 pub max_normal_impulse: Scalar,
33
34 pub local_anchor1: Vector,
37
38 pub local_anchor2: Vector,
40
41 pub anchor1: Vector,
43
44 pub anchor2: Vector,
46
47 pub normal_speed: Scalar,
49
50 pub initial_separation: Scalar,
54}
55
56#[derive(Clone, Debug, PartialEq, Reflect)]
61pub struct ContactConstraint {
62 pub body1: Entity,
64 pub body2: Entity,
66 pub collider1: Entity,
68 pub collider2: Entity,
70 pub friction: Scalar,
72 pub restitution: Scalar,
74 #[cfg(feature = "2d")]
80 pub tangent_speed: Scalar,
81 #[cfg(feature = "3d")]
87 pub tangent_velocity: Vector,
88 pub normal: Vector,
90 pub points: Vec<ContactConstraintPoint>,
92 #[cfg(feature = "parallel")]
98 pub pair_index: usize,
99 pub manifold_index: usize,
101}
102
103impl ContactConstraint {
104 pub fn warm_start(
106 &self,
107 body1: &mut RigidBodyQueryItem,
108 body2: &mut RigidBodyQueryItem,
109 normal: Vector,
110 tangent_directions: [Vector; DIM - 1],
111 warm_start_coefficient: Scalar,
112 ) {
113 let inv_mass1 = body1.effective_inverse_mass();
114 let inv_mass2 = body2.effective_inverse_mass();
115 let inv_inertia1 = body1.effective_global_angular_inertia().inverse();
116 let inv_inertia2 = body2.effective_global_angular_inertia().inverse();
117
118 for point in self.points.iter() {
119 let r1 = point.anchor1;
121 let r2 = point.anchor2;
122
123 let tangent_impulse = point
124 .tangent_part
125 .as_ref()
126 .map_or(default(), |part| part.impulse);
127
128 #[cfg(feature = "2d")]
129 let p = warm_start_coefficient
130 * (point.normal_part.impulse * normal + tangent_impulse * tangent_directions[0]);
131 #[cfg(feature = "3d")]
132 let p = warm_start_coefficient
133 * (point.normal_part.impulse * normal
134 + tangent_impulse.x * tangent_directions[0]
135 + tangent_impulse.y * tangent_directions[1]);
136
137 if body1.rb.is_dynamic() && body1.dominance() <= body2.dominance() {
138 body1.linear_velocity.0 -= p * inv_mass1;
139 body1.angular_velocity.0 -= inv_inertia1 * cross(r1, p);
140 }
141 if body2.rb.is_dynamic() && body2.dominance() <= body1.dominance() {
142 body2.linear_velocity.0 += p * inv_mass2;
143 body2.angular_velocity.0 += inv_inertia2 * cross(r2, p);
144 }
145 }
146 }
147
148 pub fn solve(
150 &mut self,
151 body1: &mut RigidBodyQueryItem,
152 body2: &mut RigidBodyQueryItem,
153 delta_secs: Scalar,
154 use_bias: bool,
155 max_overlap_solve_speed: Scalar,
156 ) {
157 let inv_mass1 = body1.effective_inverse_mass();
158 let inv_mass2 = body2.effective_inverse_mass();
159 let inv_inertia1 = body1.effective_global_angular_inertia().inverse();
160 let inv_inertia2 = body2.effective_global_angular_inertia().inverse();
161
162 let delta_translation = body2.accumulated_translation.0 - body1.accumulated_translation.0;
163
164 for point in self.points.iter_mut() {
166 let r1 = *body1.rotation * point.local_anchor1;
167 let r2 = *body2.rotation * point.local_anchor2;
168
169 let delta_separation = delta_translation + (r2 - r1);
171 let separation = delta_separation.dot(self.normal) + point.initial_separation;
172
173 let r1 = point.anchor1;
175 let r2 = point.anchor2;
176
177 let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
179
180 let impulse_magnitude = point.normal_part.solve_impulse(
182 separation,
183 relative_velocity,
184 self.normal,
185 use_bias,
186 max_overlap_solve_speed,
187 delta_secs,
188 );
189
190 point.max_normal_impulse = impulse_magnitude.max(point.max_normal_impulse);
192
193 if impulse_magnitude == 0.0 {
194 continue;
195 }
196
197 let impulse = impulse_magnitude * self.normal;
198
199 if body1.rb.is_dynamic() && body1.dominance() <= body2.dominance() {
201 body1.linear_velocity.0 -= impulse * inv_mass1;
202 body1.angular_velocity.0 -= inv_inertia1 * cross(r1, impulse);
203 }
204 if body2.rb.is_dynamic() && body2.dominance() <= body1.dominance() {
205 body2.linear_velocity.0 += impulse * inv_mass2;
206 body2.angular_velocity.0 += inv_inertia2 * cross(r2, impulse);
207 }
208 }
209
210 let tangent_directions =
211 self.tangent_directions(body1.linear_velocity.0, body2.linear_velocity.0);
212
213 for point in self.points.iter_mut() {
215 let Some(ref mut friction_part) = point.tangent_part else {
216 continue;
217 };
218
219 let r1 = point.anchor1;
221 let r2 = point.anchor2;
222
223 let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
225
226 let impulse = friction_part.solve_impulse(
228 tangent_directions,
229 relative_velocity,
230 #[cfg(feature = "2d")]
231 self.tangent_speed,
232 #[cfg(feature = "3d")]
233 self.tangent_velocity,
234 self.friction,
235 point.normal_part.impulse,
236 );
237
238 if body1.rb.is_dynamic() && body1.dominance() <= body2.dominance() {
240 body1.linear_velocity.0 -= impulse * inv_mass1;
241 body1.angular_velocity.0 -= inv_inertia1 * cross(r1, impulse);
242 }
243 if body2.rb.is_dynamic() && body2.dominance() <= body1.dominance() {
244 body2.linear_velocity.0 += impulse * inv_mass2;
245 body2.angular_velocity.0 += inv_inertia2 * cross(r2, impulse);
246 }
247 }
248 }
249
250 pub fn apply_restitution(
253 &mut self,
254 body1: &mut RigidBodyQueryItem,
255 body2: &mut RigidBodyQueryItem,
256 threshold: Scalar,
257 ) {
258 for point in self.points.iter_mut() {
259 if point.normal_speed > -threshold || point.max_normal_impulse == 0.0 {
262 continue;
263 }
264
265 let r1 = point.anchor1;
267 let r2 = point.anchor2;
268
269 let inv_mass1 = body1.effective_inverse_mass();
270 let inv_mass2 = body2.effective_inverse_mass();
271 let inv_inertia1 = body1.effective_global_angular_inertia().inverse();
272 let inv_inertia2 = body2.effective_global_angular_inertia().inverse();
273
274 let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
276 let normal_speed = relative_velocity.dot(self.normal);
277
278 let mut impulse = -point.normal_part.effective_mass
280 * (normal_speed + self.restitution * point.normal_speed);
281
282 let new_impulse = (point.normal_part.impulse + impulse).max(0.0);
284 impulse = new_impulse - point.normal_part.impulse;
285 point.normal_part.impulse = new_impulse;
286 point.max_normal_impulse = impulse.max(point.max_normal_impulse);
287
288 let impulse = impulse * self.normal;
290
291 if body1.rb.is_dynamic() && body1.dominance() <= body2.dominance() {
292 body1.linear_velocity.0 -= impulse * inv_mass1;
293 body1.angular_velocity.0 -= inv_inertia1 * cross(r1, impulse);
294 }
295 if body2.rb.is_dynamic() && body2.dominance() <= body1.dominance() {
296 body2.linear_velocity.0 += impulse * inv_mass2;
297 body2.angular_velocity.0 += inv_inertia2 * cross(r2, impulse);
298 }
299 }
300 }
301
302 #[allow(unused_variables)]
304 pub fn tangent_directions(&self, velocity1: Vector, velocity2: Vector) -> [Vector; DIM - 1] {
305 #[cfg(feature = "2d")]
306 {
307 [Vector::new(self.normal.y, -self.normal.x)]
308 }
309 #[cfg(feature = "3d")]
310 {
311 let force_direction = -self.normal;
312 let relative_velocity = velocity1 - velocity2;
313 let tangent_velocity =
314 relative_velocity - force_direction * force_direction.dot(relative_velocity);
315
316 let tangent = tangent_velocity
317 .try_normalize()
318 .unwrap_or(force_direction.any_orthonormal_vector());
319 let bitangent = force_direction.cross(tangent);
320 [tangent, bitangent]
321 }
322 }
323}
324
325impl MapEntities for ContactConstraint {
326 fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
327 self.body1 = entity_mapper.get_mapped(self.body1);
328 self.body2 = entity_mapper.get_mapped(self.body2);
329 self.collider1 = entity_mapper.get_mapped(self.collider1);
330 self.collider2 = entity_mapper.get_mapped(self.collider2);
331 }
332}