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 (pa, pb) = self.world_anchors(bodies);
529 let delta = pb - pa;
530 let len = delta.length().max(1e-7);
531 let dir = delta / len;
532 let ba = bodies.get(&self.body_a);
533 let bb = bodies.get(&self.body_b);
534 let im_a = ba.map(|b| b.inv_mass).unwrap_or(0.0);
535 let im_b = bb.map(|b| b.inv_mass).unwrap_or(0.0);
536 let ii_a = ba.map(|b| b.inv_inertia).unwrap_or(0.0);
537 let ii_b = bb.map(|b| b.inv_inertia).unwrap_or(0.0);
538 let ra = ba.map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
539 let rb = bb.map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
540 let rda = cross2(ra, dir);
541 let rdb = cross2(rb, dir);
542 let d = im_a + im_b + ii_a * rda * rda + ii_b * rdb * rdb;
543 if d < 1e-10 { 0.0 } else { 1.0 / d }
544 }
545
546 fn solve_anchor(&self, bodies: &mut HashMap<BodyHandle, BodyState>, beta: f32, dt: f32) {
548 let (pa, pb) = self.world_anchors(bodies);
549 let delta = pb - pa;
550 if delta.length_squared() < 1e-12 { return; }
551 let len = delta.length();
552 let dir = delta / len;
553 let em = self.positional_effective_mass(bodies);
554 if em < 1e-10 { return; }
555 let lambda = -beta / dt * len * em;
556 let impulse = dir * lambda;
557 if let Some(b) = bodies.get_mut(&self.body_a) {
558 b.apply_impulse(-impulse, pa - b.position);
559 }
560 if let Some(b) = bodies.get_mut(&self.body_b) {
561 b.apply_impulse(impulse, pb - b.position);
562 }
563 }
564
565 fn solve_limit(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
567 let Some((min, max)) = self.limits else { return };
568 let rel_angle = self.relative_angle(bodies);
569 let violation = if rel_angle < min {
570 rel_angle - min
571 } else if rel_angle > max {
572 rel_angle - max
573 } else {
574 return;
575 };
576 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
577 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
578 let denom = ii_a + ii_b + self.compliance / (dt * dt);
579 if denom < 1e-10 { return; }
580 let lambda = -violation / denom;
581 let prev = self.accum_limit;
582 let new_acc = if rel_angle < min {
583 (prev + lambda).max(0.0)
584 } else {
585 (prev + lambda).min(0.0)
586 };
587 let actual = new_acc - prev;
588 self.accum_limit = new_acc;
589 if let Some(b) = bodies.get_mut(&self.body_a) {
590 b.apply_angular_impulse(-actual);
591 }
592 if let Some(b) = bodies.get_mut(&self.body_b) {
593 b.apply_angular_impulse(actual);
594 }
595 }
596
597 fn solve_motor(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>) {
599 let Some((target_vel, max_torque)) = self.motor else { return };
600 let omega_a = bodies.get(&self.body_a).map(|b| b.angular_velocity).unwrap_or(0.0);
601 let omega_b = bodies.get(&self.body_b).map(|b| b.angular_velocity).unwrap_or(0.0);
602 let rel_omega = omega_b - omega_a;
603 let error = rel_omega - target_vel;
604 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
605 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
606 let denom = ii_a + ii_b;
607 if denom < 1e-10 { return; }
608 let lambda = -error / denom;
609 let prev = self.accum_motor;
610 let new_acc = (prev + lambda).clamp(-max_torque, max_torque);
611 let actual = new_acc - prev;
612 self.accum_motor = new_acc;
613 if let Some(b) = bodies.get_mut(&self.body_a) {
614 b.apply_angular_impulse(-actual);
615 }
616 if let Some(b) = bodies.get_mut(&self.body_b) {
617 b.apply_angular_impulse(actual);
618 }
619 }
620}
621
622impl Constraint for HingeConstraint {
623 fn prepare(&mut self, _bodies: &HashMap<BodyHandle, BodyState>, _dt: f32) {
624 }
626
627 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
628 let (pa, pb) = self.world_anchors(bodies);
629 let delta = pb - pa;
630 let len = delta.length().max(1e-7);
631 let dir = delta / len;
632 let ba = bodies.get(&self.body_a);
633 let bb = bodies.get(&self.body_b);
634 let va = ba.map(|b| b.velocity + perp(pa - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
635 let vb = bb.map(|b| b.velocity + perp(pb - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
636 (vb - va).dot(dir)
637 }
638
639 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
640 let (pa, pb) = self.world_anchors(bodies);
641 (pb - pa).length()
642 }
643
644 fn get_compliance(&self) -> f32 { self.compliance }
645
646 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
647 self.positional_effective_mass(bodies)
648 }
649
650 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
651 let (pa, pb) = self.world_anchors(bodies);
652 let delta = pb - pa;
653 let len = delta.length();
654 let dir = if len > 1e-7 { delta / len } else { Vec2::X };
655 let impulse = dir * lambda;
656 if let Some(b) = bodies.get_mut(&self.body_a) {
657 b.apply_impulse(-impulse, pa - b.position);
658 }
659 if let Some(b) = bodies.get_mut(&self.body_b) {
660 b.apply_impulse(impulse, pb - b.position);
661 }
662 }
663
664 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
665 let em = self.effective_mass(bodies);
667 if em > 1e-10 {
668 let cdot = self.compute_cdot(bodies);
669 let bias = self.bias(bodies, dt);
670 let delta = -(cdot + bias) * em;
671 let prev = self.accum_x;
672 let new_acc = prev + delta;
673 let actual = new_acc - prev;
674 self.accum_x = new_acc;
675 if actual.abs() > 1e-14 {
676 self.apply_impulse(bodies, actual);
677 }
678 }
679 self.solve_limit(bodies, dt);
681 self.solve_motor(bodies);
682 }
683
684 fn solve_position(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
685 self.solve_anchor(bodies, 0.2, dt);
686 }
687
688 fn accumulated_impulse(&self) -> f32 { self.accum_x }
689 fn reset_accumulated(&mut self) {
690 self.accum_x = 0.0; self.accum_y = 0.0;
691 self.accum_limit = 0.0; self.accum_motor = 0.0;
692 }
693 fn add_accumulated(&mut self, d: f32) { self.accum_x += d; }
694 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
695}
696
697#[derive(Debug, Clone)]
702pub struct BallSocketConstraint {
703 pub body_a: BodyHandle,
704 pub body_b: BodyHandle,
705 pub anchor_a: Vec2,
706 pub anchor_b: Vec2,
707 pub cone_limit: Option<f32>,
709 pub compliance: f32,
710 accumulated: f32,
711 accum_cone: f32,
712}
713
714impl BallSocketConstraint {
715 pub fn new(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2) -> Self {
716 Self { body_a, body_b, anchor_a, anchor_b, cone_limit: None, compliance: 0.0, accumulated: 0.0, accum_cone: 0.0 }
717 }
718
719 pub fn with_cone_limit(mut self, angle: f32) -> Self { self.cone_limit = Some(angle); self }
720 pub fn with_compliance(mut self, c: f32) -> Self { self.compliance = c; self }
721
722 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
723 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
724 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
725 (pa, pb)
726 }
727
728 fn solve_cone_limit(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>) {
729 let Some(max_angle) = self.cone_limit else { return };
730 let angle_a = bodies.get(&self.body_a).map(|b| b.angle).unwrap_or(0.0);
731 let angle_b = bodies.get(&self.body_b).map(|b| b.angle).unwrap_or(0.0);
732 let rel = wrap_angle(angle_b - angle_a);
733 if rel.abs() <= max_angle { return; }
734 let violation = if rel > max_angle { rel - max_angle } else { rel + max_angle };
735 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
736 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
737 let denom = ii_a + ii_b;
738 if denom < 1e-10 { return; }
739 let lambda = -violation / denom;
740 let prev = self.accum_cone;
741 let new_acc = if rel > max_angle { (prev + lambda).min(0.0) } else { (prev + lambda).max(0.0) };
742 let actual = new_acc - prev;
743 self.accum_cone = new_acc;
744 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_angular_impulse(-actual); }
745 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_angular_impulse(actual); }
746 }
747}
748
749impl Constraint for BallSocketConstraint {
750 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
751 let (pa, pb) = self.world_anchors(bodies);
752 let delta = pb - pa;
753 let len = delta.length().max(1e-7);
754 let dir = delta / len;
755 let va = bodies.get(&self.body_a).map(|b| b.velocity + perp(pa - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
756 let vb = bodies.get(&self.body_b).map(|b| b.velocity + perp(pb - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
757 (vb - va).dot(dir)
758 }
759
760 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
761 let (pa, pb) = self.world_anchors(bodies);
762 (pb - pa).length()
763 }
764
765 fn get_compliance(&self) -> f32 { self.compliance }
766
767 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
768 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
769 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
770 let d = im_a + im_b;
771 if d < 1e-10 { 0.0 } else { 1.0 / d }
772 }
773
774 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
775 let (pa, pb) = self.world_anchors(bodies);
776 let delta = pb - pa;
777 let len = delta.length();
778 let dir = if len > 1e-7 { delta / len } else { Vec2::X };
779 let impulse = dir * lambda;
780 if let Some(b) = bodies.get_mut(&self.body_a) {
781 b.apply_impulse(-impulse, pa - b.position);
782 }
783 if let Some(b) = bodies.get_mut(&self.body_b) {
784 b.apply_impulse(impulse, pb - b.position);
785 }
786 }
787
788 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
789 let em = self.effective_mass(bodies);
790 if em > 1e-10 {
791 let cdot = self.compute_cdot(bodies);
792 let bias = self.bias(bodies, dt);
793 let delta = -(cdot + bias) * em;
794 self.accumulated += delta;
795 self.apply_impulse(bodies, delta);
796 }
797 self.solve_cone_limit(bodies);
798 }
799
800 fn accumulated_impulse(&self) -> f32 { self.accumulated }
801 fn reset_accumulated(&mut self) { self.accumulated = 0.0; self.accum_cone = 0.0; }
802 fn add_accumulated(&mut self, d: f32) { self.accumulated += d; }
803 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
804}
805
806#[derive(Debug, Clone)]
811pub struct SliderConstraint {
812 pub body_a: BodyHandle,
813 pub body_b: BodyHandle,
814 pub anchor_a: Vec2,
815 pub anchor_b: Vec2,
816 pub axis: Vec2,
818 pub limits: Option<(f32, f32)>,
820 pub motor: Option<(f32, f32)>,
822 pub compliance: f32,
823 accum_perp: f32, accum_limit: f32,
825 accum_motor: f32,
826}
827
828impl SliderConstraint {
829 pub fn new(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2, axis: Vec2) -> Self {
830 let axis = axis.normalize_or_zero();
831 Self {
832 body_a, body_b, anchor_a, anchor_b, axis,
833 limits: None, motor: None, compliance: 0.0,
834 accum_perp: 0.0, accum_limit: 0.0, accum_motor: 0.0,
835 }
836 }
837
838 pub fn with_limits(mut self, min: f32, max: f32) -> Self { self.limits = Some((min, max)); self }
839 pub fn with_motor(mut self, vel: f32, max_force: f32) -> Self { self.motor = Some((vel, max_force)); self }
840 pub fn with_compliance(mut self, c: f32) -> Self { self.compliance = c; self }
841
842 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
843 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
844 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
845 (pa, pb)
846 }
847
848 fn slide_offset(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
849 let (pa, pb) = self.world_anchors(bodies);
850 (pb - pa).dot(self.axis)
851 }
852
853 fn perp_offset(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
854 let (pa, pb) = self.world_anchors(bodies);
855 let perp_axis = Vec2::new(-self.axis.y, self.axis.x);
856 (pb - pa).dot(perp_axis)
857 }
858
859 fn solve_perp(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, beta: f32, dt: f32) {
860 let perp_axis = Vec2::new(-self.axis.y, self.axis.x);
861 let c = self.perp_offset(bodies);
862 if c.abs() < 1e-8 { return; }
863 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
864 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
865 let (pa, pb) = self.world_anchors(bodies);
866 let ra = bodies.get(&self.body_a).map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
867 let rb = bodies.get(&self.body_b).map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
868 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
869 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
870 let rda = cross2(ra, perp_axis);
871 let rdb = cross2(rb, perp_axis);
872 let denom = im_a + im_b + ii_a * rda * rda + ii_b * rdb * rdb;
873 if denom < 1e-10 { return; }
874 let lambda = -beta / dt * c / denom;
875 let impulse = perp_axis * lambda;
876 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, ra); }
877 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, rb); }
878 }
879
880 fn solve_translation_limit(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
881 let Some((min, max)) = self.limits else { return };
882 let offset = self.slide_offset(bodies);
883 let violation = if offset < min { offset - min } else if offset > max { offset - max } else { return };
884 let (pa, pb) = self.world_anchors(bodies);
885 let ra = bodies.get(&self.body_a).map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
886 let rb = bodies.get(&self.body_b).map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
887 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
888 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
889 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
890 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
891 let rda = cross2(ra, self.axis);
892 let rdb = cross2(rb, self.axis);
893 let denom = im_a + im_b + ii_a * rda * rda + ii_b * rdb * rdb + self.compliance / (dt * dt);
894 if denom < 1e-10 { return; }
895 let lambda = -violation / denom;
896 let prev = self.accum_limit;
897 let new_acc = if offset < min { (prev + lambda).max(0.0) } else { (prev + lambda).min(0.0) };
898 let actual = new_acc - prev;
899 self.accum_limit = new_acc;
900 let impulse = self.axis * actual;
901 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, ra); }
902 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, rb); }
903 }
904
905 fn solve_motor_constraint(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>) {
906 let Some((target_vel, max_force)) = self.motor else { return };
907 let va = bodies.get(&self.body_a).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
908 let vb = bodies.get(&self.body_b).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
909 let rel_vel = (vb - va).dot(self.axis);
910 let error = rel_vel - target_vel;
911 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
912 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
913 let denom = im_a + im_b;
914 if denom < 1e-10 { return; }
915 let lambda = -error / denom;
916 let prev = self.accum_motor;
917 let new_acc = (prev + lambda).clamp(-max_force, max_force);
918 let actual = new_acc - prev;
919 self.accum_motor = new_acc;
920 let impulse = self.axis * actual;
921 let (pa, pb) = self.world_anchors(bodies);
922 let ra = bodies.get(&self.body_a).map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
923 let rb = bodies.get(&self.body_b).map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
924 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, ra); }
925 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, rb); }
926 }
927}
928
929impl Constraint for SliderConstraint {
930 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
931 let va = bodies.get(&self.body_a).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
932 let vb = bodies.get(&self.body_b).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
933 (vb - va).dot(Vec2::new(-self.axis.y, self.axis.x))
934 }
935
936 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
937 self.perp_offset(bodies)
938 }
939
940 fn get_compliance(&self) -> f32 { self.compliance }
941
942 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
943 let perp_axis = Vec2::new(-self.axis.y, self.axis.x);
944 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
945 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
946 let d = im_a + im_b;
947 if d < 1e-10 { 0.0 } else { 1.0 / d }
948 }
949
950 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
951 let perp_axis = Vec2::new(-self.axis.y, self.axis.x);
952 let impulse = perp_axis * lambda;
953 let (pa, pb) = self.world_anchors(bodies);
954 let ra = bodies.get(&self.body_a).map(|b| pa - b.position).unwrap_or(Vec2::ZERO);
955 let rb = bodies.get(&self.body_b).map(|b| pb - b.position).unwrap_or(Vec2::ZERO);
956 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, ra); }
957 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, rb); }
958 }
959
960 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
961 self.solve_perp(bodies, 0.1, dt);
962 self.solve_translation_limit(bodies, dt);
963 self.solve_motor_constraint(bodies);
964 }
965
966 fn solve_position(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
967 self.solve_perp(bodies, 0.2, dt);
968 }
969
970 fn accumulated_impulse(&self) -> f32 { self.accum_perp }
971 fn reset_accumulated(&mut self) { self.accum_perp = 0.0; self.accum_limit = 0.0; self.accum_motor = 0.0; }
972 fn add_accumulated(&mut self, d: f32) { self.accum_perp += d; }
973 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
974}
975
976#[derive(Debug, Clone)]
980pub struct WeldConstraint {
981 pub body_a: BodyHandle,
982 pub body_b: BodyHandle,
983 pub anchor_a: Vec2,
984 pub anchor_b: Vec2,
985 pub ref_angle: f32,
987 pub angular_compliance: f32,
989 pub linear_compliance: f32,
991 accum_lin: f32,
992 accum_ang: f32,
993}
994
995impl WeldConstraint {
996 pub fn new(body_a: BodyHandle, anchor_a: Vec2, body_b: BodyHandle, anchor_b: Vec2, ref_angle: f32) -> Self {
997 Self {
998 body_a, body_b, anchor_a, anchor_b, ref_angle,
999 angular_compliance: 0.0, linear_compliance: 0.0,
1000 accum_lin: 0.0, accum_ang: 0.0,
1001 }
1002 }
1003
1004 pub fn with_angular_compliance(mut self, c: f32) -> Self { self.angular_compliance = c; self }
1005 pub fn with_linear_compliance(mut self, c: f32) -> Self { self.linear_compliance = c; self }
1006
1007 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
1008 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
1009 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
1010 (pa, pb)
1011 }
1012
1013 fn angle_error(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1014 let angle_a = bodies.get(&self.body_a).map(|b| b.angle).unwrap_or(0.0);
1015 let angle_b = bodies.get(&self.body_b).map(|b| b.angle).unwrap_or(0.0);
1016 wrap_angle(angle_b - angle_a - self.ref_angle)
1017 }
1018}
1019
1020impl Constraint for WeldConstraint {
1021 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1022 let (pa, pb) = self.world_anchors(bodies);
1023 let delta = pb - pa;
1024 let len = delta.length().max(1e-7);
1025 let dir = delta / len;
1026 let va = bodies.get(&self.body_a).map(|b| b.velocity + perp(pa - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
1027 let vb = bodies.get(&self.body_b).map(|b| b.velocity + perp(pb - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
1028 (vb - va).dot(dir)
1029 }
1030
1031 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1032 let (pa, pb) = self.world_anchors(bodies);
1033 (pb - pa).length()
1034 }
1035
1036 fn get_compliance(&self) -> f32 { self.linear_compliance }
1037
1038 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1039 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
1040 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
1041 let d = im_a + im_b;
1042 if d < 1e-10 { 0.0 } else { 1.0 / d }
1043 }
1044
1045 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1046 let (pa, pb) = self.world_anchors(bodies);
1047 let delta = pb - pa;
1048 let len = delta.length();
1049 let dir = if len > 1e-7 { delta / len } else { Vec2::X };
1050 let impulse = dir * lambda;
1051 if let Some(b) = bodies.get_mut(&self.body_a) {
1052 b.apply_impulse(-impulse, pa - b.position);
1053 }
1054 if let Some(b) = bodies.get_mut(&self.body_b) {
1055 b.apply_impulse(impulse, pb - b.position);
1056 }
1057 }
1058
1059 fn solve_velocity(&mut self, bodies: &mut HashMap<BodyHandle, BodyState>, dt: f32) {
1060 let em_lin = self.effective_mass(bodies);
1062 if em_lin > 1e-10 {
1063 let cdot = self.compute_cdot(bodies);
1064 let bias = self.bias(bodies, dt);
1065 let delta = -(cdot + bias) * em_lin;
1066 self.accum_lin += delta;
1067 self.apply_impulse(bodies, delta);
1068 }
1069 let err = self.angle_error(bodies);
1071 let omega_a = bodies.get(&self.body_a).map(|b| b.angular_velocity).unwrap_or(0.0);
1072 let omega_b = bodies.get(&self.body_b).map(|b| b.angular_velocity).unwrap_or(0.0);
1073 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
1074 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
1075 let denom = ii_a + ii_b + self.angular_compliance / (dt * dt);
1076 if denom > 1e-10 {
1077 let ang_bias = -0.1 / dt * err;
1078 let rel_omega = omega_b - omega_a;
1079 let lambda = -(rel_omega + ang_bias) / denom;
1080 self.accum_ang += lambda;
1081 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_angular_impulse(-lambda); }
1082 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_angular_impulse(lambda); }
1083 }
1084 }
1085
1086 fn accumulated_impulse(&self) -> f32 { self.accum_lin }
1087 fn reset_accumulated(&mut self) { self.accum_lin = 0.0; self.accum_ang = 0.0; }
1088 fn add_accumulated(&mut self, d: f32) { self.accum_lin += d; }
1089 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1090}
1091
1092#[derive(Debug, Clone)]
1097pub struct PulleyConstraint {
1098 pub body_a: BodyHandle,
1099 pub body_b: BodyHandle,
1100 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,
1107}
1108
1109impl PulleyConstraint {
1110 pub fn new(
1111 body_a: BodyHandle, anchor_a: Vec2, pulley_a: Vec2,
1112 body_b: BodyHandle, anchor_b: Vec2, pulley_b: Vec2,
1113 ratio: f32, total_length: f32,
1114 ) -> Self {
1115 Self { body_a, body_b, anchor_a, anchor_b, pulley_a, pulley_b, ratio, total_length, accumulated: 0.0 }
1116 }
1117
1118 fn world_anchor_a(&self, bodies: &HashMap<BodyHandle, BodyState>) -> Vec2 {
1119 bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO)
1120 }
1121 fn world_anchor_b(&self, bodies: &HashMap<BodyHandle, BodyState>) -> Vec2 {
1122 bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO)
1123 }
1124
1125 fn lengths(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (f32, f32) {
1126 let wa = self.world_anchor_a(bodies);
1127 let wb = self.world_anchor_b(bodies);
1128 let la = (wa - self.pulley_a).length();
1129 let lb = (wb - self.pulley_b).length();
1130 (la, lb)
1131 }
1132
1133 fn dirs(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
1134 let wa = self.world_anchor_a(bodies);
1135 let wb = self.world_anchor_b(bodies);
1136 let da = (wa - self.pulley_a);
1137 let db = (wb - self.pulley_b);
1138 let la = da.length();
1139 let lb = db.length();
1140 (
1141 if la > 1e-7 { da / la } else { Vec2::X },
1142 if lb > 1e-7 { db / lb } else { Vec2::X },
1143 )
1144 }
1145}
1146
1147impl Constraint for PulleyConstraint {
1148 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1149 let (la, lb) = self.lengths(bodies);
1150 la + self.ratio * lb - self.total_length
1151 }
1152
1153 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1154 let wa = self.world_anchor_a(bodies);
1155 let wb = self.world_anchor_b(bodies);
1156 let (da, db) = self.dirs(bodies);
1157 let va = bodies.get(&self.body_a).map(|b| b.velocity + perp(wa - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
1158 let vb = bodies.get(&self.body_b).map(|b| b.velocity + perp(wb - b.position) * b.angular_velocity).unwrap_or(Vec2::ZERO);
1159 va.dot(da) + self.ratio * vb.dot(db)
1160 }
1161
1162 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1163 let wa = self.world_anchor_a(bodies);
1164 let wb = self.world_anchor_b(bodies);
1165 let (da, db) = self.dirs(bodies);
1166 let ra = bodies.get(&self.body_a).map(|b| wa - b.position).unwrap_or(Vec2::ZERO);
1167 let rb = bodies.get(&self.body_b).map(|b| wb - b.position).unwrap_or(Vec2::ZERO);
1168 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
1169 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
1170 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
1171 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
1172 let rda = cross2(ra, da);
1173 let rdb = cross2(rb, db);
1174 let denom = im_a + ii_a * rda * rda + self.ratio * self.ratio * (im_b + ii_b * rdb * rdb);
1175 if denom < 1e-10 { 0.0 } else { 1.0 / denom }
1176 }
1177
1178 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1179 let wa = self.world_anchor_a(bodies);
1180 let wb = self.world_anchor_b(bodies);
1181 let (da, db) = self.dirs(bodies);
1182 let ra = bodies.get(&self.body_a).map(|b| wa - b.position).unwrap_or(Vec2::ZERO);
1183 let rb = bodies.get(&self.body_b).map(|b| wb - b.position).unwrap_or(Vec2::ZERO);
1184 if let Some(b) = bodies.get_mut(&self.body_a) {
1185 b.apply_impulse(-da * lambda, ra);
1186 }
1187 if let Some(b) = bodies.get_mut(&self.body_b) {
1188 b.apply_impulse(-db * (lambda * self.ratio), rb);
1189 }
1190 }
1191
1192 fn impulse_bounds(&self) -> (f32, f32) { (0.0, f32::INFINITY) }
1193 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1194 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1195 fn add_accumulated(&mut self, d: f32) { self.accumulated = (self.accumulated + d).max(0.0); }
1196 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1197}
1198
1199#[derive(Debug, Clone)]
1203pub struct SpringConstraint {
1204 pub body_a: BodyHandle,
1205 pub body_b: BodyHandle,
1206 pub anchor_a: Vec2,
1207 pub anchor_b: Vec2,
1208 pub rest_length: f32,
1209 pub stiffness: f32,
1210 pub damping: f32,
1211 accumulated: f32,
1212}
1213
1214impl SpringConstraint {
1215 pub fn new(a: BodyHandle, aa: Vec2, b: BodyHandle, ab: Vec2, rest: f32, k: f32, d: f32) -> Self {
1216 Self { body_a: a, body_b: b, anchor_a: aa, anchor_b: ab, rest_length: rest, stiffness: k, damping: d, accumulated: 0.0 }
1217 }
1218
1219 fn world_anchors(&self, bodies: &HashMap<BodyHandle, BodyState>) -> (Vec2, Vec2) {
1220 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
1221 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
1222 (pa, pb)
1223 }
1224}
1225
1226impl Constraint for SpringConstraint {
1227 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1228 let (pa, pb) = self.world_anchors(bodies);
1229 (pb - pa).length() - self.rest_length
1230 }
1231
1232 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1233 let (pa, pb) = self.world_anchors(bodies);
1234 let delta = pb - pa;
1235 let len = delta.length().max(1e-7);
1236 let dir = delta / len;
1237 let va = bodies.get(&self.body_a).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
1238 let vb = bodies.get(&self.body_b).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
1239 (vb - va).dot(dir)
1240 }
1241
1242 fn get_compliance(&self) -> f32 { if self.stiffness > 1e-6 { 1.0 / self.stiffness } else { 0.0 } }
1243
1244 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1245 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
1246 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
1247 let denom = im_a + im_b + self.get_compliance();
1248 if denom < 1e-10 { 0.0 } else { 1.0 / denom }
1249 }
1250
1251 fn bias(&self, bodies: &HashMap<BodyHandle, BodyState>, dt: f32) -> f32 {
1252 self.stiffness * self.compute_c(bodies) / dt
1253 }
1254
1255 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1256 let (pa, pb) = self.world_anchors(bodies);
1257 let delta = pb - pa;
1258 let len = delta.length().max(1e-7);
1259 let dir = delta / len;
1260 let impulse = dir * lambda;
1261 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-impulse, Vec2::ZERO); }
1262 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(impulse, Vec2::ZERO); }
1263 }
1264
1265 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1266 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1267 fn add_accumulated(&mut self, d: f32) { self.accumulated += d; }
1268 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1269}
1270
1271#[derive(Debug, Clone)]
1275pub struct PinConstraint {
1276 pub body: BodyHandle,
1277 pub local_anchor: Vec2,
1278 pub world_target: Vec2,
1279 pub compliance: f32,
1280 accumulated: f32,
1281}
1282
1283impl PinConstraint {
1284 pub fn new(body: BodyHandle, local_anchor: Vec2, world_target: Vec2) -> Self {
1285 Self { body, local_anchor, world_target, compliance: 0.0, accumulated: 0.0 }
1286 }
1287 pub fn with_compliance(mut self, c: f32) -> Self { self.compliance = c; self }
1288}
1289
1290impl Constraint for PinConstraint {
1291 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1292 let pa = bodies.get(&self.body)
1293 .map(|b| b.position + rotate2(self.local_anchor, b.angle))
1294 .unwrap_or(Vec2::ZERO);
1295 (pa - self.world_target).length()
1296 }
1297
1298 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1299 let b = match bodies.get(&self.body) { Some(b) => b, None => return 0.0 };
1300 let pa = b.position + rotate2(self.local_anchor, b.angle);
1301 let dir = (pa - self.world_target).normalize_or_zero();
1302 (b.velocity + perp(pa - b.position) * b.angular_velocity).dot(dir)
1303 }
1304
1305 fn get_compliance(&self) -> f32 { self.compliance }
1306
1307 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1308 let b = match bodies.get(&self.body) { Some(b) => b, None => return 0.0 };
1309 let pa = b.position + rotate2(self.local_anchor, b.angle);
1310 let r = pa - b.position;
1311 let dir = (pa - self.world_target).normalize_or_zero();
1312 let rd = cross2(r, dir);
1313 let denom = b.inv_mass + b.inv_inertia * rd * rd;
1314 if denom < 1e-10 { 0.0 } else { 1.0 / denom }
1315 }
1316
1317 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1318 let b = match bodies.get_mut(&self.body) { Some(b) => b, None => return };
1319 let pa = b.position + rotate2(self.local_anchor, b.angle);
1320 let dir = (pa - self.world_target).normalize_or_zero();
1321 b.apply_impulse(dir * lambda, pa - b.position);
1322 }
1323
1324 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1325 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1326 fn add_accumulated(&mut self, d: f32) { self.accumulated += d; }
1327 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body] }
1328}
1329
1330#[derive(Debug, Clone)]
1334pub struct MotorConstraint {
1335 pub body: BodyHandle,
1336 pub target_velocity: f32,
1337 pub max_torque: f32,
1338 accumulated: f32,
1339}
1340
1341impl MotorConstraint {
1342 pub fn new(body: BodyHandle, target_velocity: f32, max_torque: f32) -> Self {
1343 Self { body, target_velocity, max_torque, accumulated: 0.0 }
1344 }
1345}
1346
1347impl Constraint for MotorConstraint {
1348 fn compute_c(&self, _: &HashMap<BodyHandle, BodyState>) -> f32 { 0.0 }
1349
1350 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1351 bodies.get(&self.body).map(|b| b.angular_velocity).unwrap_or(0.0) - self.target_velocity
1352 }
1353
1354 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1355 let ii = bodies.get(&self.body).map(|b| b.inv_inertia).unwrap_or(0.0);
1356 if ii < 1e-10 { 0.0 } else { 1.0 / ii }
1357 }
1358
1359 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1360 if let Some(b) = bodies.get_mut(&self.body) {
1361 b.angular_velocity += lambda * b.inv_inertia;
1362 }
1363 }
1364
1365 fn impulse_bounds(&self) -> (f32, f32) { (-self.max_torque, self.max_torque) }
1366 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1367 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1368 fn add_accumulated(&mut self, d: f32) {
1369 self.accumulated = (self.accumulated + d).clamp(-self.max_torque, self.max_torque);
1370 }
1371 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body] }
1372}
1373
1374#[derive(Debug, Clone)]
1378pub struct GearConstraint {
1379 pub body_a: BodyHandle,
1380 pub body_b: BodyHandle,
1381 pub ratio: f32,
1382 accumulated: f32,
1383}
1384
1385impl GearConstraint {
1386 pub fn new(body_a: BodyHandle, body_b: BodyHandle, ratio: f32) -> Self {
1387 Self { body_a, body_b, ratio, accumulated: 0.0 }
1388 }
1389}
1390
1391impl Constraint for GearConstraint {
1392 fn compute_c(&self, _: &HashMap<BodyHandle, BodyState>) -> f32 { 0.0 }
1393
1394 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1395 let oa = bodies.get(&self.body_a).map(|b| b.angular_velocity).unwrap_or(0.0);
1396 let ob = bodies.get(&self.body_b).map(|b| b.angular_velocity).unwrap_or(0.0);
1397 oa + self.ratio * ob
1398 }
1399
1400 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1401 let ii_a = bodies.get(&self.body_a).map(|b| b.inv_inertia).unwrap_or(0.0);
1402 let ii_b = bodies.get(&self.body_b).map(|b| b.inv_inertia).unwrap_or(0.0);
1403 let d = ii_a + self.ratio * self.ratio * ii_b;
1404 if d < 1e-10 { 0.0 } else { 1.0 / d }
1405 }
1406
1407 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1408 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_angular_impulse(lambda); }
1409 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_angular_impulse(lambda * self.ratio); }
1410 }
1411
1412 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1413 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1414 fn add_accumulated(&mut self, d: f32) { self.accumulated += d; }
1415 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1416}
1417
1418#[derive(Debug, Clone)]
1422pub struct MaxDistanceConstraint {
1423 pub body_a: BodyHandle,
1424 pub body_b: BodyHandle,
1425 pub anchor_a: Vec2,
1426 pub anchor_b: Vec2,
1427 pub max_distance: f32,
1428 pub compliance: f32,
1429 accumulated: f32,
1430}
1431
1432impl MaxDistanceConstraint {
1433 pub fn new(a: BodyHandle, aa: Vec2, b: BodyHandle, ab: Vec2, max_dist: f32) -> Self {
1434 Self { body_a: a, body_b: b, anchor_a: aa, anchor_b: ab, max_distance: max_dist, compliance: 0.0, accumulated: 0.0 }
1435 }
1436
1437 fn current_length(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1438 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
1439 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
1440 (pb - pa).length()
1441 }
1442}
1443
1444impl Constraint for MaxDistanceConstraint {
1445 fn compute_c(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1446 (self.current_length(bodies) - self.max_distance).max(0.0)
1447 }
1448
1449 fn compute_cdot(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1450 if self.compute_c(bodies) <= 0.0 { return 0.0; }
1451 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
1452 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
1453 let delta = pb - pa;
1454 let len = delta.length().max(1e-7);
1455 let dir = delta / len;
1456 let va = bodies.get(&self.body_a).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
1457 let vb = bodies.get(&self.body_b).map(|b| b.velocity).unwrap_or(Vec2::ZERO);
1458 (vb - va).dot(dir)
1459 }
1460
1461 fn get_compliance(&self) -> f32 { self.compliance }
1462
1463 fn effective_mass(&self, bodies: &HashMap<BodyHandle, BodyState>) -> f32 {
1464 let im_a = bodies.get(&self.body_a).map(|b| b.inv_mass).unwrap_or(0.0);
1465 let im_b = bodies.get(&self.body_b).map(|b| b.inv_mass).unwrap_or(0.0);
1466 let d = im_a + im_b;
1467 if d < 1e-10 { 0.0 } else { 1.0 / d }
1468 }
1469
1470 fn apply_impulse(&self, bodies: &mut HashMap<BodyHandle, BodyState>, lambda: f32) {
1471 let pa = bodies.get(&self.body_a).map(|b| b.position + rotate2(self.anchor_a, b.angle)).unwrap_or(Vec2::ZERO);
1472 let pb = bodies.get(&self.body_b).map(|b| b.position + rotate2(self.anchor_b, b.angle)).unwrap_or(Vec2::ZERO);
1473 let delta = pb - pa;
1474 let len = delta.length().max(1e-7);
1475 let dir = delta / len;
1476 let imp = dir * lambda;
1477 if let Some(b) = bodies.get_mut(&self.body_a) { b.apply_impulse(-imp, Vec2::ZERO); }
1478 if let Some(b) = bodies.get_mut(&self.body_b) { b.apply_impulse(imp, Vec2::ZERO); }
1479 }
1480
1481 fn impulse_bounds(&self) -> (f32, f32) { (0.0, f32::INFINITY) }
1482 fn accumulated_impulse(&self) -> f32 { self.accumulated }
1483 fn reset_accumulated(&mut self) { self.accumulated = 0.0; }
1484 fn add_accumulated(&mut self, d: f32) { self.accumulated = (self.accumulated + d).max(0.0); }
1485 fn body_handles(&self) -> Vec<BodyHandle> { vec![self.body_a, self.body_b] }
1486}
1487
1488pub struct ConstraintSolver {
1495 pub iteration_count: u32,
1496 pub baumgarte_factor: f32,
1497 pub warm_start: bool,
1498 pub substep_count: u32,
1500}
1501
1502impl Default for ConstraintSolver {
1503 fn default() -> Self {
1504 Self { iteration_count: 10, baumgarte_factor: 0.2, warm_start: true, substep_count: 4 }
1505 }
1506}
1507
1508impl ConstraintSolver {
1509 pub fn new(iterations: u32) -> Self {
1510 Self { iteration_count: iterations, ..Default::default() }
1511 }
1512
1513 pub fn solve(
1515 &self,
1516 bodies: &mut HashMap<BodyHandle, BodyState>,
1517 constraints: &mut [Box<dyn Constraint>],
1518 dt: f32,
1519 ) {
1520 if dt < 1e-8 { return; }
1521
1522 if self.warm_start {
1524 for c in constraints.iter() {
1525 let lambda = c.accumulated_impulse();
1526 if lambda.abs() > 1e-10 {
1527 c.apply_impulse(bodies, lambda * 0.8); }
1529 }
1530 } else {
1531 for c in constraints.iter_mut() { c.reset_accumulated(); }
1532 }
1533
1534 for c in constraints.iter_mut() {
1536 c.prepare(bodies, dt);
1537 }
1538
1539 for _ in 0..self.iteration_count {
1541 for c in constraints.iter_mut() {
1542 c.solve_velocity(bodies, dt);
1543 }
1544 }
1545 }
1546
1547 pub fn solve_positions(
1549 &self,
1550 bodies: &mut HashMap<BodyHandle, BodyState>,
1551 constraints: &mut [Box<dyn Constraint>],
1552 dt: f32,
1553 ) {
1554 let alpha = self.baumgarte_factor / dt.max(1e-6);
1555 for c in constraints.iter_mut() {
1556 let em = c.effective_mass(bodies);
1557 if em < 1e-10 { continue; }
1558 let c_val = c.compute_c(bodies);
1559 if c_val.abs() < 1e-5 { continue; }
1560 let lambda = -alpha * c_val * em;
1561 c.apply_impulse(bodies, lambda);
1562 }
1563 }
1564
1565 pub fn solve_xpbd(
1567 &self,
1568 bodies: &mut HashMap<BodyHandle, BodyState>,
1569 constraints: &mut [Box<dyn Constraint>],
1570 dt: f32,
1571 ) {
1572 if dt < 1e-8 { return; }
1573 let sub_dt = dt / self.substep_count as f32;
1574
1575 for _ in 0..self.substep_count {
1576 for c in constraints.iter_mut() { c.reset_accumulated(); }
1578
1579 for body in bodies.values_mut() {
1581 if !body.is_static() {
1582 body.position += body.velocity * sub_dt;
1583 body.angle += body.angular_velocity * sub_dt;
1584 }
1585 }
1586
1587 for _ in 0..self.iteration_count {
1589 for c in constraints.iter_mut() {
1590 c.solve_position(bodies, sub_dt);
1591 }
1592 }
1593
1594 }
1597 }
1598}
1599
1600#[derive(Debug, Clone)]
1604pub struct ConstraintIsland {
1605 pub body_handles: Vec<BodyHandle>,
1606 pub constraint_indices: Vec<usize>,
1607}
1608
1609pub fn detect_islands(
1611 bodies: &HashMap<BodyHandle, BodyState>,
1612 constraints: &[Box<dyn Constraint>],
1613) -> Vec<ConstraintIsland> {
1614 let body_list: Vec<BodyHandle> = bodies.keys().copied().collect();
1615 let n = body_list.len();
1616 if n == 0 { return Vec::new(); }
1617
1618 let mut handle_to_idx: HashMap<BodyHandle, usize> = HashMap::new();
1620 for (i, h) in body_list.iter().enumerate() {
1621 handle_to_idx.insert(*h, i);
1622 }
1623
1624 let mut parent: Vec<usize> = (0..n).collect();
1626 let mut rank: Vec<usize> = vec![0; n];
1627
1628 fn find(parent: &mut Vec<usize>, x: usize) -> usize {
1629 if parent[x] != x { parent[x] = find(parent, parent[x]); }
1630 parent[x]
1631 }
1632
1633 fn union(parent: &mut Vec<usize>, rank: &mut Vec<usize>, x: usize, y: usize) {
1634 let rx = find(parent, x);
1635 let ry = find(parent, y);
1636 if rx == ry { return; }
1637 if rank[rx] < rank[ry] { parent[rx] = ry; }
1638 else if rank[rx] > rank[ry] { parent[ry] = rx; }
1639 else { parent[ry] = rx; rank[rx] += 1; }
1640 }
1641
1642 for c in constraints.iter() {
1644 let handles = c.body_handles();
1645 if handles.len() < 2 { continue; }
1646 for i in 1..handles.len() {
1647 if let (Some(&ia), Some(&ib)) = (handle_to_idx.get(&handles[0]), handle_to_idx.get(&handles[i])) {
1648 union(&mut parent, &mut rank, ia, ib);
1649 }
1650 }
1651 }
1652
1653 let mut island_map: HashMap<usize, usize> = HashMap::new();
1655 let mut islands: Vec<ConstraintIsland> = Vec::new();
1656
1657 for (i, h) in body_list.iter().enumerate() {
1658 let root = find(&mut parent, i);
1659 let island_idx = *island_map.entry(root).or_insert_with(|| {
1660 let idx = islands.len();
1661 islands.push(ConstraintIsland { body_handles: Vec::new(), constraint_indices: Vec::new() });
1662 idx
1663 });
1664 islands[island_idx].body_handles.push(*h);
1665 }
1666
1667 for (ci, c) in constraints.iter().enumerate() {
1669 let handles = c.body_handles();
1670 if handles.is_empty() { continue; }
1671 let first = handles[0];
1672 if let Some(&idx) = handle_to_idx.get(&first) {
1673 let root = find(&mut parent, idx);
1674 if let Some(&island_idx) = island_map.get(&root) {
1675 islands[island_idx].constraint_indices.push(ci);
1676 }
1677 }
1678 }
1679
1680 islands
1681}
1682
1683pub struct ConstraintWorld {
1687 pub bodies: HashMap<BodyHandle, BodyState>,
1688 pub constraints: Vec<Box<dyn Constraint>>,
1689 pub solver: ConstraintSolver,
1690 pub gravity: Vec2,
1691 next_body_id: u32,
1692 next_con_id: u32,
1693 pub use_islands: bool,
1695 pub use_xpbd: bool,
1697}
1698
1699impl ConstraintWorld {
1700 pub fn new() -> Self {
1701 Self {
1702 bodies: HashMap::new(),
1703 constraints: Vec::new(),
1704 solver: ConstraintSolver::default(),
1705 gravity: Vec2::new(0.0, -9.81),
1706 next_body_id: 1,
1707 next_con_id: 1,
1708 use_islands: false,
1709 use_xpbd: false,
1710 }
1711 }
1712
1713 pub fn add_body(&mut self, state: BodyState) -> BodyHandle {
1714 let id = BodyHandle(self.next_body_id);
1715 self.next_body_id += 1;
1716 self.bodies.insert(id, state);
1717 id
1718 }
1719
1720 pub fn add_constraint(&mut self, c: Box<dyn Constraint>) -> ConstraintId {
1721 let id = ConstraintId(self.next_con_id);
1722 self.next_con_id += 1;
1723 self.constraints.push(c);
1724 id
1725 }
1726
1727 pub fn remove_body(&mut self, handle: BodyHandle) {
1728 self.bodies.remove(&handle);
1729 }
1730
1731 pub fn step(&mut self, dt: f32) {
1732 if dt < 1e-8 { return; }
1733
1734 for body in self.bodies.values_mut() {
1736 if !body.is_static() {
1737 body.velocity += self.gravity * dt;
1738 }
1739 }
1740
1741 if self.use_xpbd {
1742 for body in self.bodies.values_mut() {
1744 if !body.is_static() {
1745 body.position += body.velocity * dt;
1746 body.angle += body.angular_velocity * dt;
1747 }
1748 }
1749 self.solver.solve_xpbd(&mut self.bodies, &mut self.constraints, dt);
1750 } else {
1751 self.solver.solve(&mut self.bodies, &mut self.constraints, dt);
1753 self.solver.solve_positions(&mut self.bodies, &mut self.constraints, dt);
1754
1755 for body in self.bodies.values_mut() {
1757 if !body.is_static() {
1758 body.position += body.velocity * dt;
1759 body.angle += body.angular_velocity * dt;
1760 }
1761 }
1762 }
1763 }
1764
1765 pub fn body(&self, h: BodyHandle) -> Option<&BodyState> { self.bodies.get(&h) }
1766 pub fn body_mut(&mut self, h: BodyHandle) -> Option<&mut BodyState> { self.bodies.get_mut(&h) }
1767
1768 pub fn body_count(&self) -> usize { self.bodies.len() }
1769 pub fn constraint_count(&self) -> usize { self.constraints.len() }
1770
1771 pub fn total_kinetic_energy(&self) -> f32 {
1773 self.bodies.values().map(|b| b.kinetic_energy()).sum()
1774 }
1775
1776 pub fn remove_sleeping(&mut self, threshold: f32) {
1778 let sleeping: Vec<BodyHandle> = self.bodies.iter()
1779 .filter(|(_, b)| b.kinetic_energy() < threshold)
1780 .map(|(h, _)| *h)
1781 .collect();
1782 for h in sleeping { self.bodies.remove(&h); }
1783 }
1784}
1785
1786impl Default for ConstraintWorld {
1787 fn default() -> Self { Self::new() }
1788}
1789
1790#[cfg(test)]
1793mod tests {
1794 use super::*;
1795 use std::f32::consts::PI;
1796
1797 fn make_two_bodies(world: &mut ConstraintWorld, mass: f32, pos_a: Vec2, pos_b: Vec2) -> (BodyHandle, BodyHandle) {
1798 let a = world.add_body(BodyState::new(pos_a, mass, mass * 0.1));
1799 let b = world.add_body(BodyState::new(pos_b, mass, mass * 0.1));
1800 (a, b)
1801 }
1802
1803 #[test]
1804 fn distance_constraint_rigid_rod() {
1805 let mut world = ConstraintWorld::new();
1806 world.gravity = Vec2::ZERO;
1807 let (a, b) = make_two_bodies(&mut world, 1.0, Vec2::ZERO, Vec2::new(3.0, 0.0));
1808 let c = Box::new(DistanceConstraint::rigid_rod(a, Vec2::ZERO, b, Vec2::ZERO, 2.0));
1809 world.add_constraint(c);
1810 for _ in 0..60 {
1811 world.step(1.0 / 60.0);
1812 }
1813 let pa = world.body(a).unwrap().position;
1814 let pb = world.body(b).unwrap().position;
1815 let dist = (pb - pa).length();
1816 assert!((dist - 2.0).abs() < 0.1, "rod should maintain 2m distance, got {dist}");
1817 }
1818
1819 #[test]
1820 fn distance_constraint_soft_spring() {
1821 let mut world = ConstraintWorld::new();
1822 world.gravity = Vec2::ZERO;
1823 let (a, b) = make_two_bodies(&mut world, 1.0, Vec2::ZERO, Vec2::new(5.0, 0.0));
1824 let c = Box::new(DistanceConstraint::soft(a, Vec2::ZERO, b, Vec2::ZERO, 2.0, 0.01));
1825 world.add_constraint(c);
1826 world.step(1.0 / 60.0);
1827 let pb = world.body(b).unwrap().position;
1829 assert!(pb.x.is_finite());
1830 }
1831
1832 #[test]
1833 fn hinge_constraint_limits() {
1834 let mut world = ConstraintWorld::new();
1835 world.gravity = Vec2::ZERO;
1836 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1837 let b = world.add_body({
1838 let mut s = BodyState::new(Vec2::new(0.0, 1.0), 1.0, 0.1);
1839 s.angular_velocity = 10.0;
1840 s
1841 });
1842 let c = Box::new(
1843 HingeConstraint::new(a, Vec2::ZERO, b, Vec2::new(0.0, -1.0))
1844 .with_limits(-PI / 4.0, PI / 4.0)
1845 );
1846 world.add_constraint(c);
1847 for _ in 0..120 {
1848 world.step(1.0 / 60.0);
1849 }
1850 let angle = world.body(b).unwrap().angle;
1851 assert!(angle.abs() <= PI / 4.0 + 0.1, "angle {} should be within limits", angle);
1852 }
1853
1854 #[test]
1855 fn hinge_constraint_motor() {
1856 let mut world = ConstraintWorld::new();
1857 world.gravity = Vec2::ZERO;
1858 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1859 let b = world.add_body(BodyState::new(Vec2::ZERO, 1.0, 1.0));
1860 let c = Box::new(
1861 HingeConstraint::new(a, Vec2::ZERO, b, Vec2::ZERO)
1862 .with_motor(2.0 * PI, 100.0)
1863 );
1864 world.add_constraint(c);
1865 for _ in 0..60 {
1866 world.step(1.0 / 60.0);
1867 }
1868 let omega = world.body(b).unwrap().angular_velocity;
1869 assert!(omega > 0.0, "motor should drive positive angular velocity, got {omega}");
1870 }
1871
1872 #[test]
1873 fn ball_socket_cone_limit() {
1874 let mut world = ConstraintWorld::new();
1875 world.gravity = Vec2::ZERO;
1876 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1877 let b = world.add_body({
1878 let mut s = BodyState::new(Vec2::new(1.0, 0.0), 1.0, 0.1);
1879 s.angular_velocity = 5.0;
1880 s
1881 });
1882 let c = Box::new(
1883 BallSocketConstraint::new(a, Vec2::ZERO, b, Vec2::new(-1.0, 0.0))
1884 .with_cone_limit(PI / 6.0)
1885 );
1886 world.add_constraint(c);
1887 for _ in 0..60 {
1888 world.step(1.0 / 60.0);
1889 }
1890 let angle = world.body(b).unwrap().angle;
1891 assert!(angle.abs() <= PI / 6.0 + 0.2, "cone angle exceeded");
1892 }
1893
1894 #[test]
1895 fn slider_constraint_limits() {
1896 let mut world = ConstraintWorld::new();
1897 world.gravity = Vec2::ZERO;
1898 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1899 let b = world.add_body({
1900 let mut s = BodyState::new(Vec2::new(0.5, 0.0), 1.0, 0.1);
1901 s.velocity = Vec2::new(5.0, 0.0);
1902 s
1903 });
1904 let c = Box::new(
1905 SliderConstraint::new(a, Vec2::ZERO, b, Vec2::ZERO, Vec2::X)
1906 .with_limits(-1.0, 1.0)
1907 );
1908 world.add_constraint(c);
1909 for _ in 0..120 {
1910 world.step(1.0 / 60.0);
1911 }
1912 let pos_x = world.body(b).unwrap().position.x;
1913 assert!(pos_x <= 1.5, "slider should be limited, got x={pos_x}");
1914 }
1915
1916 #[test]
1917 fn weld_constraint_holds() {
1918 let mut world = ConstraintWorld::new();
1919 world.gravity = Vec2::new(0.0, -9.81);
1920 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1921 let b = world.add_body(BodyState::new(Vec2::new(1.0, 0.0), 1.0, 0.1));
1922 let c = Box::new(WeldConstraint::new(a, Vec2::new(1.0, 0.0), b, Vec2::ZERO, 0.0));
1923 world.add_constraint(c);
1924 for _ in 0..60 {
1925 world.step(1.0 / 60.0);
1926 }
1927 let pos = world.body(b).unwrap().position;
1928 assert!((pos - Vec2::new(1.0, 0.0)).length() < 0.5, "weld should hold, drift={}", (pos - Vec2::new(1.0, 0.0)).length());
1929 }
1930
1931 #[test]
1932 fn pulley_constraint_ratio() {
1933 let mut world = ConstraintWorld::new();
1934 world.gravity = Vec2::ZERO;
1935 let a = world.add_body(BodyState::new(Vec2::new(-3.0, 0.0), 1.0, 0.1));
1936 let b = world.add_body(BodyState::new(Vec2::new( 3.0, 0.0), 1.0, 0.1));
1937 let init_la = (Vec2::new(-3.0, 0.0) - Vec2::new(-1.0, 2.0)).length();
1938 let init_lb = (Vec2::new( 3.0, 0.0) - Vec2::new( 1.0, 2.0)).length();
1939 let total = init_la + init_lb;
1940 let c = Box::new(PulleyConstraint::new(
1941 a, Vec2::ZERO, Vec2::new(-1.0, 2.0),
1942 b, Vec2::ZERO, Vec2::new( 1.0, 2.0),
1943 1.0, total,
1944 ));
1945 world.add_constraint(c);
1946 world.step(1.0 / 60.0);
1947 let pa = world.body(a).unwrap().position;
1948 assert!(pa.is_finite(), "pulley should not produce NaN");
1949 }
1950
1951 #[test]
1952 fn spring_constraint_oscillates() {
1953 let mut world = ConstraintWorld::new();
1954 world.gravity = Vec2::ZERO;
1955 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
1956 let b = world.add_body(BodyState::new(Vec2::new(3.0, 0.0), 1.0, 0.1));
1957 let c = Box::new(SpringConstraint::new(a, Vec2::ZERO, b, Vec2::ZERO, 1.0, 50.0, 0.5));
1958 world.add_constraint(c);
1959 let pos_before = world.body(b).unwrap().position.x;
1960 world.step(0.016);
1961 let pos_after = world.body(b).unwrap().position.x;
1962 assert!(pos_after < pos_before, "spring should pull body toward rest length");
1963 }
1964
1965 #[test]
1966 fn motor_constraint_drives_rotation() {
1967 let mut world = ConstraintWorld::new();
1968 world.gravity = Vec2::ZERO;
1969 let a = world.add_body(BodyState::new(Vec2::ZERO, 1.0, 1.0));
1970 let c = Box::new(MotorConstraint::new(a, 5.0, 100.0));
1971 world.add_constraint(c);
1972 for _ in 0..30 {
1973 world.step(1.0 / 60.0);
1974 }
1975 let omega = world.body(a).unwrap().angular_velocity;
1976 assert!(omega > 0.0, "motor should spin body, got {omega}");
1977 }
1978
1979 #[test]
1980 fn gear_constraint_couples_bodies() {
1981 let mut world = ConstraintWorld::new();
1982 world.gravity = Vec2::ZERO;
1983 let a = world.add_body({
1984 let mut s = BodyState::new(Vec2::ZERO, 1.0, 1.0);
1985 s.angular_velocity = 2.0;
1986 s
1987 });
1988 let b = world.add_body(BodyState::new(Vec2::new(2.0, 0.0), 1.0, 1.0));
1989 let c = Box::new(GearConstraint::new(a, b, 2.0));
1990 world.add_constraint(c);
1991 for _ in 0..30 {
1992 world.step(1.0 / 60.0);
1993 }
1994 let oa = world.body(a).unwrap().angular_velocity;
1995 let ob = world.body(b).unwrap().angular_velocity;
1996 let coupling_err = (oa + 2.0 * ob).abs();
1998 assert!(coupling_err < 1.0, "gear coupling error too large: {coupling_err}");
1999 }
2000
2001 #[test]
2002 fn max_distance_constraint_rope() {
2003 let mut world = ConstraintWorld::new();
2004 world.gravity = Vec2::ZERO;
2005 let a = world.add_body(BodyState::static_body(Vec2::ZERO));
2006 let b = world.add_body({
2007 let mut s = BodyState::new(Vec2::new(3.0, 0.0), 1.0, 0.1);
2008 s.velocity = Vec2::new(10.0, 0.0);
2009 s
2010 });
2011 let c = Box::new(MaxDistanceConstraint::new(a, Vec2::ZERO, b, Vec2::ZERO, 2.0));
2012 world.add_constraint(c);
2013 for _ in 0..60 {
2014 world.step(1.0 / 60.0);
2015 }
2016 let dist = world.body(b).unwrap().position.length();
2017 assert!(dist <= 2.5, "rope should limit distance, got {dist}");
2018 }
2019
2020 #[test]
2021 fn pin_constraint_holds_at_world_point() {
2022 let mut world = ConstraintWorld::new();
2023 world.gravity = Vec2::new(0.0, -9.81);
2024 let a = world.add_body(BodyState::new(Vec2::new(0.0, 5.0), 1.0, 0.1));
2025 let target = Vec2::new(0.0, 5.0);
2026 let c = Box::new(PinConstraint::new(a, Vec2::ZERO, target));
2027 world.add_constraint(c);
2028 for _ in 0..60 {
2029 world.step(1.0 / 60.0);
2030 }
2031 let pos = world.body(a).unwrap().position;
2032 let err = (pos - target).length();
2033 assert!(err < 0.5, "pin should hold, error={err}");
2034 }
2035
2036 #[test]
2037 fn constraint_world_gravity_applied() {
2038 let mut world = ConstraintWorld::new();
2039 let a = world.add_body(BodyState::new(Vec2::new(0.0, 10.0), 1.0, 0.1));
2040 world.step(1.0);
2041 let pos = world.body(a).unwrap().position;
2042 assert!(pos.y < 10.0, "gravity should pull body down");
2043 }
2044
2045 #[test]
2046 fn island_detection_finds_connected_components() {
2047 let mut world = ConstraintWorld::new();
2048 let a = world.add_body(BodyState::new(Vec2::ZERO, 1.0, 0.1));
2049 let b = world.add_body(BodyState::new(Vec2::new(1.0, 0.0), 1.0, 0.1));
2050 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)));
2052 let islands = detect_islands(&world.bodies, &world.constraints);
2053 assert_eq!(islands.len(), 2, "should detect 2 islands");
2055 }
2056
2057 #[test]
2058 fn contact_constraint_non_penetration() {
2059 let mut bodies: HashMap<BodyHandle, BodyState> = HashMap::new();
2060 let h_a = BodyHandle(1);
2061 let h_b = BodyHandle(2);
2062 let mut ba = BodyState::new(Vec2::ZERO, 1.0, 0.1);
2063 let mut bb = BodyState::new(Vec2::new(0.5, 0.0), 1.0, 0.1);
2064 ba.velocity = Vec2::new(1.0, 0.0);
2065 bb.velocity = Vec2::new(-1.0, 0.0);
2066 bodies.insert(h_a, ba);
2067 bodies.insert(h_b, bb);
2068 let mut c = ContactConstraint::new(
2069 h_a, h_b,
2070 Vec2::new(0.25, 0.0), Vec2::X, 0.5,
2071 0.3, 0.5,
2072 );
2073 c.prepare(&bodies, 1.0 / 60.0);
2074 c.solve_contact(&mut bodies, 1.0 / 60.0);
2075 let va = bodies[&h_a].velocity.x;
2076 let vb = bodies[&h_b].velocity.x;
2077 assert!(vb >= va - 0.01, "relative velocity after impulse should be non-negative");
2078 }
2079
2080 #[test]
2081 fn total_kinetic_energy_is_finite() {
2082 let mut world = ConstraintWorld::new();
2083 for i in 0..10 {
2084 let s = BodyState::new(Vec2::new(i as f32, 0.0), 1.0, 0.1);
2085 world.add_body(s);
2086 }
2087 for _ in 0..60 {
2088 world.step(1.0 / 60.0);
2089 }
2090 let ke = world.total_kinetic_energy();
2091 assert!(ke.is_finite(), "KE should be finite");
2092 }
2093}