Skip to main content

arcane_engine/physics/
constraints.rs

1use super::types::{BodyType, Constraint, RigidBody};
2
3/// Solve all constraints for this timestep.
4pub fn solve_constraints(
5    bodies: &mut [Option<RigidBody>],
6    constraints: &[Constraint],
7    _dt: f32,
8) {
9    for constraint in constraints {
10        match constraint {
11            Constraint::Distance {
12                body_a,
13                body_b,
14                distance,
15                anchor_a,
16                anchor_b,
17                ..
18            } => solve_distance(bodies, *body_a, *body_b, *distance, *anchor_a, *anchor_b),
19            Constraint::Revolute {
20                body_a,
21                body_b,
22                pivot,
23                ..
24            } => solve_revolute(bodies, *body_a, *body_b, *pivot),
25        }
26    }
27}
28
29fn solve_distance(
30    bodies: &mut [Option<RigidBody>],
31    id_a: u32,
32    id_b: u32,
33    target_distance: f32,
34    anchor_a: (f32, f32),
35    anchor_b: (f32, f32),
36) {
37    let a_idx = id_a as usize;
38    let b_idx = id_b as usize;
39
40    let (xa, ya, cos_a, sin_a, inv_ma, type_a) = match &bodies[a_idx] {
41        Some(b) => (b.x, b.y, b.angle.cos(), b.angle.sin(), b.inv_mass, b.body_type),
42        None => return,
43    };
44    let (xb, yb, cos_b, sin_b, inv_mb, type_b) = match &bodies[b_idx] {
45        Some(b) => (b.x, b.y, b.angle.cos(), b.angle.sin(), b.inv_mass, b.body_type),
46        None => return,
47    };
48
49    if type_a != BodyType::Dynamic && type_b != BodyType::Dynamic {
50        return;
51    }
52
53    // World-space anchor positions
54    let wa_x = xa + anchor_a.0 * cos_a - anchor_a.1 * sin_a;
55    let wa_y = ya + anchor_a.0 * sin_a + anchor_a.1 * cos_a;
56    let wb_x = xb + anchor_b.0 * cos_b - anchor_b.1 * sin_b;
57    let wb_y = yb + anchor_b.0 * sin_b + anchor_b.1 * cos_b;
58
59    let dx = wb_x - wa_x;
60    let dy = wb_y - wa_y;
61    let current_distance = (dx * dx + dy * dy).sqrt();
62
63    if current_distance < 1e-8 {
64        return;
65    }
66
67    let nx = dx / current_distance;
68    let ny = dy / current_distance;
69    let error = current_distance - target_distance;
70
71    let inv_total = inv_ma + inv_mb;
72    if inv_total == 0.0 {
73        return;
74    }
75
76    let correction = error / inv_total;
77
78    if let Some(a) = &mut bodies[a_idx] {
79        if a.body_type == BodyType::Dynamic {
80            a.x += correction * inv_ma * nx;
81            a.y += correction * inv_ma * ny;
82        }
83    }
84    if let Some(b) = &mut bodies[b_idx] {
85        if b.body_type == BodyType::Dynamic {
86            b.x -= correction * inv_mb * nx;
87            b.y -= correction * inv_mb * ny;
88        }
89    }
90}
91
92fn solve_revolute(
93    bodies: &mut [Option<RigidBody>],
94    id_a: u32,
95    id_b: u32,
96    pivot: (f32, f32),
97) {
98    let a_idx = id_a as usize;
99    let b_idx = id_b as usize;
100
101    let (xa, ya, inv_ma, type_a) = match &bodies[a_idx] {
102        Some(b) => (b.x, b.y, b.inv_mass, b.body_type),
103        None => return,
104    };
105    let (xb, yb, inv_mb, type_b) = match &bodies[b_idx] {
106        Some(b) => (b.x, b.y, b.inv_mass, b.body_type),
107        None => return,
108    };
109
110    if type_a != BodyType::Dynamic && type_b != BodyType::Dynamic {
111        return;
112    }
113
114    // For revolute: both bodies should have the same world-space pivot point.
115    // Compute error: difference between where each body thinks the pivot is.
116    // For simplicity, use pivot as a world-space anchor that both bodies must share.
117    let mid_x = (xa + xb) * 0.5;
118    let mid_y = (ya + yb) * 0.5;
119    let _ = mid_x;
120    let _ = mid_y;
121
122    // Error from body A to pivot
123    let err_ax = pivot.0 - xa;
124    let err_ay = pivot.1 - ya;
125    // Error from body B to pivot
126    let err_bx = pivot.0 - xb;
127    let err_by = pivot.1 - yb;
128
129    let inv_total = inv_ma + inv_mb;
130    if inv_total == 0.0 {
131        return;
132    }
133
134    // Apply position correction toward the pivot
135    // Each body moves proportional to its inverse mass
136    let _ = err_ax;
137    let _ = err_ay;
138    let _ = err_bx;
139    let _ = err_by;
140
141    // Compute where each body's pivot would be in world space
142    // For simplicity, the "local" pivot offsets are stored relative to body position at constraint creation
143    // Re-derive: the pivot should remain at the same world position
144    // We push both bodies so that both are at the distance implied by the pivot
145    // Body A should be at (pivot.0 - local_offset_a), etc.
146    // Since we don't store local offsets, treat pivot as a fixed world point that both bodies are pinned to.
147
148    // Correction: move each body partially toward making their center go through appropriate offset
149    // Actually, the simplest revolute: both bodies' positions are free, but they share a point.
150    // Here we just correct velocities to prevent separation at the pivot.
151    // A better approach: store local anchors. For now, apply direct position correction.
152
153    // Desired: anchor_a_world == anchor_b_world == pivot
154    // Since anchor is at body center (0,0): body.pos == pivot for each
155    // This doesn't make physical sense for general revolute. Use anchor-based approach:
156
157    // With no local anchors, revolute just pins both bodies at the same point (the pivot).
158    // Correction = move both bodies toward pivot proportional to inverse mass.
159    if let Some(a) = &mut bodies[a_idx] {
160        if a.body_type == BodyType::Dynamic {
161            let dx = pivot.0 - a.x;
162            let dy = pivot.1 - a.y;
163            a.x += dx * inv_ma / inv_total;
164            a.y += dy * inv_ma / inv_total;
165        }
166    }
167    if let Some(b) = &mut bodies[b_idx] {
168        if b.body_type == BodyType::Dynamic {
169            let dx = pivot.0 - b.x;
170            let dy = pivot.1 - b.y;
171            b.x += dx * inv_mb / inv_total;
172            b.y += dy * inv_mb / inv_total;
173        }
174    }
175}