Skip to main content

arcane_engine/physics/
world.rs

1use std::collections::HashMap;
2
3use super::broadphase::SpatialHash;
4use super::constraints::solve_constraints;
5use super::integrate::integrate;
6use super::narrowphase::test_collision;
7use super::resolve::{initialize_contacts, resolve_contacts_position, resolve_contacts_velocity_iteration, warm_start_contacts};
8use super::sleep::update_sleep;
9use super::types::*;
10
11pub struct PhysicsWorld {
12    bodies: Vec<Option<RigidBody>>,
13    free_ids: Vec<BodyId>,
14    next_id: BodyId,
15    constraints: Vec<Constraint>,
16    next_constraint_id: ConstraintId,
17    gravity: (f32, f32),
18    fixed_dt: f32,
19    accumulator: f32,
20    contacts: Vec<Contact>,
21    broadphase: SpatialHash,
22    solver_iterations: usize,
23    /// Warm-start cache: maps (body_a, body_b) → (normal_impulse, friction_impulse)
24    warm_cache: HashMap<(BodyId, BodyId), (f32, f32)>,
25}
26
27impl PhysicsWorld {
28    pub fn new(gravity_x: f32, gravity_y: f32) -> Self {
29        Self {
30            bodies: Vec::new(),
31            free_ids: Vec::new(),
32            next_id: 0,
33            constraints: Vec::new(),
34            next_constraint_id: 0,
35            gravity: (gravity_x, gravity_y),
36            fixed_dt: 1.0 / 60.0,
37            accumulator: 0.0,
38            contacts: Vec::new(),
39            broadphase: SpatialHash::new(64.0),
40            solver_iterations: 10,
41            warm_cache: HashMap::new(),
42        }
43    }
44
45    /// Fixed-timestep physics step. Accumulates dt and runs sub-steps as needed.
46    pub fn step(&mut self, dt: f32) {
47        self.accumulator += dt;
48
49        while self.accumulator >= self.fixed_dt {
50            self.sub_step(self.fixed_dt);
51            self.accumulator -= self.fixed_dt;
52        }
53    }
54
55    fn sub_step(&mut self, dt: f32) {
56        // 1. Integrate
57        for body in self.bodies.iter_mut().flatten() {
58            integrate(body, self.gravity.0, self.gravity.1, dt);
59        }
60
61        // 2. Broadphase
62        self.broadphase.clear();
63        for body in self.bodies.iter().flatten() {
64            let (min_x, min_y, max_x, max_y) = get_shape_aabb(body);
65            self.broadphase.insert(body.id, min_x, min_y, max_x, max_y);
66        }
67        let pairs = self.broadphase.get_pairs();
68
69        // 3. Narrowphase
70        self.contacts.clear();
71        for (id_a, id_b) in pairs {
72            let a_idx = id_a as usize;
73            let b_idx = id_b as usize;
74
75            // Layer/mask filtering
76            let (layer_a, mask_a, sleeping_a) = match &self.bodies[a_idx] {
77                Some(b) => (b.layer, b.mask, b.sleeping),
78                None => continue,
79            };
80            let (layer_b, mask_b, sleeping_b) = match &self.bodies[b_idx] {
81                Some(b) => (b.layer, b.mask, b.sleeping),
82                None => continue,
83            };
84
85            // Both layers must pass each other's masks
86            if (layer_a & mask_b) == 0 || (layer_b & mask_a) == 0 {
87                continue;
88            }
89
90            // Skip if both sleeping
91            if sleeping_a && sleeping_b {
92                continue;
93            }
94
95            // We need to borrow both bodies immutably. Use index-based access.
96            let body_a = self.bodies[a_idx].as_ref().unwrap();
97            let body_b = self.bodies[b_idx].as_ref().unwrap();
98
99            if let Some(contact) = test_collision(body_a, body_b) {
100                self.contacts.push(contact);
101            }
102        }
103
104        // 3b. Sort contacts bottom-up for stack stability.
105        self.contacts.sort_by(|a, b| {
106            let ay = self.bodies[a.body_a as usize]
107                .as_ref()
108                .map_or(0.0f32, |body| body.y)
109                .max(
110                    self.bodies[a.body_b as usize]
111                        .as_ref()
112                        .map_or(0.0f32, |body| body.y),
113                );
114            let by = self.bodies[b.body_a as usize]
115                .as_ref()
116                .map_or(0.0f32, |body| body.y)
117                .max(
118                    self.bodies[b.body_b as usize]
119                        .as_ref()
120                        .map_or(0.0f32, |body| body.y),
121                );
122            by.partial_cmp(&ay).unwrap_or(std::cmp::Ordering::Equal)
123        });
124
125        // 3c. Pre-compute velocity bias (restitution) and tangent direction for each contact.
126        // Must happen BEFORE warm start so bias reflects post-integration velocities.
127        let gravity_mag = (self.gravity.0 * self.gravity.0 + self.gravity.1 * self.gravity.1).sqrt();
128        let restitution_threshold = gravity_mag * dt * 1.5;
129        initialize_contacts(&self.bodies, &mut self.contacts, restitution_threshold);
130
131        // 3d. Warm start: initialize accumulated impulses from previous frame's cache
132        for contact in &mut self.contacts {
133            let key = (contact.body_a.min(contact.body_b), contact.body_a.max(contact.body_b));
134            if let Some(&(jn, jt)) = self.warm_cache.get(&key) {
135                // Scale slightly to avoid overshoot when contacts shift between frames.
136                // Island-based sleeping prevents the cascade that made 0.95 problematic
137                // with per-body sleeping (no body sleeps until the whole stack settles).
138                contact.accumulated_jn = jn * 0.95;
139                contact.accumulated_jt = jt * 0.95;
140            }
141        }
142        warm_start_contacts(&mut self.bodies, &self.contacts);
143
144        // 4. Velocity solve (contacts + constraints)
145        for i in 0..self.solver_iterations {
146            let reverse = i % 2 == 1;
147            resolve_contacts_velocity_iteration(&mut self.bodies, &mut self.contacts, reverse);
148            solve_constraints(&mut self.bodies, &self.constraints, dt);
149        }
150
151        // 4b. Save accumulated impulses to warm cache for next frame
152        self.warm_cache.clear();
153        for contact in &self.contacts {
154            let key = (contact.body_a.min(contact.body_b), contact.body_a.max(contact.body_b));
155            self.warm_cache.insert(key, (contact.accumulated_jn, contact.accumulated_jt));
156        }
157
158        // 5. Position correction (multiple iterations with fresh collision re-check)
159        for i in 0..4 {
160            for contact in &mut self.contacts {
161                let a = &self.bodies[contact.body_a as usize];
162                let b = &self.bodies[contact.body_b as usize];
163                if let (Some(a), Some(b)) = (a, b) {
164                    if let Some(fresh) = test_collision(a, b) {
165                        contact.penetration = fresh.penetration;
166                        contact.normal = fresh.normal;
167                        contact.contact_point = fresh.contact_point;
168                    } else {
169                        contact.penetration = 0.0;
170                    }
171                }
172            }
173            resolve_contacts_position(&mut self.bodies, &self.contacts, i % 2 == 1);
174        }
175
176        // 6. Update sleep
177        update_sleep(&mut self.bodies, &self.contacts, dt);
178    }
179
180    pub fn add_body(
181        &mut self,
182        body_type: BodyType,
183        shape: Shape,
184        x: f32,
185        y: f32,
186        mass: f32,
187        material: Material,
188        layer: u16,
189        mask: u16,
190    ) -> BodyId {
191        let id = if let Some(recycled) = self.free_ids.pop() {
192            recycled
193        } else {
194            let id = self.next_id;
195            self.next_id += 1;
196            id
197        };
198
199        let (inv_mass, inertia, inv_inertia) = compute_mass_and_inertia(&shape, mass, body_type);
200
201        let body = RigidBody {
202            id,
203            body_type,
204            shape,
205            material,
206            x,
207            y,
208            angle: 0.0,
209            vx: 0.0,
210            vy: 0.0,
211            angular_velocity: 0.0,
212            fx: 0.0,
213            fy: 0.0,
214            torque: 0.0,
215            mass,
216            inv_mass,
217            inertia,
218            inv_inertia,
219            layer,
220            mask,
221            sleeping: false,
222            sleep_timer: 0.0,
223        };
224
225        let idx = id as usize;
226        if idx >= self.bodies.len() {
227            self.bodies.resize_with(idx + 1, || None);
228        }
229        self.bodies[idx] = Some(body);
230        id
231    }
232
233    pub fn remove_body(&mut self, id: BodyId) {
234        let idx = id as usize;
235        if idx < self.bodies.len() {
236            self.bodies[idx] = None;
237            self.free_ids.push(id);
238        }
239    }
240
241    pub fn get_body(&self, id: BodyId) -> Option<&RigidBody> {
242        self.bodies.get(id as usize)?.as_ref()
243    }
244
245    pub fn get_body_mut(&mut self, id: BodyId) -> Option<&mut RigidBody> {
246        self.bodies.get_mut(id as usize)?.as_mut()
247    }
248
249    pub fn set_velocity(&mut self, id: BodyId, vx: f32, vy: f32) {
250        if let Some(body) = self.get_body_mut(id) {
251            body.vx = vx;
252            body.vy = vy;
253            body.sleeping = false;
254            body.sleep_timer = 0.0;
255        }
256    }
257
258    pub fn set_angular_velocity(&mut self, id: BodyId, av: f32) {
259        if let Some(body) = self.get_body_mut(id) {
260            body.angular_velocity = av;
261            body.sleeping = false;
262            body.sleep_timer = 0.0;
263        }
264    }
265
266    pub fn apply_force(&mut self, id: BodyId, fx: f32, fy: f32) {
267        if let Some(body) = self.get_body_mut(id) {
268            body.fx += fx;
269            body.fy += fy;
270            body.sleeping = false;
271            body.sleep_timer = 0.0;
272        }
273    }
274
275    pub fn apply_impulse(&mut self, id: BodyId, ix: f32, iy: f32) {
276        if let Some(body) = self.get_body_mut(id) {
277            body.vx += ix * body.inv_mass;
278            body.vy += iy * body.inv_mass;
279            body.sleeping = false;
280            body.sleep_timer = 0.0;
281        }
282    }
283
284    pub fn set_position(&mut self, id: BodyId, x: f32, y: f32) {
285        if let Some(body) = self.get_body_mut(id) {
286            body.x = x;
287            body.y = y;
288            body.sleeping = false;
289            body.sleep_timer = 0.0;
290        }
291    }
292
293    pub fn set_collision_layers(&mut self, id: BodyId, layer: u16, mask: u16) {
294        if let Some(body) = self.get_body_mut(id) {
295            body.layer = layer;
296            body.mask = mask;
297        }
298    }
299
300    pub fn add_constraint(&mut self, constraint: Constraint) -> ConstraintId {
301        let id = self.next_constraint_id;
302        self.next_constraint_id += 1;
303
304        let constraint = match constraint {
305            Constraint::Distance {
306                body_a,
307                body_b,
308                distance,
309                anchor_a,
310                anchor_b,
311                ..
312            } => Constraint::Distance {
313                id,
314                body_a,
315                body_b,
316                distance,
317                anchor_a,
318                anchor_b,
319            },
320            Constraint::Revolute {
321                body_a,
322                body_b,
323                pivot,
324                ..
325            } => Constraint::Revolute {
326                id,
327                body_a,
328                body_b,
329                pivot,
330            },
331        };
332        self.constraints.push(constraint);
333        id
334    }
335
336    pub fn remove_constraint(&mut self, id: ConstraintId) {
337        self.constraints.retain(|c| c.id() != id);
338    }
339
340    pub fn query_aabb(&self, min_x: f32, min_y: f32, max_x: f32, max_y: f32) -> Vec<BodyId> {
341        let mut result = Vec::new();
342        for body in self.bodies.iter().flatten() {
343            let (bmin_x, bmin_y, bmax_x, bmax_y) = get_shape_aabb(body);
344            if bmax_x >= min_x && bmin_x <= max_x && bmax_y >= min_y && bmin_y <= max_y {
345                result.push(body.id);
346            }
347        }
348        result
349    }
350
351    pub fn raycast(
352        &self,
353        ox: f32,
354        oy: f32,
355        dx: f32,
356        dy: f32,
357        max_dist: f32,
358    ) -> Option<(BodyId, f32, f32, f32)> {
359        let dir_len = (dx * dx + dy * dy).sqrt();
360        if dir_len < 1e-8 {
361            return None;
362        }
363        let ndx = dx / dir_len;
364        let ndy = dy / dir_len;
365
366        let mut closest: Option<(BodyId, f32, f32, f32)> = None;
367
368        for body in self.bodies.iter().flatten() {
369            let t = match &body.shape {
370                Shape::Circle { radius } => {
371                    ray_vs_circle(ox, oy, ndx, ndy, body.x, body.y, *radius)
372                }
373                Shape::AABB { half_w, half_h } => {
374                    ray_vs_aabb(ox, oy, ndx, ndy, body.x, body.y, *half_w, *half_h)
375                }
376                Shape::Polygon { vertices } => {
377                    ray_vs_polygon(ox, oy, ndx, ndy, body, vertices)
378                }
379            };
380
381            if let Some(t) = t {
382                if t >= 0.0 && t <= max_dist {
383                    let hit_x = ox + ndx * t;
384                    let hit_y = oy + ndy * t;
385                    if closest.is_none() || t < closest.unwrap().3 {
386                        closest = Some((body.id, hit_x, hit_y, t));
387                    }
388                }
389            }
390        }
391        closest
392    }
393
394    pub fn get_contacts(&self) -> &[Contact] {
395        &self.contacts
396    }
397
398    /// Return all active (non-None) bodies.
399    pub fn all_bodies(&self) -> Vec<&RigidBody> {
400        self.bodies.iter().filter_map(|b| b.as_ref()).collect()
401    }
402
403    /// Return the world gravity.
404    pub fn gravity(&self) -> (f32, f32) {
405        self.gravity
406    }
407
408    /// Return the number of active bodies.
409    pub fn body_count(&self) -> usize {
410        self.bodies.iter().filter(|b| b.is_some()).count()
411    }
412}
413
414fn ray_vs_circle(
415    ox: f32, oy: f32,
416    dx: f32, dy: f32,
417    cx: f32, cy: f32,
418    radius: f32,
419) -> Option<f32> {
420    let fx = ox - cx;
421    let fy = oy - cy;
422    let a = dx * dx + dy * dy;
423    let b = 2.0 * (fx * dx + fy * dy);
424    let c = fx * fx + fy * fy - radius * radius;
425    let discriminant = b * b - 4.0 * a * c;
426    if discriminant < 0.0 {
427        return None;
428    }
429    let sqrt_d = discriminant.sqrt();
430    let t1 = (-b - sqrt_d) / (2.0 * a);
431    let t2 = (-b + sqrt_d) / (2.0 * a);
432    if t1 >= 0.0 {
433        Some(t1)
434    } else if t2 >= 0.0 {
435        Some(t2)
436    } else {
437        None
438    }
439}
440
441fn ray_vs_aabb(
442    ox: f32, oy: f32,
443    dx: f32, dy: f32,
444    cx: f32, cy: f32,
445    hw: f32, hh: f32,
446) -> Option<f32> {
447    let min_x = cx - hw;
448    let max_x = cx + hw;
449    let min_y = cy - hh;
450    let max_y = cy + hh;
451
452    let (mut tmin, mut tmax) = if dx.abs() < 1e-8 {
453        if ox < min_x || ox > max_x {
454            return None;
455        }
456        (f32::MIN, f32::MAX)
457    } else {
458        let inv_dx = 1.0 / dx;
459        let t1 = (min_x - ox) * inv_dx;
460        let t2 = (max_x - ox) * inv_dx;
461        (t1.min(t2), t1.max(t2))
462    };
463
464    let (tymin, tymax) = if dy.abs() < 1e-8 {
465        if oy < min_y || oy > max_y {
466            return None;
467        }
468        (f32::MIN, f32::MAX)
469    } else {
470        let inv_dy = 1.0 / dy;
471        let t1 = (min_y - oy) * inv_dy;
472        let t2 = (max_y - oy) * inv_dy;
473        (t1.min(t2), t1.max(t2))
474    };
475
476    tmin = tmin.max(tymin);
477    tmax = tmax.min(tymax);
478
479    if tmin > tmax || tmax < 0.0 {
480        return None;
481    }
482
483    Some(if tmin >= 0.0 { tmin } else { tmax })
484}
485
486fn ray_vs_polygon(
487    ox: f32, oy: f32,
488    dx: f32, dy: f32,
489    body: &RigidBody,
490    vertices: &[(f32, f32)],
491) -> Option<f32> {
492    let cos = body.angle.cos();
493    let sin = body.angle.sin();
494    let n = vertices.len();
495    if n < 3 {
496        return None;
497    }
498
499    let mut closest_t: Option<f32> = None;
500
501    for i in 0..n {
502        let (vx0, vy0) = vertices[i];
503        let (vx1, vy1) = vertices[(i + 1) % n];
504
505        // Transform to world space
506        let ax = vx0 * cos - vy0 * sin + body.x;
507        let ay = vx0 * sin + vy0 * cos + body.y;
508        let bx = vx1 * cos - vy1 * sin + body.x;
509        let by = vx1 * sin + vy1 * cos + body.y;
510
511        if let Some(t) = ray_vs_segment(ox, oy, dx, dy, ax, ay, bx, by) {
512            if closest_t.is_none() || t < closest_t.unwrap() {
513                closest_t = Some(t);
514            }
515        }
516    }
517    closest_t
518}
519
520fn ray_vs_segment(
521    ox: f32, oy: f32,
522    dx: f32, dy: f32,
523    ax: f32, ay: f32,
524    bx: f32, by: f32,
525) -> Option<f32> {
526    let ex = bx - ax;
527    let ey = by - ay;
528    let denom = dx * ey - dy * ex;
529    if denom.abs() < 1e-8 {
530        return None;
531    }
532    let t = ((ax - ox) * ey - (ay - oy) * ex) / denom;
533    let u = ((ax - ox) * dy - (ay - oy) * dx) / denom;
534    if t >= 0.0 && u >= 0.0 && u <= 1.0 {
535        Some(t)
536    } else {
537        None
538    }
539}