1use std::fmt::Debug;
2use std::marker;
3use std::ops::{Add, Mul, Sub};
4
5use cgmath::num_traits::{Float, NumCast};
6use cgmath::{BaseFloat, EuclideanSpace, InnerSpace, One, Rotation, Zero};
7use collision::Contact;
8
9use super::{Inertia, Mass, Material, PartialCrossProduct, Velocity};
10use {NextFrame, Pose};
11
12const POSITIONAL_CORRECTION_PERCENT: f32 = 0.2;
13const POSITIONAL_CORRECTION_K_SLOP: f32 = 0.01;
14
15#[derive(Debug, PartialEq)]
26pub struct SingleChangeSet<B, P, R, A>
27where
28 P: EuclideanSpace,
29 P::Scalar: BaseFloat,
30 R: Rotation<P>,
31 A: Clone,
32 B: Pose<P, R>,
33{
34 pose: Option<B>,
35 velocity: Option<NextFrame<Velocity<P::Diff, A>>>,
36 m: marker::PhantomData<(P, R)>,
37}
38
39impl<B, P, R, A> Default for SingleChangeSet<B, P, R, A>
40where
41 P: EuclideanSpace,
42 P::Scalar: BaseFloat,
43 R: Rotation<P>,
44 A: Clone,
45 B: Pose<P, R>,
46{
47 fn default() -> Self {
48 Self {
49 pose: None,
50 velocity: None,
51 m: marker::PhantomData,
52 }
53 }
54}
55
56impl<B, P, R, A> SingleChangeSet<B, P, R, A>
57where
58 P: EuclideanSpace,
59 P::Scalar: BaseFloat,
60 R: Rotation<P>,
61 A: Clone,
62 B: Pose<P, R>,
63{
64 #[allow(dead_code)]
65 fn new(pose: Option<B>, velocity: Option<NextFrame<Velocity<P::Diff, A>>>) -> Self {
66 SingleChangeSet {
67 pose,
68 velocity,
69 m: marker::PhantomData,
70 }
71 }
72
73 fn add_pose(&mut self, pose: Option<B>) {
74 self.pose = pose;
75 }
76
77 fn add_velocity(&mut self, velocity: Option<NextFrame<Velocity<P::Diff, A>>>) {
78 self.velocity = velocity;
79 }
80
81 pub fn apply(
83 self,
84 pose: Option<&mut NextFrame<B>>,
85 velocity: Option<&mut NextFrame<Velocity<P::Diff, A>>>,
86 ) {
87 if let (Some(pose), Some(update_pose)) = (pose, self.pose) {
88 pose.value = update_pose;
89 }
90 if let (Some(velocity), Some(update_velocity)) = (velocity, self.velocity) {
91 *velocity = update_velocity;
92 }
93 }
94}
95
96pub struct ResolveData<'a, B, P, R, I, A>
106where
107 P: EuclideanSpace + 'a,
108 P::Scalar: BaseFloat,
109 R: Rotation<P> + 'a,
110 I: 'a,
111 A: Clone + 'a,
112 B: Pose<P, R> + 'a,
113{
114 pub velocity: Option<&'a NextFrame<Velocity<P::Diff, A>>>,
116 pub pose: &'a B,
118 pub mass: &'a Mass<P::Scalar, I>,
120 pub material: &'a Material,
122 m: marker::PhantomData<(P, R)>,
123}
124
125impl<'a, B, P, R, I, A> ResolveData<'a, B, P, R, I, A>
126where
127 P: EuclideanSpace + 'a,
128 P::Scalar: BaseFloat,
129 R: Rotation<P> + 'a,
130 I: 'a,
131 A: Clone + 'a,
132 B: Pose<P, R> + 'a,
133{
134 pub fn new(
136 velocity: Option<&'a NextFrame<Velocity<P::Diff, A>>>,
137 pose: &'a B,
138 mass: &'a Mass<P::Scalar, I>,
139 material: &'a Material,
140 ) -> Self {
141 ResolveData {
142 velocity,
143 pose,
144 mass,
145 material,
146 m: marker::PhantomData,
147 }
148 }
149}
150
151pub fn resolve_contact<'a, B, P, R, I, A, O>(
174 contact: &Contact<P>,
175 a: &ResolveData<'a, B, P, R, I, A>,
176 b: &ResolveData<'a, B, P, R, I, A>,
177) -> (SingleChangeSet<B, P, R, A>, SingleChangeSet<B, P, R, A>)
178where
179 P: EuclideanSpace + 'a,
180 P::Scalar: BaseFloat,
181 R: Rotation<P> + 'a,
182 P::Diff: Debug + Zero + Clone + InnerSpace + PartialCrossProduct<P::Diff, Output = O>,
183 O: PartialCrossProduct<P::Diff, Output = P::Diff>,
184 A: PartialCrossProduct<P::Diff, Output = P::Diff> + Clone + Zero + 'a,
185 &'a A: Sub<O, Output = A> + Add<O, Output = A>,
186 I: Inertia<Orientation = R> + Mul<O, Output = O>,
187 B: Pose<P, R> + 'a,
188{
189 let a_velocity = a.velocity.map(|v| v.value.clone()).unwrap_or_default();
190 let b_velocity = b.velocity.map(|v| v.value.clone()).unwrap_or_default();
191 let a_inverse_mass = a.mass.inverse_mass();
192 let b_inverse_mass = b.mass.inverse_mass();
193 let total_inverse_mass = a_inverse_mass + b_inverse_mass;
194
195 let (a_position_new, b_position_new) =
197 positional_correction(contact, a.pose, b.pose, a_inverse_mass, b_inverse_mass);
198
199 let mut a_set = SingleChangeSet::default();
200 a_set.add_pose(a_position_new);
201 let mut b_set = SingleChangeSet::default();
202 b_set.add_pose(b_position_new);
203
204 if total_inverse_mass == P::Scalar::zero() {
207 return (a_set, b_set);
208 }
209
210 let r_a = contact.contact_point - a.pose.transform_point(P::origin());
211 let r_b = contact.contact_point - b.pose.transform_point(P::origin());
212
213 let p_a_dot = *a_velocity.linear() + a_velocity.angular().cross(&r_a);
214 let p_b_dot = *b_velocity.linear() + b_velocity.angular().cross(&r_b);
215
216 let rv = p_b_dot - p_a_dot;
217 let velocity_along_normal = contact.normal.dot(rv);
218
219 if velocity_along_normal > P::Scalar::zero() {
221 return (a_set, b_set);
222 }
223
224 let a_res: P::Scalar = a.material.restitution();
226 let b_res: P::Scalar = b.material.restitution();
227 let e = a_res.min(b_res);
228 let numerator = -(P::Scalar::one() + e) * velocity_along_normal;
229
230 let a_tensor = a.mass.world_inverse_inertia(&a.pose.rotation());
231 let b_tensor = b.mass.world_inverse_inertia(&b.pose.rotation());
232
233 let term3 = contact
234 .normal
235 .dot((a_tensor * (r_a.cross(&contact.normal))).cross(&r_a));
236 let term4 = contact
237 .normal
238 .dot((b_tensor * (r_b.cross(&contact.normal))).cross(&r_b));
239
240 let j = numerator / (a_inverse_mass + b_inverse_mass + term3 + term4);
241 let impulse = contact.normal * j;
242
243 let a_velocity_new = a.velocity.map(|v| NextFrame {
245 value: Velocity::new(
246 *v.value.linear() - impulse * a_inverse_mass,
247 v.value.angular() - a_tensor * r_a.cross(&impulse),
248 ),
249 });
250
251 let b_velocity_new = b.velocity.map(|v| NextFrame {
252 value: Velocity::new(
253 *v.value.linear() + impulse * b_inverse_mass,
254 v.value.angular() + b_tensor * r_b.cross(&impulse),
255 ),
256 });
257
258 a_set.add_velocity(a_velocity_new);
259 b_set.add_velocity(b_velocity_new);
260
261 (a_set, b_set)
262}
263
264fn positional_correction<S, B, P, R>(
287 contact: &Contact<P>,
288 a_position: &B,
289 b_position: &B,
290 a_inverse_mass: S,
291 b_inverse_mass: S,
292) -> (Option<B>, Option<B>)
293where
294 S: BaseFloat,
295 P: EuclideanSpace<Scalar = S>,
296 R: Rotation<P>,
297 P::Diff: Debug + Zero + Clone + InnerSpace,
298 B: Pose<P, R>,
299{
300 let total_inverse_mass = a_inverse_mass + b_inverse_mass;
301 let k_slop: S = NumCast::from(POSITIONAL_CORRECTION_K_SLOP).unwrap();
302 let percent: S = NumCast::from(POSITIONAL_CORRECTION_PERCENT).unwrap();
303 let correction_penetration_depth = contact.penetration_depth - k_slop;
304 let correction_magnitude =
305 correction_penetration_depth.max(S::zero()) / total_inverse_mass * percent;
306 let correction = contact.normal * correction_magnitude;
307 let a_position_new = new_pose(a_position, correction * -a_inverse_mass);
308 let b_position_new = new_pose(b_position, correction * b_inverse_mass);
309 (Some(a_position_new), Some(b_position_new))
310}
311
312fn new_pose<B, P, R>(next_frame: &B, correction: P::Diff) -> B
313where
314 P: EuclideanSpace,
315 P::Scalar: BaseFloat,
316 R: Rotation<P>,
317 P::Diff: Clone,
318 B: Pose<P, R>,
319{
320 B::new(next_frame.position() + correction, next_frame.rotation())
321}
322
323#[cfg(test)]
324mod tests {
325 use cgmath::{Basis2, One, Point2, Vector2};
326 use collision::{CollisionStrategy, Contact};
327
328 use super::*;
329 use collide::ContactEvent;
330 use BodyPose;
331
332 #[test]
333 fn test_resolve_2d_f32() {
334 let mass = Mass::<f32, f32>::new_with_inertia(0.5, 0.);
335 let material = Material::default();
336 let left_velocity = NextFrame {
337 value: Velocity::new(Vector2::<f32>::new(1., 0.), 0.),
338 };
339 let left_pose = BodyPose::new(Point2::origin(), Basis2::one());
340 let right_velocity = NextFrame {
341 value: Velocity::new(Vector2::new(-2., 0.), 0.),
342 };
343 let right_pose = BodyPose::new(Point2::new(1., 0.), Basis2::one());
344 let contact = ContactEvent::new(
345 (1, 2),
346 Contact::new_impl(CollisionStrategy::FullResolution, Vector2::new(1., 0.), 0.5),
347 );
348 let set = resolve_contact(
349 &contact.contact,
350 &ResolveData::new(Some(&left_velocity), &left_pose, &mass, &material),
351 &ResolveData::new(Some(&right_velocity), &right_pose, &mass, &material),
352 );
353 assert_eq!(
354 (
355 SingleChangeSet::new(
356 Some(BodyPose::new(
357 Point2::new(-0.04900000075250864, 0.),
358 Basis2::one()
359 )),
360 Some(NextFrame {
361 value: Velocity::new(Vector2::new(-2., 0.), 0.),
362 }),
363 ),
364 SingleChangeSet::new(
365 Some(BodyPose::new(
366 Point2::new(1.0490000007525087, 0.),
367 Basis2::one()
368 )),
369 Some(NextFrame {
370 value: Velocity::new(Vector2::new(1., 0.), 0.),
371 }),
372 )
373 ),
374 set
375 );
376 }
377
378 #[test]
379 fn test_resolve_2d_f64() {
380 let mass = Mass::<f64, f64>::new_with_inertia(0.5, 0.);
381 let material = Material::default();
382 let left_velocity = NextFrame {
383 value: Velocity::new(Vector2::<f64>::new(1., 0.), 0.),
384 };
385 let left_pose = BodyPose::new(Point2::origin(), Basis2::one());
386 let right_velocity = NextFrame {
387 value: Velocity::new(Vector2::new(-2., 0.), 0.),
388 };
389 let right_pose = BodyPose::new(Point2::new(1., 0.), Basis2::one());
390 let contact = ContactEvent::new(
391 (1, 2),
392 Contact::new_impl(CollisionStrategy::FullResolution, Vector2::new(1., 0.), 0.5),
393 );
394 let set = resolve_contact(
395 &contact.contact,
396 &ResolveData::new(Some(&left_velocity), &left_pose, &mass, &material),
397 &ResolveData::new(Some(&right_velocity), &right_pose, &mass, &material),
398 );
399 assert_eq!(
400 (
401 SingleChangeSet::new(
402 Some(BodyPose::new(
403 Point2::new(-0.04900000075250864, 0.),
404 Basis2::one()
405 )),
406 Some(NextFrame {
407 value: Velocity::new(Vector2::new(-2., 0.), 0.),
408 }),
409 ),
410 SingleChangeSet::new(
411 Some(BodyPose::new(
412 Point2::new(1.0490000007525087, 0.),
413 Basis2::one()
414 )),
415 Some(NextFrame {
416 value: Velocity::new(Vector2::new(1., 0.), 0.),
417 }),
418 )
419 ),
420 set
421 );
422 }
423}