1use glam::{Vec2, Vec3, Mat2};
22use std::collections::HashMap;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct BodyHandle(pub u32);
29
30pub const WORLD: BodyHandle = BodyHandle(u32::MAX);
32
33#[derive(Debug, Clone)]
37pub struct BodyState {
38 pub position: Vec2,
39 pub velocity: Vec2,
40 pub angle: f32,
41 pub angular_velocity: f32,
42 pub inv_mass: f32,
44 pub inv_inertia: f32,
46 pub impulse_accum: Vec2,
48 pub torque_accum: f32,
49}
50
51impl BodyState {
52 pub fn new(position: Vec2, mass: f32, inertia: f32) -> Self {
53 Self {
54 position,
55 velocity: Vec2::ZERO,
56 angle: 0.0,
57 angular_velocity: 0.0,
58 inv_mass: if mass > 0.0 { 1.0 / mass } else { 0.0 },
59 inv_inertia: if inertia > 0.0 { 1.0 / inertia } else { 0.0 },
60 impulse_accum: Vec2::ZERO,
61 torque_accum: 0.0,
62 }
63 }
64
65 pub fn static_body(position: Vec2) -> Self {
66 Self::new(position, 0.0, 0.0)
67 }
68
69 pub fn apply_impulse(&mut self, impulse: Vec2, contact_arm: Vec2) {
70 self.velocity += impulse * self.inv_mass;
71 self.angular_velocity += cross2(contact_arm, impulse) * self.inv_inertia;
72 }
73
74 pub fn apply_angular_impulse(&mut self, ang_impulse: f32) {
75 self.angular_velocity += ang_impulse * self.inv_inertia;
76 }
77
78 pub fn is_static(&self) -> bool { self.inv_mass < 1e-12 }
79
80 pub fn kinetic_energy(&self) -> f32 {
81 let m = if self.inv_mass > 1e-12 { 1.0 / self.inv_mass } else { 0.0 };
82 let i = if self.inv_inertia > 1e-12 { 1.0 / self.inv_inertia } else { 0.0 };
83 0.5 * m * self.velocity.length_squared() + 0.5 * i * self.angular_velocity * self.angular_velocity
84 }
85}
86
87#[inline]
91pub fn cross2(a: Vec2, b: Vec2) -> f32 { a.x * b.y - a.y * b.x }
92
93#[inline]
95pub fn perp(v: Vec2) -> Vec2 { Vec2::new(-v.y, v.x) }
96
97#[inline]
99pub fn rotate2(v: Vec2, theta: f32) -> Vec2 {
100 let (s, c) = theta.sin_cos();
101 Vec2::new(c * v.x - s * v.y, s * v.x + c * v.y)
102}
103
104#[inline]
106fn wrap_angle(a: f32) -> f32 {
107 use std::f32::consts::PI;
108 let mut r = a % (2.0 * PI);
109 if r > PI { r -= 2.0 * PI; }
110 if r < -PI { r += 2.0 * PI; }
111 r
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
117pub struct ConstraintId(pub u32);
118
119pub trait Constraint: std::fmt::Debug {
123 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32;
125 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32;
127 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32);
129 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32;
131 fn get_compliance(&self) -> f32 { 0.0 }
133 fn bias(&self, bodies: &HashMap<BodyHandle, BodyState>, dt: f32) -> f32 {
135 let beta = 0.1;
136 -beta / dt * self.compute_c(bodies)
137 }
138 fn prepare(&mut self, _bodies: &HashMap<BodyHandle, BodyState>, _dt: f32) {}
140 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
142 let em = self.effective_mass(bodies);
143 if em < 1e-10 { return; }
144 let cdot = self.compute_cdot(bodies);
145 let bias = self.bias(bodies, dt);
146 let delta = -(cdot + bias) * em;
147 let (lo, hi) = self.impulse_bounds();
148 let prev = self.accumulated_impulse();
149 let new_acc = (prev + delta).clamp(lo, hi);
150 let actual = new_acc - prev;
151 self.add_accumulated(actual);
152 if actual.abs() > 1e-14 {
153 self.apply_impulse(bodies, actual);
154 }
155 }
156 fn solve_position(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
158 let compliance = self.get_compliance();
159 let em = self.effective_mass(bodies);
160 let c = self.compute_c(bodies);
161 let denom = em + compliance / (dt * dt);
162 if denom < 1e-10 { return; }
163 let lambda = -c / denom;
164 self.apply_impulse(bodies, lambda);
165 }
166 fn accumulated_impulse(&self) -> f32;
168 fn reset_accumulated(&mut self);
169 fn add_accumulated(&mut self, delta: f32);
170 fn impulse_bounds(&self) -> (f32, f32) { (f32::NEG_INFINITY, f32::INFINITY) }
172 fn body_handles(&self) -> Vec<BodyHandle> { Vec::new() }
174}
175
176#[derive(Debug, Clone)]
182pub struct ContactConstraint {
183 pub body_a: BodyHandle,
184 pub body_b: BodyHandle,
185 pub contact_point: Vec2,
187 pub normal: Vec2,
189 pub penetration: f32,
191 pub restitution: f32,
193 pub friction: f32,
195 pub baumgarte: f32,
197 pub slop: f32,
199 pub cached_normal: f32,
201 pub cached_tangent: f32,
203 eff_mass_n: f32,
205 eff_mass_t: f32,
207 bias: f32,
209 ra: Vec2,
211 rb: Vec2,
213}
214
215impl ContactConstraint {
216 pub fn new(
217 body_a: BodyHandle, body_b: BodyHandle,
218 contact_point: Vec2, normal: Vec2, penetration: f32,
219 restitution: f32, friction: f32,
220 ) -> Self {
221 Self {
222 body_a, body_b, contact_point, normal, penetration,
223 restitution, friction,
224 baumgarte: 0.2, slop: 0.005,
225 cached_normal: 0.0, cached_tangent: 0.0,
226 eff_mass_n: 0.0, eff_mass_t: 0.0,
227 bias: 0.0, ra: Vec2::ZERO, rb: Vec2::ZERO,
228 }
229 }
230
231 fn tangent(&self) -> Vec2 {
232 Vec2::new(-self.normal.y, self.normal.x)
233 }
234
235 fn compute_effective_mass_along(
236 &self, dir: Vec2, bodies: &HashMap<BodyHandle, BodyState>,
237 ) -> f32 {
238 let ba = bodies.get(&self.body_a);
239 let bb = bodies.get(&self.body_b);
240 let im_a = ba.map(|b| b.inv_mass).unwrap_or(0.0);
241 let im_b = bb.map(|b| b.inv_mass).unwrap_or(0.0);
242 let ii_a = ba.map(|b| b.inv_inertia).unwrap_or(0.0);
243 let ii_b = bb.map(|b| b.inv_inertia).unwrap_or(0.0);
244 let rda = cross2(self.ra, dir);
245 let rdb = cross2(self.rb, dir);
246 let d = im_a + im_b + ii_a * rda * rda + ii_b * rdb * rdb;
247 if d < 1e-10 { 0.0 } else { 1.0 / d }
248 }
249
250 fn relative_velocity_at_contact(&self, bodies: &HashMap<BodyHandle, BodyState>) -> Vec2 {
251 let va = bodies.get(&self.body_a)
252 .map(|b| b.velocity + perp(self.ra) * b.angular_velocity)
253 .unwrap_or(Vec2::ZERO);
254 let vb = bodies.get(&self.body_b)
255 .map(|b| b.velocity + perp(self.rb) * b.angular_velocity)
256 .unwrap_or(Vec2::ZERO);
257 vb - va
258 }
259
260 fn apply_normal_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
262 let impulse = self.normal * lambda;
263 if let Some(b) = bodies.get_mut(&self.body_a) {
264 b.apply_impulse(-impulse, self.ra);
265 }
266 if let Some(b) = bodies.get_mut(&self.body_b) {
267 b.apply_impulse(impulse, self.rb);
268 }
269 }
270
271 fn apply_tangent_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
273 let tangent = self.tangent();
274 let impulse = tangent * lambda;
275 if let Some(b) = bodies.get_mut(&self.body_a) {
276 b.apply_impulse(-impulse, self.ra);
277 }
278 if let Some(b) = bodies.get_mut(&self.body_b) {
279 b.apply_impulse(impulse, self.rb);
280 }
281 }
282
283 pub fn solve_contact(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
285 let rel_vel = self.relative_velocity_at_contact(bodies);
287 let vn = rel_vel.dot(self.normal);
288 let lambda_n = -(vn + self.bias) * self.eff_mass_n;
289 let prev_n = self.cached_normal;
290 let new_n = (prev_n + lambda_n).max(0.0);
291 let actual_n = new_n - prev_n;
292 self.cached_normal = new_n;
293 self.apply_normal_impulse(bodies, actual_n);
294
295 let rel_vel2 = self.relative_velocity_at_contact(bodies);
297 let tangent = self.tangent();
298 let vt = rel_vel2.dot(tangent);
299 let lambda_t = -vt * self.eff_mass_t;
300 let max_friction = self.friction * self.cached_normal;
301 let prev_t = self.cached_tangent;
302 let new_t = (prev_t + lambda_t).clamp(-max_friction, max_friction);
303 let actual_t = new_t - prev_t;
304 self.cached_tangent = new_t;
305 self.apply_tangent_impulse(bodies, actual_t);
306 }
307}
308
309impl Constraint for ContactConstraint {
310 fn prepare(&mut self, bodies: &HashMap<BodyHandle, BodyState>, dt: f32) {
311 let ba = bodies.get(&self.body_a);
312 let bb = bodies.get(&self.body_b);
313 self.ra = ba.map(|b| self.contact_point - b.position).unwrap_or(Vec2::ZERO);
314 self.rb = bb.map(|b| self.contact_point - b.position).unwrap_or(Vec2::ZERO);
315
316 self.eff_mass_n = self.compute_effective_mass_along(self.normal, bodies);
317 self.eff_mass_t = self.compute_effective_mass_along(self.tangent(), bodies);
318
319 let rel_vel = self.relative_velocity_at_contact(bodies);
321 let vn = rel_vel.dot(self.normal);
322 let restitution_bias = if vn < -1.0 { -self.restitution * vn } else { 0.0 };
323
324 let baumgarte_bias = self.baumgarte / dt * (self.penetration - self.slop).max(0.0);
326
327 self.bias = restitution_bias + baumgarte_bias;
328 }
330
331 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
332 self.relative_velocity_at_contact(bodies).dot(self.normal)
333 }
334
335 fn compute_c(&self, _bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
336 -self.penetration
337 }
338
339 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
340 self.compute_effective_mass_along(self.normal, bodies)
341 }
342
343 fn bias(&self, _bodies: &HashMap<BodyHandle, BodyState>, _dt: f32) -> f32 {
344 self.bias
345 }
346
347 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
348 self.apply_normal_impulse(bodies, lambda);
349 }
350
351 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
352 self.solve_contact(bodies, dt);
353 }
354
355 fn impulse_bounds(&self) -> (f32, f32) { (0.0, f32::INFINITY) }
356 fn accumulated_impulse(&self) -> f32 { self.cached_normal }
357 fn reset_accumulated(&mut self) { self.cached_normal = 0.0; self.cached_tangent = 0.0; }
358 fn add_accumulated(&mut self, d: f32) { self.cached_normal = (self.cached_normal + d).max(0.0); }
359 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
360}
361
362#[derive(Debug, Clone)]
367pub struct DistanceConstraint {
368 pub body_a: BodyHandle,
369 pub body_b: BodyHandle,
370 pub anchor_a: Vec2,
371 pub anchor_b: Vec2,
372 pub rest_length: f32,
373 pub compliance: f32,
375 accumulated: f32,
376}
377
378impl DistanceConstraint {
379 pub fn new(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2, rest_length: f32) -> Self {
380 Self { body_a, body_b, anchor_a, anchor_b, rest_length, compliance: 0.0, accumulated: 0.0 }
381 }
382
383 pub fn rigid_rod(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2, rest_length: f32) -> Self {
385 Self::new(body_a, anchor_a, body_b, anchor_b, rest_length)
386 }
387
388 pub fn soft(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2, rest_length: f32, compliance: f32) -> Self {
390 Self { body_a, body_b, anchor_a, anchor_b, rest_length, compliance, accumulated: 0.0 }
391 }
392
393 pub fn with_compliance(mut self, c: f32) -> Self { self.compliance = c; self }
394
395 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
396 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
397 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
398 (pa, pb)
399 }
400
401 fn constraint_dir(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, f32) {
402 let (pa, pb) = self.world_anchors(bodies);
403 let delta = pb - pa;
404 let len = delta.length();
405 let dir = if len > 1e-7 { delta / len } else { Vec2::X };
406 (dir, len)
407 }
408}
409
410impl Constraint for DistanceConstraint {
411 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
412 let (pa, pb) = self.world_anchors(bodies);
413 let (dir, _) = self.constraint_dir(bodies);
414 let va = bodies.get(&self.body_a).map(|b| b.velocity + perp(pa - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
415 let vb = bodies.get(&self.body_b).map(|b| b.velocity + perp(pb - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
416 (vb - va).dot(dir)
417 }
418
419 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
420 let (_, len) = self.constraint_dir(bodies);
421 len - self.rest_length
422 }
423
424 fn get_compliance(&self) -> f32 { self.compliance }
425
426 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
427 let (pa, pb) = self.world_anchors(bodies);
428 let (dir, _) = self.constraint_dir(bodies);
429 let ba = bodies.get(&self.body_a);
430 let bb = bodies.get(&self.body_b);
431 let ra = ba.map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
432 let rb = bb.map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
433 let im_a = ba.map(|b| b.inv_mass).unwrap_or(0.0);
434 let im_b = bb.map(|b| b.inv_mass).unwrap_or(0.0);
435 let ii_a = ba.map(|b| b.inv_inertia).unwrap_or(0.0);
436 let ii_b = bb.map(|b| b.inv_inertia).unwrap_or(0.0);
437 let rda = cross2(ra, dir);
438 let rdb = cross2(rb, dir);
439 let denom = im_a + im_b + ii_a * rda * rda + ii_b * rdb * rdb;
440 if denom < 1e-10 { 0.0 } else { 1.0 / denom }
441 }
442
443 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
444 let (pa, pb) = {
445 let ba = bodies.get(&self.body_a);
446 let bb = bodies.get(&self.body_b);
447 (
448 ba.map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO),
449 bb.map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO),
450 )
451 };
452 let delta = pb - pa;
453 let len = delta.length();
454 let dir = if len > 1e-7 { delta / len } else { Vec2::X };
455 let impulse = dir * lambda;
456 if let Some(b) = bodies.get_mut(&self.body_a) {
457 let r = pa - b.position;
458 b.apply_impulse(-impulse, r);
459 }
460 if let Some(b) = bodies.get_mut(&self.body_b) {
461 let r = pb - b.position;
462 b.apply_impulse(impulse, r);
463 }
464 }
465
466 fn accumulated_impulse(&self) -> f32 { self.accumulated }
467 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
468 fn add_accumulated(&mut self, d: f32) { self.accumulated += d; }
469 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
470}
471
472#[derive(Debug, Clone)]
477pub struct HingeConstraint {
478 pub body_a: BodyHandle,
479 pub body_b: BodyHandle,
480 pub anchor_a: Vec2,
481 pub anchor_b: Vec2,
482 pub limits: Option<(f32, f32)>,
484 pub motor: Option<(f32, f32)>,
486 pub compliance: f32,
488 accum_x: f32,
489 accum_y: f32,
490 accum_limit: f32,
491 accum_motor: f32,
492}
493
494impl HingeConstraint {
495 pub fn new(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2) -> Self {
496 Self {
497 body_a, body_b, anchor_a, anchor_b,
498 limits: None, motor: None, compliance: 0.0,
499 accum_x: 0.0, accum_y: 0.0, accum_limit: 0.0, accum_motor: 0.0,
500 }
501 }
502
503 pub fn with_limits(mut self, min: f32, max: f32) -> Self {
505 self.limits = Some((min, max)); self
506 }
507
508 pub fn with_motor(mut self, target_velocity: f32, max_torque: f32) -> Self {
510 self.motor = Some((target_velocity, max_torque)); self
511 }
512
513 pub fn with_compliance(mut self, c: f32) -> Self { self.compliance = c; self }
514
515 fn relative_angle(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
516 let angle_a = bodies.get(&self.body_a).map(|b| b.angle).unwrap_or(0.0);
517 let angle_b = bodies.get(&self.body_b).map(|b| b.angle).unwrap_or(0.0);
518 wrap_angle(angle_b - angle_a)
519 }
520
521 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
522 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
523 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
524 (pa, pb)
525 }
526
527 fn positional_effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
528 let ba = bodies.get(&self.body_a);
529 let bb = bodies.get(&self.body_b);
530 let im_a = ba.map(|b| b.inv_mass).unwrap_or(0.0);
531 let im_b = bb.map(|b| b.inv_mass).unwrap_or(0.0);
532 let d = im_a + im_b;
533 if d < 1e-10 { 0.0 } else { 1.0 / d }
534 }
535
536 fn solve_anchor(&self, bodies: &mut HashMap<BodyHandle, BodyState>, beta: f32, dt: f32) {
538 let (pa, pb) = self.world_anchors(bodies);
539 let delta = pb - pa;
540 if delta.length_squared() < 1e-12 { return; }
541 let len = delta.length();
542 let dir = delta / len;
543 let em = self.positional_effective_mass(bodies);
544 if em < 1e-10 { return; }
545 let lambda = -beta / dt * len * em;
546 let impulse = dir * lambda;
547 if let Some(b) = bodies.get_mut(&self.body_a) {
548 b.apply_impulse(-impulse, pa - b.position);
549 }
550 if let Some(b) = bodies.get_mut(&self.body_b) {
551 b.apply_impulse(impulse, pb - b.position);
552 }
553 }
554
555 fn solve_limit(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
557 let Some((min, max)) = self.limits else { return };
558 let rel_angle = self.relative_angle(bodies);
559 let violation = if rel_angle < min {
560 rel_angle - min
561 } else if rel_angle > max {
562 rel_angle - max
563 } else {
564 return;
565 };
566 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
567 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
568 let denom = ii_a + ii_b + self.compliance / (dt * dt);
569 if denom < 1e-10 { return; }
570 let lambda = -violation / denom;
571 let prev = self.accum_limit;
572 let new_acc = if rel_angle < min {
573 (prev + lambda).max(0.0)
574 } else {
575 (prev + lambda).min(0.0)
576 };
577 let actual = new_acc - prev;
578 self.accum_limit = new_acc;
579 if let Some(b) = bodies.get_mut(&self.body_a) {
580 b.apply_angular_impulse(-actual);
581 }
582 if let Some(b) = bodies.get_mut(&self.body_b) {
583 b.apply_angular_impulse(actual);
584 }
585 }
586
587 fn solve_motor(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>) {
589 let Some((target_vel, max_torque)) = self.motor else { return };
590 let omega_a = bodies.get(&self.body_a).map(|b| b.angular_velocity).unwrap_or(0.0);
591 let omega_b = bodies.get(&self.body_b).map(|b| b.angular_velocity).unwrap_or(0.0);
592 let rel_omega = omega_b - omega_a;
593 let error = rel_omega - target_vel;
594 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
595 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
596 let denom = ii_a + ii_b;
597 if denom < 1e-10 { return; }
598 let lambda = -error / denom;
599 let prev = self.accum_motor;
600 let new_acc = (prev + lambda).clamp(-max_torque, max_torque);
601 let actual = new_acc - prev;
602 self.accum_motor = new_acc;
603 if let Some(b) = bodies.get_mut(&self.body_a) {
604 b.apply_angular_impulse(-actual);
605 }
606 if let Some(b) = bodies.get_mut(&self.body_b) {
607 b.apply_angular_impulse(actual);
608 }
609 }
610}
611
612impl Constraint for HingeConstraint {
613 fn prepare(&mut self, _bodies: &HashMap<BodyHandle, BodyState>, _dt: f32) {
614 }
616
617 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
618 let (pa, pb) = self.world_anchors(bodies);
619 let ba = bodies.get(&self.body_a);
620 let bb = bodies.get(&self.body_b);
621 let va = ba.map(|b| b.velocity + perp(pa - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
622 let vb = bb.map(|b| b.velocity + perp(pb - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
623 (vb - va).length()
624 }
625
626 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
627 let (pa, pb) = self.world_anchors(bodies);
628 (pb - pa).length()
629 }
630
631 fn get_compliance(&self) -> f32 { self.compliance }
632
633 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
634 self.positional_effective_mass(bodies)
635 }
636
637 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
638 let (pa, pb) = self.world_anchors(bodies);
639 let delta = pb - pa;
640 let len = delta.length();
641 let dir = if len > 1e-7 { delta / len } else { Vec2::X };
642 let impulse = dir * lambda;
643 if let Some(b) = bodies.get_mut(&self.body_a) {
644 b.apply_impulse(-impulse, pa - b.position);
645 }
646 if let Some(b) = bodies.get_mut(&self.body_b) {
647 b.apply_impulse(impulse, pb - b.position);
648 }
649 }
650
651 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
652 let em = self.effective_mass(bodies);
654 if em > 1e-10 {
655 let cdot = self.compute_cdot(bodies);
656 let bias = self.bias(bodies, dt);
657 let delta = -(cdot + bias) * em;
658 let prev = self.accum_x;
659 let new_acc = prev + delta;
660 let actual = new_acc - prev;
661 self.accum_x = new_acc;
662 if actual.abs() > 1e-14 {
663 self.apply_impulse(bodies, actual);
664 }
665 }
666 self.solve_limit(bodies, dt);
668 self.solve_motor(bodies);
669 }
670
671 fn solve_position(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
672 self.solve_anchor(bodies, 0.2, dt);
673 }
674
675 fn accumulated_impulse(&self) -> f32 { self.accum_x }
676 fn reset_accumulated(&mut self) {
677 self.accum_x = 0.0; self.accum_y = 0.0;
678 self.accum_limit = 0.0; self.accum_motor = 0.0;
679 }
680 fn add_accumulated(&mut self, d: f32) { self.accum_x += d; }
681 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
682}
683
684#[derive(Debug, Clone)]
689pub struct BallSocketConstraint {
690 pub body_a: BodyHandle,
691 pub body_b: BodyHandle,
692 pub anchor_a: Vec2,
693 pub anchor_b: Vec2,
694 pub cone_limit: Option<f32>,
696 pub compliance: f32,
697 accumulated: f32,
698 accum_cone: f32,
699}
700
701impl BallSocketConstraint {
702 pub fn new(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2) -> Self {
703 Self { body_a, body_b, anchor_a, anchor_b, cone_limit: None, compliance: 0.0, accumulated: 0.0, accum_cone: 0.0 }
704 }
705
706 pub fn with_cone_limit(mut self, angle: f32) -> Self { self.cone_limit = Some(angle); self }
707 pub fn with_compliance(mut self, c: f32) -> Self { self.compliance = c; self }
708
709 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
710 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
711 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
712 (pa, pb)
713 }
714
715 fn solve_cone_limit(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>) {
716 let Some(max_angle) = self.cone_limit else { return };
717 let angle_a = bodies.get(&self.body_a).map(|b| b.angle).unwrap_or(0.0);
718 let angle_b = bodies.get(&self.body_b).map(|b| b.angle).unwrap_or(0.0);
719 let rel = wrap_angle(angle_b - angle_a);
720 if rel.abs() <= max_angle { return; }
721 let violation = if rel > max_angle { rel - max_angle } else { rel + max_angle };
722 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
723 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
724 let denom = ii_a + ii_b;
725 if denom < 1e-10 { return; }
726 let lambda = -violation / denom;
727 let prev = self.accum_cone;
728 let new_acc = if rel > max_angle { (prev + lambda).min(0.0) } else { (prev + lambda).max(0.0) };
729 let actual = new_acc - prev;
730 self.accum_cone = new_acc;
731 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_angular_impulse(-actual); }
732 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_angular_impulse(actual); }
733 }
734}
735
736impl Constraint for BallSocketConstraint {
737 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
738 let (pa, pb) = self.world_anchors(bodies);
739 let va = bodies.get(&self.body_a).map(|b| b.velocity + perp(pa - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
740 let vb = bodies.get(&self.body_b).map(|b| b.velocity + perp(pb - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
741 (vb - va).length()
742 }
743
744 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
745 let (pa, pb) = self.world_anchors(bodies);
746 (pb - pa).length()
747 }
748
749 fn get_compliance(&self) -> f32 { self.compliance }
750
751 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
752 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
753 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
754 let d = im_a + im_b;
755 if d < 1e-10 { 0.0 } else { 1.0 / d }
756 }
757
758 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
759 let (pa, pb) = self.world_anchors(bodies);
760 let delta = pb - pa;
761 let len = delta.length();
762 let dir = if len > 1e-7 { delta / len } else { Vec2::X };
763 let impulse = dir * lambda;
764 if let Some(b) = bodies.get_mut(&self.body_a) {
765 b.apply_impulse(-impulse, pa - b.position);
766 }
767 if let Some(b) = bodies.get_mut(&self.body_b) {
768 b.apply_impulse(impulse, pb - b.position);
769 }
770 }
771
772 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
773 let em = self.effective_mass(bodies);
774 if em > 1e-10 {
775 let cdot = self.compute_cdot(bodies);
776 let bias = self.bias(bodies, dt);
777 let delta = -(cdot + bias) * em;
778 self.accumulated += delta;
779 self.apply_impulse(bodies, delta);
780 }
781 self.solve_cone_limit(bodies);
782 }
783
784 fn accumulated_impulse(&self) -> f32 { self.accumulated }
785 fn reset_accumulated(&mut self) { self.accumulated = 0.0; self.accum_cone = 0.0; }
786 fn add_accumulated(&mut self, d: f32) { self.accumulated += d; }
787 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
788}
789
790#[derive(Debug, Clone)]
795pub struct SliderConstraint {
796 pub body_a: BodyHandle,
797 pub body_b: BodyHandle,
798 pub anchor_a: Vec2,
799 pub anchor_b: Vec2,
800 pub axis: Vec2,
802 pub limits: Option<(f32, f32)>,
804 pub motor: Option<(f32, f32)>,
806 pub compliance: f32,
807 accum_perp: f32, accum_limit: f32,
809 accum_motor: f32,
810}
811
812impl SliderConstraint {
813 pub fn new(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2, axis: Vec2) -> Self {
814 let axis = axis.normalize_or_zero();
815 Self {
816 body_a, body_b, anchor_a, anchor_b, axis,
817 limits: None, motor: None, compliance: 0.0,
818 accum_perp: 0.0, accum_limit: 0.0, accum_motor: 0.0,
819 }
820 }
821
822 pub fn with_limits(mut self, min: f32, max: f32) -> Self { self.limits = Some((min, max)); self }
823 pub fn with_motor(mut self, vel: f32, max_force: f32) -> Self { self.motor = Some((vel, max_force)); self }
824 pub fn with_compliance(mut self, c: f32) -> Self { self.compliance = c; self }
825
826 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
827 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
828 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
829 (pa, pb)
830 }
831
832 fn slide_offset(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
833 let (pa, pb) = self.world_anchors(bodies);
834 (pb - pa).dot(self.axis)
835 }
836
837 fn perp_offset(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
838 let (pa, pb) = self.world_anchors(bodies);
839 let perp_axis = Vec2::new(-self.axis.y, self.axis.x);
840 (pb - pa).dot(perp_axis)
841 }
842
843 fn solve_perp(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, beta: f32, dt: f32) {
844 let perp_axis = Vec2::new(-self.axis.y, self.axis.x);
845 let c = self.perp_offset(bodies);
846 if c.abs() < 1e-8 { return; }
847 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
848 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
849 let (pa, pb) = self.world_anchors(bodies);
850 let ra = bodies.get(&self.body_a).map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
851 let rb = bodies.get(&self.body_b).map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
852 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
853 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
854 let rda = cross2(ra, perp_axis);
855 let rdb = cross2(rb, perp_axis);
856 let denom = im_a + im_b + ii_a * rda * rda + ii_b * rdb * rdb;
857 if denom < 1e-10 { return; }
858 let lambda = -beta / dt * c / denom;
859 let impulse = perp_axis * lambda;
860 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, ra); }
861 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, rb); }
862 }
863
864 fn solve_translation_limit(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
865 let Some((min, max)) = self.limits else { return };
866 let offset = self.slide_offset(bodies);
867 let violation = if offset < min { offset - min } else if offset > max { offset - max } else { return };
868 let (pa, pb) = self.world_anchors(bodies);
869 let ra = bodies.get(&self.body_a).map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
870 let rb = bodies.get(&self.body_b).map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
871 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
872 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
873 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
874 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
875 let rda = cross2(ra, self.axis);
876 let rdb = cross2(rb, self.axis);
877 let denom = im_a + im_b + ii_a * rda * rda + ii_b * rdb * rdb + self.compliance / (dt * dt);
878 if denom < 1e-10 { return; }
879 let lambda = -violation / denom;
880 let prev = self.accum_limit;
881 let new_acc = if offset < min { (prev + lambda).max(0.0) } else { (prev + lambda).min(0.0) };
882 let actual = new_acc - prev;
883 self.accum_limit = new_acc;
884 let impulse = self.axis * actual;
885 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, ra); }
886 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, rb); }
887 }
888
889 fn solve_motor_constraint(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>) {
890 let Some((target_vel, max_force)) = self.motor else { return };
891 let va = bodies.get(&self.body_a).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
892 let vb = bodies.get(&self.body_b).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
893 let rel_vel = (vb - va).dot(self.axis);
894 let error = rel_vel - target_vel;
895 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
896 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
897 let denom = im_a + im_b;
898 if denom < 1e-10 { return; }
899 let lambda = -error / denom;
900 let prev = self.accum_motor;
901 let new_acc = (prev + lambda).clamp(-max_force, max_force);
902 let actual = new_acc - prev;
903 self.accum_motor = new_acc;
904 let impulse = self.axis * actual;
905 let (pa, pb) = self.world_anchors(bodies);
906 let ra = bodies.get(&self.body_a).map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
907 let rb = bodies.get(&self.body_b).map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
908 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, ra); }
909 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, rb); }
910 }
911}
912
913impl Constraint for SliderConstraint {
914 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
915 let va = bodies.get(&self.body_a).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
916 let vb = bodies.get(&self.body_b).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
917 (vb - va).dot(Vec2::new(-self.axis.y, self.axis.x))
918 }
919
920 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
921 self.perp_offset(bodies)
922 }
923
924 fn get_compliance(&self) -> f32 { self.compliance }
925
926 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
927 let perp_axis = Vec2::new(-self.axis.y, self.axis.x);
928 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
929 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
930 let d = im_a + im_b;
931 if d < 1e-10 { 0.0 } else { 1.0 / d }
932 }
933
934 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
935 let perp_axis = Vec2::new(-self.axis.y, self.axis.x);
936 let impulse = perp_axis * lambda;
937 let (pa, pb) = self.world_anchors(bodies);
938 let ra = bodies.get(&self.body_a).map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
939 let rb = bodies.get(&self.body_b).map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
940 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, ra); }
941 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, rb); }
942 }
943
944 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
945 self.solve_perp(bodies, 0.1, dt);
946 self.solve_translation_limit(bodies, dt);
947 self.solve_motor_constraint(bodies);
948 }
949
950 fn solve_position(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
951 self.solve_perp(bodies, 0.2, dt);
952 }
953
954 fn accumulated_impulse(&self) -> f32 { self.accum_perp }
955 fn reset_accumulated(&mut self) { self.accum_perp = 0.0; self.accum_limit = 0.0; self.accum_motor = 0.0; }
956 fn add_accumulated(&mut self, d: f32) { self.accum_perp += d; }
957 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
958}
959
960#[derive(Debug, Clone)]
964pub struct WeldConstraint {
965 pub body_a: BodyHandle,
966 pub body_b: BodyHandle,
967 pub anchor_a: Vec2,
968 pub anchor_b: Vec2,
969 pub ref_angle: f32,
971 pub angular_compliance: f32,
973 pub linear_compliance: f32,
975 accum_lin: f32,
976 accum_ang: f32,
977}
978
979impl WeldConstraint {
980 pub fn new(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2, ref_angle: f32) -> Self {
981 Self {
982 body_a, body_b, anchor_a, anchor_b, ref_angle,
983 angular_compliance: 0.0, linear_compliance: 0.0,
984 accum_lin: 0.0, accum_ang: 0.0,
985 }
986 }
987
988 pub fn with_angular_compliance(mut self, c: f32) -> Self { self.angular_compliance = c; self }
989 pub fn with_linear_compliance(mut self, c: f32) -> Self { self.linear_compliance = c; self }
990
991 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
992 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
993 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
994 (pa, pb)
995 }
996
997 fn angle_error(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
998 let angle_a = bodies.get(&self.body_a).map(|b| b.angle).unwrap_or(0.0);
999 let angle_b = bodies.get(&self.body_b).map(|b| b.angle).unwrap_or(0.0);
1000 wrap_angle(angle_b - angle_a - self.ref_angle)
1001 }
1002}
1003
1004impl Constraint for WeldConstraint {
1005 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1006 let (pa, pb) = self.world_anchors(bodies);
1007 let va = bodies.get(&self.body_a).map(|b| b.velocity + perp(pa - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
1008 let vb = bodies.get(&self.body_b).map(|b| b.velocity + perp(pb - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
1009 (vb - va).length()
1010 }
1011
1012 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1013 let (pa, pb) = self.world_anchors(bodies);
1014 (pb - pa).length()
1015 }
1016
1017 fn get_compliance(&self) -> f32 { self.linear_compliance }
1018
1019 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1020 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
1021 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
1022 let d = im_a + im_b;
1023 if d < 1e-10 { 0.0 } else { 1.0 / d }
1024 }
1025
1026 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1027 let (pa, pb) = self.world_anchors(bodies);
1028 let delta = pb - pa;
1029 let len = delta.length();
1030 let dir = if len > 1e-7 { delta / len } else { Vec2::X };
1031 let impulse = dir * lambda;
1032 if let Some(b) = bodies.get_mut(&self.body_a) {
1033 b.apply_impulse(-impulse, pa - b.position);
1034 }
1035 if let Some(b) = bodies.get_mut(&self.body_b) {
1036 b.apply_impulse(impulse, pb - b.position);
1037 }
1038 }
1039
1040 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
1041 let em_lin = self.effective_mass(bodies);
1043 if em_lin > 1e-10 {
1044 let cdot = self.compute_cdot(bodies);
1045 let bias = self.bias(bodies, dt);
1046 let delta = -(cdot + bias) * em_lin;
1047 self.accum_lin += delta;
1048 self.apply_impulse(bodies, delta);
1049 }
1050 let err = self.angle_error(bodies);
1052 let omega_a = bodies.get(&self.body_a).map(|b| b.angular_velocity).unwrap_or(0.0);
1053 let omega_b = bodies.get(&self.body_b).map(|b| b.angular_velocity).unwrap_or(0.0);
1054 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
1055 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
1056 let denom = ii_a + ii_b + self.angular_compliance / (dt * dt);
1057 if denom > 1e-10 {
1058 let ang_bias = -0.1 / dt * err;
1059 let rel_omega = omega_b - omega_a;
1060 let lambda = -(rel_omega + ang_bias) / denom;
1061 self.accum_ang += lambda;
1062 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_angular_impulse(-lambda); }
1063 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_angular_impulse(lambda); }
1064 }
1065 }
1066
1067 fn accumulated_impulse(&self) -> f32 { self.accum_lin }
1068 fn reset_accumulated(&mut self) { self.accum_lin = 0.0; self.accum_ang = 0.0; }
1069 fn add_accumulated(&mut self, d: f32) { self.accum_lin += d; }
1070 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1071}
1072
1073#[derive(Debug, Clone)]
1078pub struct PulleyConstraint {
1079 pub body_a: BodyHandle,
1080 pub body_b: BodyHandle,
1081 pub anchor_a: Vec2, pub anchor_b: Vec2, pub pulley_a: Vec2, pub pulley_b: Vec2, pub ratio: f32, pub total_length: f32, accumulated: f32,
1088}
1089
1090impl PulleyConstraint {
1091 pub fn new(
1092 body_a: BodyHandle, anchor_a: Vec2, pulley_a: Vec2,
1093 body_b: BodyHandle, anchor_b: Vec2, pulley_b: Vec2,
1094 ratio: f32, total_length: f32,
1095 ) -> Self {
1096 Self { body_a, body_b, anchor_a, anchor_b, pulley_a, pulley_b, ratio, total_length, accumulated: 0.0 }
1097 }
1098
1099 fn world_anchor_a(&self, bodies: &HashMap<BodyHandle, BodyState>) -> Vec2 {
1100 bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO)
1101 }
1102 fn world_anchor_b(&self, bodies: &HashMap<BodyHandle, BodyState>) -> Vec2 {
1103 bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO)
1104 }
1105
1106 fn lengths(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (f32, f32) {
1107 let wa = self.world_anchor_a(bodies);
1108 let wb = self.world_anchor_b(bodies);
1109 let la = (wa - self.pulley_a).length();
1110 let lb = (wb - self.pulley_b).length();
1111 (la, lb)
1112 }
1113
1114 fn dirs(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
1115 let wa = self.world_anchor_a(bodies);
1116 let wb = self.world_anchor_b(bodies);
1117 let da = (wa - self.pulley_a);
1118 let db = (wb - self.pulley_b);
1119 let la = da.length();
1120 let lb = db.length();
1121 (
1122 if la > 1e-7 { da / la } else { Vec2::X },
1123 if lb > 1e-7 { db / lb } else { Vec2::X },
1124 )
1125 }
1126}
1127
1128impl Constraint for PulleyConstraint {
1129 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1130 let (la, lb) = self.lengths(bodies);
1131 la + self.ratio * lb - self.total_length
1132 }
1133
1134 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1135 let wa = self.world_anchor_a(bodies);
1136 let wb = self.world_anchor_b(bodies);
1137 let (da, db) = self.dirs(bodies);
1138 let va = bodies.get(&self.body_a).map(|b| b.velocity + perp(wa - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
1139 let vb = bodies.get(&self.body_b).map(|b| b.velocity + perp(wb - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
1140 va.dot(da) + self.ratio * vb.dot(db)
1141 }
1142
1143 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1144 let wa = self.world_anchor_a(bodies);
1145 let wb = self.world_anchor_b(bodies);
1146 let (da, db) = self.dirs(bodies);
1147 let ra = bodies.get(&self.body_a).map(|b| wa - b.position).unwrap_or(Vec2::ZERO);
1148 let rb = bodies.get(&self.body_b).map(|b| wb - b.position).unwrap_or(Vec2::ZERO);
1149 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
1150 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
1151 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
1152 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
1153 let rda = cross2(ra, da);
1154 let rdb = cross2(rb, db);
1155 let denom = im_a + ii_a * rda * rda + self.ratio * self.ratio * (im_b + ii_b * rdb * rdb);
1156 if denom < 1e-10 { 0.0 } else { 1.0 / denom }
1157 }
1158
1159 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1160 let wa = self.world_anchor_a(bodies);
1161 let wb = self.world_anchor_b(bodies);
1162 let (da, db) = self.dirs(bodies);
1163 let ra = bodies.get(&self.body_a).map(|b| wa - b.position).unwrap_or(Vec2::ZERO);
1164 let rb = bodies.get(&self.body_b).map(|b| wb - b.position).unwrap_or(Vec2::ZERO);
1165 if let Some(b) = bodies.get_mut(&self.body_a) {
1166 b.apply_impulse(-da * lambda, ra);
1167 }
1168 if let Some(b) = bodies.get_mut(&self.body_b) {
1169 b.apply_impulse(-db * (lambda * self.ratio), rb);
1170 }
1171 }
1172
1173 fn impulse_bounds(&self) -> (f32, f32) { (0.0, f32::INFINITY) }
1174 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1175 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1176 fn add_accumulated(&mut self, d: f32) { self.accumulated = (self.accumulated + d).max(0.0); }
1177 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1178}
1179
1180#[derive(Debug, Clone)]
1184pub struct SpringConstraint {
1185 pub body_a: BodyHandle,
1186 pub body_b: BodyHandle,
1187 pub anchor_a: Vec2,
1188 pub anchor_b: Vec2,
1189 pub rest_length: f32,
1190 pub stiffness: f32,
1191 pub damping: f32,
1192 accumulated: f32,
1193}
1194
1195impl SpringConstraint {
1196 pub fn new(a: BodyHandle, aa: Vec2, b: BodyHandle, ab: Vec2, rest: f32, k: f32, d: f32) -> Self {
1197 Self { body_a: a, body_b: b, anchor_a: aa, anchor_b: ab, rest_length: rest, stiffness: k, damping: d, accumulated: 0.0 }
1198 }
1199
1200 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
1201 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
1202 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
1203 (pa, pb)
1204 }
1205}
1206
1207impl Constraint for SpringConstraint {
1208 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1209 let (pa, pb) = self.world_anchors(bodies);
1210 (pb - pa).length() - self.rest_length
1211 }
1212
1213 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1214 let (pa, pb) = self.world_anchors(bodies);
1215 let delta = pb - pa;
1216 let len = delta.length().max(1e-7);
1217 let dir = delta / len;
1218 let va = bodies.get(&self.body_a).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
1219 let vb = bodies.get(&self.body_b).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
1220 (vb - va).dot(dir)
1221 }
1222
1223 fn get_compliance(&self) -> f32 { if self.stiffness > 1e-6 { 1.0 / self.stiffness } else { 0.0 } }
1224
1225 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1226 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
1227 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
1228 let denom = im_a + im_b + self.get_compliance();
1229 if denom < 1e-10 { 0.0 } else { 1.0 / denom }
1230 }
1231
1232 fn bias(&self, bodies: &HashMap<BodyHandle, BodyState>, dt: f32) -> f32 {
1233 -self.stiffness * self.compute_c(bodies) / dt
1234 }
1235
1236 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1237 let (pa, pb) = self.world_anchors(bodies);
1238 let delta = pb - pa;
1239 let len = delta.length().max(1e-7);
1240 let dir = delta / len;
1241 let impulse = dir * lambda;
1242 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, Vec2::ZERO); }
1243 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, Vec2::ZERO); }
1244 }
1245
1246 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1247 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1248 fn add_accumulated(&mut self, d: f32) { self.accumulated += d; }
1249 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1250}
1251
1252#[derive(Debug, Clone)]
1256pub struct PinConstraint {
1257 pub body: BodyHandle,
1258 pub local_anchor: Vec2,
1259 pub world_target: Vec2,
1260 pub compliance: f32,
1261 accumulated: f32,
1262}
1263
1264impl PinConstraint {
1265 pub fn new(body: BodyHandle, local_anchor: Vec2, world_target: Vec2) -> Self {
1266 Self { body, local_anchor, world_target, compliance: 0.0, accumulated: 0.0 }
1267 }
1268 pub fn with_compliance(mut self, c: f32) -> Self { self.compliance = c; self }
1269}
1270
1271impl Constraint for PinConstraint {
1272 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1273 let pa = bodies.get(&self.body)
1274 .map(|b| b.position + rotate2(self.local_anchor, b.angle))
1275 .unwrap_or(Vec2::ZERO);
1276 (pa - self.world_target).length()
1277 }
1278
1279 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1280 let b = match bodies.get(&self.body) { Some(b) => b, None => return 0.0 };
1281 let pa = b.position + rotate2(self.local_anchor, b.angle);
1282 let dir = (pa - self.world_target).normalize_or_zero();
1283 (b.velocity + perp(pa - b.position) * b.angular_velocity).dot(dir)
1284 }
1285
1286 fn get_compliance(&self) -> f32 { self.compliance }
1287
1288 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1289 let b = match bodies.get(&self.body) { Some(b) => b, None => return 0.0 };
1290 let pa = b.position + rotate2(self.local_anchor, b.angle);
1291 let r = pa - b.position;
1292 let dir = (pa - self.world_target).normalize_or_zero();
1293 let rd = cross2(r, dir);
1294 let denom = b.inv_mass + b.inv_inertia * rd * rd;
1295 if denom < 1e-10 { 0.0 } else { 1.0 / denom }
1296 }
1297
1298 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1299 let b = match bodies.get_mut(&self.body) { Some(b) => b, None => return };
1300 let pa = b.position + rotate2(self.local_anchor, b.angle);
1301 let dir = (pa - self.world_target).normalize_or_zero();
1302 b.apply_impulse(-dir * lambda, pa - b.position);
1303 }
1304
1305 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1306 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1307 fn add_accumulated(&mut self, d: f32) { self.accumulated += d; }
1308 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body] }
1309}
1310
1311#[derive(Debug, Clone)]
1315pub struct MotorConstraint {
1316 pub body: BodyHandle,
1317 pub target_velocity: f32,
1318 pub max_torque: f32,
1319 accumulated: f32,
1320}
1321
1322impl MotorConstraint {
1323 pub fn new(body: BodyHandle, target_velocity: f32, max_torque: f32) -> Self {
1324 Self { body, target_velocity, max_torque, accumulated: 0.0 }
1325 }
1326}
1327
1328impl Constraint for MotorConstraint {
1329 fn compute_c(&self, _: &HashMap<BodyHandle, BodyState>) -> f32 { 0.0 }
1330
1331 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1332 bodies.get(&self.body).map(|b| b.angular_velocity).unwrap_or(0.0) - self.target_velocity
1333 }
1334
1335 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1336 let ii = bodies.get(&self.body).map(|b| b.inv_inertia).unwrap_or(0.0);
1337 if ii < 1e-10 { 0.0 } else { 1.0 / ii }
1338 }
1339
1340 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1341 if let Some(b) = bodies.get_mut(&self.body) {
1342 b.angular_velocity -= lambda * b.inv_inertia;
1343 }
1344 }
1345
1346 fn impulse_bounds(&self) -> (f32, f32) { (-self.max_torque, self.max_torque) }
1347 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1348 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1349 fn add_accumulated(&mut self, d: f32) {
1350 self.accumulated = (self.accumulated + d).clamp(-self.max_torque, self.max_torque);
1351 }
1352 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body] }
1353}
1354
1355#[derive(Debug, Clone)]
1359pub struct GearConstraint {
1360 pub body_a: BodyHandle,
1361 pub body_b: BodyHandle,
1362 pub ratio: f32,
1363 accumulated: f32,
1364}
1365
1366impl GearConstraint {
1367 pub fn new(body_a: BodyHandle, body_b: BodyHandle, ratio: f32) -> Self {
1368 Self { body_a, body_b, ratio, accumulated: 0.0 }
1369 }
1370}
1371
1372impl Constraint for GearConstraint {
1373 fn compute_c(&self, _: &HashMap<BodyHandle, BodyState>) -> f32 { 0.0 }
1374
1375 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1376 let oa = bodies.get(&self.body_a).map(|b| b.angular_velocity).unwrap_or(0.0);
1377 let ob = bodies.get(&self.body_b).map(|b| b.angular_velocity).unwrap_or(0.0);
1378 oa + self.ratio * ob
1379 }
1380
1381 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1382 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
1383 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
1384 let d = ii_a + self.ratio * self.ratio * ii_b;
1385 if d < 1e-10 { 0.0 } else { 1.0 / d }
1386 }
1387
1388 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1389 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_angular_impulse(-lambda); }
1390 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_angular_impulse(-lambda * self.ratio); }
1391 }
1392
1393 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1394 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1395 fn add_accumulated(&mut self, d: f32) { self.accumulated += d; }
1396 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1397}
1398
1399#[derive(Debug, Clone)]
1403pub struct MaxDistanceConstraint {
1404 pub body_a: BodyHandle,
1405 pub body_b: BodyHandle,
1406 pub anchor_a: Vec2,
1407 pub anchor_b: Vec2,
1408 pub max_distance: f32,
1409 pub compliance: f32,
1410 accumulated: f32,
1411}
1412
1413impl MaxDistanceConstraint {
1414 pub fn new(a: BodyHandle, aa: Vec2, b: BodyHandle, ab: Vec2, max_dist: f32) -> Self {
1415 Self { body_a: a, body_b: b, anchor_a: aa, anchor_b: ab, max_distance: max_dist, compliance: 0.0, accumulated: 0.0 }
1416 }
1417
1418 fn current_length(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1419 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
1420 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
1421 (pb - pa).length()
1422 }
1423}
1424
1425impl Constraint for MaxDistanceConstraint {
1426 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1427 (self.current_length(bodies) - self.max_distance).max(0.0)
1428 }
1429
1430 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1431 if self.compute_c(bodies) <= 0.0 { return 0.0; }
1432 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
1433 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
1434 let delta = pb - pa;
1435 let len = delta.length().max(1e-7);
1436 let dir = delta / len;
1437 let va = bodies.get(&self.body_a).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
1438 let vb = bodies.get(&self.body_b).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
1439 (vb - va).dot(dir)
1440 }
1441
1442 fn get_compliance(&self) -> f32 { self.compliance }
1443
1444 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1445 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
1446 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
1447 let d = im_a + im_b;
1448 if d < 1e-10 { 0.0 } else { 1.0 / d }
1449 }
1450
1451 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1452 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
1453 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
1454 let delta = pb - pa;
1455 let len = delta.length().max(1e-7);
1456 let dir = delta / len;
1457 let imp = dir * lambda;
1458 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-imp, Vec2::ZERO); }
1459 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(imp, Vec2::ZERO); }
1460 }
1461
1462 fn impulse_bounds(&self) -> (f32, f32) { (0.0, f32::INFINITY) }
1463 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1464 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1465 fn add_accumulated(&mut self, d: f32) { self.accumulated = (self.accumulated + d).max(0.0); }
1466 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1467}
1468
1469pub struct ConstraintSolver {
1476 pub iteration_count: u32,
1477 pub baumgarte_factor: f32,
1478 pub warm_start: bool,
1479 pub substep_count: u32,
1481}
1482
1483impl Default for ConstraintSolver {
1484 fn default() -> Self {
1485 Self { iteration_count: 10, baumgarte_factor: 0.2, warm_start: true, substep_count: 4 }
1486 }
1487}
1488
1489impl ConstraintSolver {
1490 pub fn new(iterations: u32) -> Self {
1491 Self { iteration_count: iterations, ..Default::default() }
1492 }
1493
1494 pub fn solve(
1496 &self,
1497 bodies: &mut HashMap<BodyHandle, BodyState>,
1498 constraints: &mut [Box<dyn Constraint>],
1499 dt: f32,
1500 ) {
1501 if dt < 1e-8 { return; }
1502
1503 if self.warm_start {
1505 for c in constraints.iter() {
1506 let lambda = c.accumulated_impulse();
1507 if lambda.abs() > 1e-10 {
1508 c.apply_impulse(bodies, lambda * 0.8); }
1510 }
1511 } else {
1512 for c in constraints.iter_mut() { c.reset_accumulated(); }
1513 }
1514
1515 for c in constraints.iter_mut() {
1517 c.prepare(bodies, dt);
1518 }
1519
1520 for _ in 0..self.iteration_count {
1522 for c in constraints.iter_mut() {
1523 c.solve_velocity(bodies, dt);
1524 }
1525 }
1526 }
1527
1528 pub fn solve_positions(
1530 &self,
1531 bodies: &mut HashMap<BodyHandle, BodyState>,
1532 constraints: &mut [Box<dyn Constraint>],
1533 dt: f32,
1534 ) {
1535 let alpha = self.baumgarte_factor / dt.max(1e-6);
1536 for c in constraints.iter_mut() {
1537 let em = c.effective_mass(bodies);
1538 if em < 1e-10 { continue; }
1539 let c_val = c.compute_c(bodies);
1540 if c_val.abs() < 1e-5 { continue; }
1541 let lambda = -alpha * c_val * em;
1542 c.apply_impulse(bodies, lambda);
1543 }
1544 }
1545
1546 pub fn solve_xpbd(
1548 &self,
1549 bodies: &mut HashMap<BodyHandle, BodyState>,
1550 constraints: &mut [Box<dyn Constraint>],
1551 dt: f32,
1552 ) {
1553 if dt < 1e-8 { return; }
1554 let sub_dt = dt / self.substep_count as f32;
1555
1556 for _ in 0..self.substep_count {
1557 for c in constraints.iter_mut() { c.reset_accumulated(); }
1559
1560 for body in bodies.values_mut() {
1562 if !body.is_static() {
1563 body.position += body.velocity * sub_dt;
1564 body.angle += body.angular_velocity * sub_dt;
1565 }
1566 }
1567
1568 for _ in 0..self.iteration_count {
1570 for c in constraints.iter_mut() {
1571 c.solve_position(bodies, sub_dt);
1572 }
1573 }
1574
1575 }
1578 }
1579}
1580
1581#[derive(Debug, Clone)]
1585pub struct ConstraintIsland {
1586 pub body_handles: Vec<BodyHandle>,
1587 pub constraint_indices: Vec<usize>,
1588}
1589
1590pub fn detect_islands(
1592 bodies: &HashMap<BodyHandle, BodyState>,
1593 constraints: &[Box<dyn Constraint>],
1594) -> Vec<ConstraintIsland> {
1595 let body_list: Vec<BodyHandle> = bodies.keys().copied().collect();
1596 let n = body_list.len();
1597 if n == 0 { return Vec::new(); }
1598
1599 let mut handle_to_idx: HashMap<BodyHandle, usize> = HashMap::new();
1601 for (i, h) in body_list.iter().enumerate() {
1602 handle_to_idx.insert(*h, i);
1603 }
1604
1605 let mut parent: Vec<usize> = (0..n).collect();
1607 let mut rank: Vec<usize> = vec![0; n];
1608
1609 fn find(parent: &mut Vec<usize>, x: usize) -> usize {
1610 if parent[x] != x { parent[x] = find(parent, parent[x]); }
1611 parent[x]
1612 }
1613
1614 fn union(parent: &mut Vec<usize>, rank: &mut Vec<usize>, x: usize, y: usize) {
1615 let rx = find(parent, x);
1616 let ry = find(parent, y);
1617 if rx == ry { return; }
1618 if rank[rx] < rank[ry] { parent[rx] = ry; }
1619 else if rank[rx] > rank[ry] { parent[ry] = rx; }
1620 else { parent[ry] = rx; rank[rx] += 1; }
1621 }
1622
1623 for c in constraints.iter() {
1625 let handles = c.body_handles();
1626 if handles.len() < 2 { continue; }
1627 for i in 1..handles.len() {
1628 if let (Some(&ia), Some(&ib)) = (handle_to_idx.get(&handles[0]), handle_to_idx.get(&handles[i])) {
1629 union(&mut parent, &mut rank, ia, ib);
1630 }
1631 }
1632 }
1633
1634 let mut island_map: HashMap<usize, usize> = HashMap::new();
1636 let mut islands: Vec<ConstraintIsland> = Vec::new();
1637
1638 for (i, h) in body_list.iter().enumerate() {
1639 let root = find(&mut parent, i);
1640 let island_idx = *island_map.entry(root).or_insert_with(|| {
1641 let idx = islands.len();
1642 islands.push(ConstraintIsland { body_handles: Vec::new(), constraint_indices: Vec::new() });
1643 idx
1644 });
1645 islands[island_idx].body_handles.push(*h);
1646 }
1647
1648 for (ci, c) in constraints.iter().enumerate() {
1650 let handles = c.body_handles();
1651 if handles.is_empty() { continue; }
1652 let first = handles[0];
1653 if let Some(&idx) = handle_to_idx.get(&first) {
1654 let root = find(&mut parent, idx);
1655 if let Some(&island_idx) = island_map.get(&root) {
1656 islands[island_idx].constraint_indices.push(ci);
1657 }
1658 }
1659 }
1660
1661 islands
1662}
1663
1664pub struct ConstraintWorld {
1668 pub bodies: HashMap<BodyHandle, BodyState>,
1669 pub constraints: Vec<Box<dyn Constraint>>,
1670 pub solver: ConstraintSolver,
1671 pub gravity: Vec2,
1672 next_body_id: u32,
1673 next_con_id: u32,
1674 pub use_islands: bool,
1676 pub use_xpbd: bool,
1678}
1679
1680impl ConstraintWorld {
1681 pub fn new() -> Self {
1682 Self {
1683 bodies: HashMap::new(),
1684 constraints: Vec::new(),
1685 solver: ConstraintSolver::default(),
1686 gravity: Vec2::new(0.0, -9.81),
1687 next_body_id: 1,
1688 next_con_id: 1,
1689 use_islands: false,
1690 use_xpbd: false,
1691 }
1692 }
1693
1694 pub fn add_body(&mut self, state: BodyState) -> BodyHandle {
1695 let id = BodyHandle(self.next_body_id);
1696 self.next_body_id += 1;
1697 self.bodies.insert(id, state);
1698 id
1699 }
1700
1701 pub fn add_constraint(&mut self, c: Box<dyn Constraint>) -> ConstraintId {
1702 let id = ConstraintId(self.next_con_id);
1703 self.next_con_id += 1;
1704 self.constraints.push(c);
1705 id
1706 }
1707
1708 pub fn remove_body(&mut self, handle: BodyHandle) {
1709 self.bodies.remove(&handle);
1710 }
1711
1712 pub fn step(&mut self, dt: f32) {
1713 if dt < 1e-8 { return; }
1714
1715 for body in self.bodies.values_mut() {
1717 if !body.is_static() {
1718 body.velocity += self.gravity * dt;
1719 }
1720 }
1721
1722 if self.use_xpbd {
1723 for body in self.bodies.values_mut() {
1725 if !body.is_static() {
1726 body.position += body.velocity * dt;
1727 body.angle += body.angular_velocity * dt;
1728 }
1729 }
1730 self.solver.solve_xpbd(&mut self.bodies, &mut self.constraints, dt);
1731 } else {
1732 for body in self.bodies.values_mut() {
1735 if !body.is_static() {
1736 body.position += body.velocity * dt;
1737 body.angle += body.angular_velocity * dt;
1738 }
1739 }
1740
1741 self.solver.solve(&mut self.bodies, &mut self.constraints, dt);
1742 self.solver.solve_positions(&mut self.bodies, &mut self.constraints, dt);
1743 }
1744 }
1745
1746 pub fn body(&self, h: BodyHandle) -> Option<&BodyState> { self.bodies.get(&h) }
1747 pub fn body_mut(&mut self, h: BodyHandle) -> Option<&mut BodyState> { self.bodies.get_mut(&h) }
1748
1749 pub fn body_count(&self) -> usize { self.bodies.len() }
1750 pub fn constraint_count(&self) -> usize { self.constraints.len() }
1751
1752 pub fn total_kinetic_energy(&self) -> f32 {
1754 self.bodies.values().map(|b| b.kinetic_energy()).sum()
1755 }
1756
1757 pub fn remove_sleeping(&mut self, threshold: f32) {
1759 let sleeping: Vec<BodyHandle> = self.bodies.iter()
1760 .filter(|(_, b)| b.kinetic_energy() < threshold)
1761 .map(|(h, _)| *h)
1762 .collect();
1763 for h in sleeping { self.bodies.remove(&h); }
1764 }
1765}
1766
1767impl Default for ConstraintWorld {
1768 fn default() -> Self { Self::new() }
1769}
1770
1771#[cfg(test)]
1774mod tests {
1775 use super::*;
1776 use std::f32::consts::PI;
1777
1778 fn make_two_bodies(world: &mut ConstraintWorld, mass: f32, pos_a: Vec2, pos_b: Vec2) -> (BodyHandle, BodyHandle) {
1779 let a = world.add_body(BodyState::new(pos_a, mass, mass * 0.1));
1780 let b = world.add_body(BodyState::new(pos_b, mass, mass * 0.1));
1781 (a, b)
1782 }
1783
1784 #[test]
1785 fn distance_constraint_rigid_rod() {
1786 let mut world = ConstraintWorld::new();
1787 world.gravity = Vec2::ZERO;
1788 let (a, b) = make_two_bodies(&mut world, 1.0, Vec2::ZERO, Vec2::new(3.0, 0.0));
1789 let c = Box::new(DistanceConstraint::rigid_rod(a, Vec2::ZERO, b, Vec2::ZERO, 2.0));
1790 world.add_constraint(c);
1791 for _ in 0..60 {
1792 world.step(1.0 / 60.0);
1793 }
1794 let pa = world.body(a).unwrap().position;
1795 let pb = world.body(b).unwrap().position;
1796 let dist = (pb - pa).length();
1797 assert!((dist - 2.0).abs() < 0.1, "rod should maintain 2m distance, got {dist}");
1798 }
1799
1800 #[test]
1801 fn distance_constraint_soft_spring() {
1802 let mut world = ConstraintWorld::new();
1803 world.gravity = Vec2::ZERO;
1804 let (a, b) = make_two_bodies(&mut world, 1.0, Vec2::ZERO, Vec2::new(5.0, 0.0));
1805 let c = Box::new(DistanceConstraint::soft(a, Vec2::ZERO, b, Vec2::ZERO, 2.0, 0.01));
1806 world.add_constraint(c);
1807 world.step(1.0 / 60.0);
1808 let pb = world.body(b).unwrap().position;
1810 assert!(pb.x.is_finite());
1811 }
1812
1813 #[test]
1814 fn hinge_constraint_limits() {
1815 let mut world = ConstraintWorld::new();
1816 world.gravity = Vec2::ZERO;
1817 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1818 let b = world.add_body({
1819 let mut s = BodyState::new(Vec2::new(0.0, 1.0), 1.0, 0.1);
1820 s.angular_velocity = 10.0;
1821 s
1822 });
1823 let c = Box::new(
1824 HingeConstraint::new(a, Vec2::ZERO, b, Vec2::new(0.0, -1.0))
1825 .with_limits(-PI / 4.0, PI / 4.0)
1826 );
1827 world.add_constraint(c);
1828 for _ in 0..120 {
1829 world.step(1.0 / 60.0);
1830 }
1831 let angle = world.body(b).unwrap().angle;
1832 assert!(angle.abs() <= PI / 4.0 + 0.1, "angle {} should be within limits", angle);
1833 }
1834
1835 #[test]
1836 fn hinge_constraint_motor() {
1837 let mut world = ConstraintWorld::new();
1838 world.gravity = Vec2::ZERO;
1839 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1840 let b = world.add_body(BodyState::new(Vec2::ZERO, 1.0, 1.0));
1841 let c = Box::new(
1842 HingeConstraint::new(a, Vec2::ZERO, b, Vec2::ZERO)
1843 .with_motor(2.0 * PI, 100.0)
1844 );
1845 world.add_constraint(c);
1846 for _ in 0..60 {
1847 world.step(1.0 / 60.0);
1848 }
1849 let omega = world.body(b).unwrap().angular_velocity;
1850 assert!(omega > 0.0, "motor should drive positive angular velocity, got {omega}");
1851 }
1852
1853 #[test]
1854 fn ball_socket_cone_limit() {
1855 let mut world = ConstraintWorld::new();
1856 world.gravity = Vec2::ZERO;
1857 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1858 let b = world.add_body({
1859 let mut s = BodyState::new(Vec2::new(1.0, 0.0), 1.0, 0.1);
1860 s.angular_velocity = 5.0;
1861 s
1862 });
1863 let c = Box::new(
1864 BallSocketConstraint::new(a, Vec2::ZERO, b, Vec2::new(-1.0, 0.0))
1865 .with_cone_limit(PI / 6.0)
1866 );
1867 world.add_constraint(c);
1868 for _ in 0..60 {
1869 world.step(1.0 / 60.0);
1870 }
1871 let angle = world.body(b).unwrap().angle;
1872 assert!(angle.abs() <= PI / 6.0 + 0.2, "cone angle exceeded");
1873 }
1874
1875 #[test]
1876 fn slider_constraint_limits() {
1877 let mut world = ConstraintWorld::new();
1878 world.gravity = Vec2::ZERO;
1879 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1880 let b = world.add_body({
1881 let mut s = BodyState::new(Vec2::new(0.5, 0.0), 1.0, 0.1);
1882 s.velocity = Vec2::new(5.0, 0.0);
1883 s
1884 });
1885 let c = Box::new(
1886 SliderConstraint::new(a, Vec2::ZERO, b, Vec2::ZERO, Vec2::X)
1887 .with_limits(-1.0, 1.0)
1888 );
1889 world.add_constraint(c);
1890 for _ in 0..120 {
1891 world.step(1.0 / 60.0);
1892 }
1893 let pos_x = world.body(b).unwrap().position.x;
1894 assert!(pos_x <= 1.5, "slider should be limited, got x={pos_x}");
1895 }
1896
1897 #[test]
1898 fn weld_constraint_holds() {
1899 let mut world = ConstraintWorld::new();
1900 world.gravity = Vec2::new(0.0, -9.81);
1901 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1902 let b = world.add_body(BodyState::new(Vec2::new(1.0, 0.0), 1.0, 0.1));
1903 let c = Box::new(WeldConstraint::new(a, Vec2::new(1.0, 0.0), b, Vec2::ZERO, 0.0));
1904 world.add_constraint(c);
1905 for _ in 0..60 {
1906 world.step(1.0 / 60.0);
1907 }
1908 let pos = world.body(b).unwrap().position;
1909 assert!((pos - Vec2::new(1.0, 0.0)).length() < 0.5, "weld should hold, drift={}", (pos - Vec2::new(1.0, 0.0)).length());
1910 }
1911
1912 #[test]
1913 fn pulley_constraint_ratio() {
1914 let mut world = ConstraintWorld::new();
1915 world.gravity = Vec2::ZERO;
1916 let a = world.add_body(BodyState::new(Vec2::new(-3.0, 0.0), 1.0, 0.1));
1917 let b = world.add_body(BodyState::new(Vec2::new( 3.0, 0.0), 1.0, 0.1));
1918 let init_la = (Vec2::new(-3.0, 0.0) - Vec2::new(-1.0, 2.0)).length();
1919 let init_lb = (Vec2::new( 3.0, 0.0) - Vec2::new( 1.0, 2.0)).length();
1920 let total = init_la + init_lb;
1921 let c = Box::new(PulleyConstraint::new(
1922 a, Vec2::ZERO, Vec2::new(-1.0, 2.0),
1923 b, Vec2::ZERO, Vec2::new( 1.0, 2.0),
1924 1.0, total,
1925 ));
1926 world.add_constraint(c);
1927 world.step(1.0 / 60.0);
1928 let pa = world.body(a).unwrap().position;
1929 assert!(pa.is_finite(), "pulley should not produce NaN");
1930 }
1931
1932 #[test]
1933 fn spring_constraint_oscillates() {
1934 let mut world = ConstraintWorld::new();
1935 world.gravity = Vec2::ZERO;
1936 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1937 let b = world.add_body(BodyState::new(Vec2::new(3.0, 0.0), 1.0, 0.1));
1938 let c = Box::new(SpringConstraint::new(a, Vec2::ZERO, b, Vec2::ZERO, 1.0, 50.0, 0.5));
1939 world.add_constraint(c);
1940 let pos_before = world.body(b).unwrap().position.x;
1941 world.step(0.016);
1942 let pos_after = world.body(b).unwrap().position.x;
1943 assert!(pos_after < pos_before, "spring should pull body toward rest length");
1944 }
1945
1946 #[test]
1947 fn motor_constraint_drives_rotation() {
1948 let mut world = ConstraintWorld::new();
1949 world.gravity = Vec2::ZERO;
1950 let a = world.add_body(BodyState::new(Vec2::ZERO, 1.0, 1.0));
1951 let c = Box::new(MotorConstraint::new(a, 5.0, 100.0));
1952 world.add_constraint(c);
1953 for _ in 0..30 {
1954 world.step(1.0 / 60.0);
1955 }
1956 let omega = world.body(a).unwrap().angular_velocity;
1957 assert!(omega > 0.0, "motor should spin body, got {omega}");
1958 }
1959
1960 #[test]
1961 fn gear_constraint_couples_bodies() {
1962 let mut world = ConstraintWorld::new();
1963 world.gravity = Vec2::ZERO;
1964 let a = world.add_body({
1965 let mut s = BodyState::new(Vec2::ZERO, 1.0, 1.0);
1966 s.angular_velocity = 2.0;
1967 s
1968 });
1969 let b = world.add_body(BodyState::new(Vec2::new(2.0, 0.0), 1.0, 1.0));
1970 let c = Box::new(GearConstraint::new(a, b, 2.0));
1971 world.add_constraint(c);
1972 for _ in 0..30 {
1973 world.step(1.0 / 60.0);
1974 }
1975 let oa = world.body(a).unwrap().angular_velocity;
1976 let ob = world.body(b).unwrap().angular_velocity;
1977 let coupling_err = (oa + 2.0 * ob).abs();
1979 assert!(coupling_err < 1.0, "gear coupling error too large: {coupling_err}");
1980 }
1981
1982 #[test]
1983 fn max_distance_constraint_rope() {
1984 let mut world = ConstraintWorld::new();
1985 world.gravity = Vec2::ZERO;
1986 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1987 let b = world.add_body({
1988 let mut s = BodyState::new(Vec2::new(3.0, 0.0), 1.0, 0.1);
1989 s.velocity = Vec2::new(10.0, 0.0);
1990 s
1991 });
1992 let c = Box::new(MaxDistanceConstraint::new(a, Vec2::ZERO, b, Vec2::ZERO, 2.0));
1993 world.add_constraint(c);
1994 for _ in 0..60 {
1995 world.step(1.0 / 60.0);
1996 }
1997 let dist = world.body(b).unwrap().position.length();
1998 assert!(dist <= 2.5, "rope should limit distance, got {dist}");
1999 }
2000
2001 #[test]
2002 fn pin_constraint_holds_at_world_point() {
2003 let mut world = ConstraintWorld::new();
2004 world.gravity = Vec2::new(0.0, -9.81);
2005 let a = world.add_body(BodyState::new(Vec2::new(0.0, 5.0), 1.0, 0.1));
2006 let target = Vec2::new(0.0, 5.0);
2007 let c = Box::new(PinConstraint::new(a, Vec2::ZERO, target));
2008 world.add_constraint(c);
2009 for _ in 0..60 {
2010 world.step(1.0 / 60.0);
2011 }
2012 let pos = world.body(a).unwrap().position;
2013 let err = (pos - target).length();
2014 assert!(err < 0.5, "pin should hold, error={err}");
2015 }
2016
2017 #[test]
2018 fn constraint_world_gravity_applied() {
2019 let mut world = ConstraintWorld::new();
2020 let a = world.add_body(BodyState::new(Vec2::new(0.0, 10.0), 1.0, 0.1));
2021 world.step(1.0);
2022 let pos = world.body(a).unwrap().position;
2023 assert!(pos.y < 10.0, "gravity should pull body down");
2024 }
2025
2026 #[test]
2027 fn island_detection_finds_connected_components() {
2028 let mut world = ConstraintWorld::new();
2029 let a = world.add_body(BodyState::new(Vec2::ZERO, 1.0, 0.1));
2030 let b = world.add_body(BodyState::new(Vec2::new(1.0, 0.0), 1.0, 0.1));
2031 let c = world.add_body(BodyState::new(Vec2::new(5.0, 0.0), 1.0, 0.1)); world.add_constraint(Box::new(DistanceConstraint::new(a, Vec2::ZERO, b, Vec2::ZERO, 1.0)));
2033 let islands = detect_islands(&world.bodies, &world.constraints);
2034 assert_eq!(islands.len(), 2, "should detect 2 islands");
2036 }
2037
2038 #[test]
2039 fn contact_constraint_non_penetration() {
2040 let mut bodies: HashMap<BodyHandle, BodyState> = HashMap::new();
2041 let h_a = BodyHandle(1);
2042 let h_b = BodyHandle(2);
2043 let mut ba = BodyState::new(Vec2::ZERO, 1.0, 0.1);
2044 let mut bb = BodyState::new(Vec2::new(0.5, 0.0), 1.0, 0.1);
2045 ba.velocity = Vec2::new(1.0, 0.0);
2046 bb.velocity = Vec2::new(-1.0, 0.0);
2047 bodies.insert(h_a, ba);
2048 bodies.insert(h_b, bb);
2049 let mut c = ContactConstraint::new(
2050 h_a, h_b,
2051 Vec2::new(0.25, 0.0), Vec2::X, 0.5,
2052 0.3, 0.5,
2053 );
2054 c.prepare(&bodies, 1.0 / 60.0);
2055 c.solve_contact(&mut bodies, 1.0 / 60.0);
2056 let va = bodies[&h_a].velocity.x;
2057 let vb = bodies[&h_b].velocity.x;
2058 assert!(vb >= va - 0.01, "relative velocity after impulse should be non-negative");
2059 }
2060
2061 #[test]
2062 fn total_kinetic_energy_is_finite() {
2063 let mut world = ConstraintWorld::new();
2064 for i in 0..10 {
2065 let s = BodyState::new(Vec2::new(i as f32, 0.0), 1.0, 0.1);
2066 world.add_body(s);
2067 }
2068 for _ in 0..60 {
2069 world.step(1.0 / 60.0);
2070 }
2071 let ke = world.total_kinetic_energy();
2072 assert!(ke.is_finite(), "KE should be finite");
2073 }
2074}