symtropy_physics/
constraint.rs1use crate::body::{BodyHandle, RigidBody};
7
8pub trait Constraint<const D: usize>: Send + Sync {
10 fn bodies(&self) -> (BodyHandle, BodyHandle);
12
13 fn solve(&self, body_a: &mut RigidBody<D>, body_b: &mut RigidBody<D>, dt: f64);
16
17 fn solve_velocity(&self, _body_a: &mut RigidBody<D>, _body_b: &mut RigidBody<D>, _dt: f64) {}
21}
22
23pub struct DistanceConstraint<const D: usize> {
25 pub body_a: BodyHandle,
26 pub body_b: BodyHandle,
27 pub rest_length: f64,
28 pub stiffness: f64,
29}
30
31impl<const D: usize> Constraint<D> for DistanceConstraint<D> {
32 fn bodies(&self) -> (BodyHandle, BodyHandle) {
33 (self.body_a, self.body_b)
34 }
35
36 fn solve(&self, body_a: &mut RigidBody<D>, body_b: &mut RigidBody<D>, _dt: f64) {
37 let delta = body_b.transform.translation.0 - body_a.transform.translation.0;
38 let dist = delta.norm();
39 if dist < 1e-15 {
40 return;
41 }
42
43 let error = dist - self.rest_length;
44 let correction = delta / dist * error * self.stiffness;
45
46 let total_inv_mass = body_a.inv_mass + body_b.inv_mass;
47 if total_inv_mass < 1e-15 {
48 return;
49 }
50
51 let ratio_a = body_a.inv_mass / total_inv_mass;
52 let ratio_b = body_b.inv_mass / total_inv_mass;
53
54 if body_a.is_dynamic() {
55 body_a.transform.translation.0 += correction * ratio_a;
56 }
57 if body_b.is_dynamic() {
58 body_b.transform.translation.0 -= correction * ratio_b;
59 }
60 }
61
62 fn solve_velocity(&self, body_a: &mut RigidBody<D>, body_b: &mut RigidBody<D>, _dt: f64) {
63 let delta = body_b.transform.translation.0 - body_a.transform.translation.0;
64 let dist = delta.norm();
65 if dist < 1e-15 { return; }
66 let normal = delta / dist;
67
68 let rel_vel = body_b.linear_velocity - body_a.linear_velocity;
69 let vel_along = rel_vel.dot(&normal);
70
71 let total_inv = body_a.inv_mass + body_b.inv_mass;
72 if total_inv < 1e-15 { return; }
73 let impulse = -vel_along / total_inv * self.stiffness;
74
75 let impulse_vec = normal * impulse;
76 if body_a.is_dynamic() {
77 body_a.linear_velocity -= impulse_vec * body_a.inv_mass;
78 }
79 if body_b.is_dynamic() {
80 body_b.linear_velocity += impulse_vec * body_b.inv_mass;
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use symtropy_math::Point;
89
90 #[test]
91 fn distance_constraint_corrects() {
92 let mut a = RigidBody::<3>::dynamic_sphere(BodyHandle(0), Point::new([0.0, 0.0, 0.0]), 1.0, 1.0);
93 let mut b = RigidBody::<3>::dynamic_sphere(BodyHandle(1), Point::new([5.0, 0.0, 0.0]), 1.0, 1.0);
94
95 let constraint = DistanceConstraint {
96 body_a: BodyHandle(0),
97 body_b: BodyHandle(1),
98 rest_length: 3.0,
99 stiffness: 1.0,
100 };
101
102 for _ in 0..10 {
104 constraint.solve(&mut a, &mut b, 0.01);
105 }
106
107 let dist = a.transform.translation.distance(&b.transform.translation);
108 assert!((dist - 3.0).abs() < 0.1, "dist = {dist}, expected ~3.0");
109 }
110}