shadowengine2d/
physics.rs

1/*
2 * MIT License
3 * 
4 * Copyright (c) 2025 ShadowEngine2D
5 * 
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 * 
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 * 
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25use crate::math::{Position, Size, Vec2};
26use crate::entity::EntityId;
27use crate::EngineResult;
28use serde::{Serialize, Deserialize};
29use std::collections::HashMap;
30use parry2d::{
31    shape::{Cuboid, Ball, SharedShape},
32    math::{Point, Vector},
33    query::Ray,
34};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum CollisionShape {
38    Rectangle,
39    Circle,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct RigidBody {
44    pub position: Position,
45    pub velocity: Vec2,
46    pub acceleration: Vec2,
47    pub mass: f32,
48    pub is_static: bool,
49    pub drag: f32,
50    pub bounce: f32,
51}
52
53impl Default for RigidBody {
54    fn default() -> Self {
55        Self {
56            position: Position::new(0.0, 0.0),
57            velocity: Vec2::new(0.0, 0.0),
58            acceleration: Vec2::new(0.0, 0.0),
59            mass: 1.0,
60            is_static: false,
61            drag: 0.98,
62            bounce: 0.5,
63        }
64    }
65}
66
67impl RigidBody {
68    pub fn new() -> Self {
69        Default::default()
70    }
71
72    pub fn with_mass(mut self, mass: f32) -> Self {
73        self.mass = mass.max(0.001);
74        self
75    }
76
77    pub fn with_position(mut self, position: Position) -> Self {
78        self.position = position;
79        self
80    }
81
82    pub fn with_velocity(mut self, velocity: Vec2) -> Self {
83        self.velocity = velocity;
84        self
85    }
86
87    pub fn static_body(mut self) -> Self {
88        self.is_static = true;
89        self.mass = f32::INFINITY;
90        self
91    }
92
93    pub fn apply_force(&mut self, force: Vec2) {
94        if !self.is_static {
95            self.acceleration += force / self.mass;
96        }
97    }
98
99    pub fn apply_impulse(&mut self, impulse: Vec2) {
100        if !self.is_static {
101            self.velocity += impulse / self.mass;
102        }
103    }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct Collider {
108    pub shape: CollisionShape,
109    pub size: Size,
110    pub offset: Vec2,
111    pub is_trigger: bool,
112    pub collision_layers: u32,
113    pub collision_mask: u32,
114}
115
116impl Default for Collider {
117    fn default() -> Self {
118        Self {
119            shape: CollisionShape::Rectangle,
120            size: Size::new(32.0, 32.0),
121            offset: Vec2::new(0.0, 0.0),
122            is_trigger: false,
123            collision_layers: 1,
124            collision_mask: u32::MAX,
125        }
126    }
127}
128
129impl Collider {
130    pub fn rectangle(width: f32, height: f32) -> Self {
131        Self {
132            shape: CollisionShape::Rectangle,
133            size: Size::new(width, height),
134            ..Default::default()
135        }
136    }
137
138    pub fn circle(radius: f32) -> Self {
139        Self {
140            shape: CollisionShape::Circle,
141            size: Size::new(radius * 2.0, radius * 2.0),
142            ..Default::default()
143        }
144    }
145
146    pub fn trigger(mut self) -> Self {
147        self.is_trigger = true;
148        self
149    }
150
151    pub fn with_layers(mut self, layers: u32) -> Self {
152        self.collision_layers = layers;
153        self
154    }
155
156    pub fn with_mask(mut self, mask: u32) -> Self {
157        self.collision_mask = mask;
158        self
159    }
160}
161
162#[derive(Debug, Clone)]
163pub struct CollisionEvent {
164    pub entity_a: EntityId,
165    pub entity_b: EntityId,
166    pub contact_point: Position,
167    pub normal: Vec2,
168    pub penetration: f32,
169}
170
171pub struct PhysicsWorld {
172    gravity: Vec2,
173    rigid_bodies: HashMap<EntityId, RigidBody>,
174    colliders: HashMap<EntityId, Collider>,
175    collision_events: Vec<CollisionEvent>,
176    time_accumulator: f32,
177    fixed_timestep: f32,
178}
179
180impl Default for PhysicsWorld {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186impl PhysicsWorld {
187    pub fn new() -> Self {
188        Self {
189            gravity: Vec2::new(0.0, -9.81 * 100.0),
190            rigid_bodies: HashMap::new(),
191            colliders: HashMap::new(),
192            collision_events: Vec::new(),
193            time_accumulator: 0.0,
194            fixed_timestep: 1.0 / 60.0,
195        }
196    }
197
198    pub fn set_gravity(&mut self, gravity: Vec2) {
199        self.gravity = gravity;
200    }
201
202    pub fn add_rigid_body(&mut self, entity: EntityId, body: RigidBody) {
203        self.rigid_bodies.insert(entity, body);
204    }
205
206    pub fn add_collider(&mut self, entity: EntityId, collider: Collider) {
207        self.colliders.insert(entity, collider);
208    }
209
210    pub fn get_rigid_body(&self, entity: EntityId) -> Option<&RigidBody> {
211        self.rigid_bodies.get(&entity)
212    }
213
214    pub fn get_rigid_body_mut(&mut self, entity: EntityId) -> Option<&mut RigidBody> {
215        self.rigid_bodies.get_mut(&entity)
216    }
217
218    pub fn get_collider(&self, entity: EntityId) -> Option<&Collider> {
219        self.colliders.get(&entity)
220    }
221
222    pub fn remove_entity(&mut self, entity: EntityId) {
223        self.rigid_bodies.remove(&entity);
224        self.colliders.remove(&entity);
225    }
226
227    pub fn update(&mut self, delta_time: f32) -> EngineResult<()> {
228        self.time_accumulator += delta_time;
229        self.collision_events.clear();
230
231        while self.time_accumulator >= self.fixed_timestep {
232            self.physics_step(self.fixed_timestep)?;
233            self.time_accumulator -= self.fixed_timestep;
234        }
235
236        Ok(())
237    }
238
239    fn physics_step(&mut self, dt: f32) -> EngineResult<()> {
240
241        for (_, body) in self.rigid_bodies.iter_mut() {
242            if !body.is_static {
243
244                body.acceleration += self.gravity;
245
246                body.velocity += body.acceleration * dt;
247                body.velocity *= body.drag;
248
249                body.position.x += body.velocity.x * dt;
250                body.position.y += body.velocity.y * dt;
251
252                body.acceleration = Vec2::new(0.0, 0.0);
253            }
254        }
255
256        self.detect_collisions()?;
257
258        Ok(())
259    }
260
261    fn detect_collisions(&mut self) -> EngineResult<()> {
262        let entities: Vec<EntityId> = self.colliders.keys().copied().collect();
263        
264        for i in 0..entities.len() {
265            for j in (i + 1)..entities.len() {
266                let entity_a = entities[i];
267                let entity_b = entities[j];
268                
269                let collider_a = self.colliders.get(&entity_a).unwrap().clone();
270                let collider_b = self.colliders.get(&entity_b).unwrap().clone();
271
272                if (collider_a.collision_layers & collider_b.collision_mask) == 0 ||
273                   (collider_b.collision_layers & collider_a.collision_mask) == 0 {
274                    continue;
275                }
276                
277                if let (Some(body_a), Some(body_b)) = (
278                    self.rigid_bodies.get(&entity_a),
279                    self.rigid_bodies.get(&entity_b)
280                ) {
281                    if let Some(collision) = self.check_collision(
282                        entity_a, &body_a.position, &collider_a,
283                        entity_b, &body_b.position, &collider_b
284                    ) {
285                        self.collision_events.push(collision.clone());
286
287                        if !collider_a.is_trigger && !collider_b.is_trigger {
288                            self.resolve_collision(collision);
289                        }
290                    }
291                }
292            }
293        }
294        
295        Ok(())
296    }
297
298    fn check_collision(
299        &self,
300        entity_a: EntityId,
301        pos_a: &Position,
302        collider_a: &Collider,
303        entity_b: EntityId,
304        pos_b: &Position,
305        collider_b: &Collider,
306    ) -> Option<CollisionEvent> {
307        match (collider_a.shape, collider_b.shape) {
308            (CollisionShape::Rectangle, CollisionShape::Rectangle) => {
309                self.check_aabb_collision(entity_a, pos_a, collider_a, entity_b, pos_b, collider_b)
310            }
311            (CollisionShape::Circle, CollisionShape::Circle) => {
312                self.check_circle_collision(entity_a, pos_a, collider_a, entity_b, pos_b, collider_b)
313            }
314            _ => {
315
316                self.check_aabb_collision(entity_a, pos_a, collider_a, entity_b, pos_b, collider_b)
317            }
318        }
319    }
320
321    fn check_aabb_collision(
322        &self,
323        entity_a: EntityId,
324        pos_a: &Position,
325        collider_a: &Collider,
326        entity_b: EntityId,
327        pos_b: &Position,
328        collider_b: &Collider,
329    ) -> Option<CollisionEvent> {
330        let a_min_x = pos_a.x + collider_a.offset.x - collider_a.size.x / 2.0;
331        let a_max_x = pos_a.x + collider_a.offset.x + collider_a.size.x / 2.0;
332        let a_min_y = pos_a.y + collider_a.offset.y - collider_a.size.y / 2.0;
333        let a_max_y = pos_a.y + collider_a.offset.y + collider_a.size.y / 2.0;
334
335        let b_min_x = pos_b.x + collider_b.offset.x - collider_b.size.x / 2.0;
336        let b_max_x = pos_b.x + collider_b.offset.x + collider_b.size.x / 2.0;
337        let b_min_y = pos_b.y + collider_b.offset.y - collider_b.size.y / 2.0;
338        let b_max_y = pos_b.y + collider_b.offset.y + collider_b.size.y / 2.0;
339
340        if a_max_x >= b_min_x && a_min_x <= b_max_x && a_max_y >= b_min_y && a_min_y <= b_max_y {
341            let overlap_x = (a_max_x - b_min_x).min(b_max_x - a_min_x);
342            let overlap_y = (a_max_y - b_min_y).min(b_max_y - a_min_y);
343
344            let (normal, penetration) = if overlap_x < overlap_y {
345                let normal_x = if pos_a.x < pos_b.x { -1.0 } else { 1.0 };
346                (Vec2::new(normal_x, 0.0), overlap_x)
347            } else {
348                let normal_y = if pos_a.y < pos_b.y { -1.0 } else { 1.0 };
349                (Vec2::new(0.0, normal_y), overlap_y)
350            };
351
352            Some(CollisionEvent {
353                entity_a,
354                entity_b,
355                contact_point: Position::new(
356                    (pos_a.x + pos_b.x) / 2.0,
357                    (pos_a.y + pos_b.y) / 2.0,
358                ),
359                normal,
360                penetration,
361            })
362        } else {
363            None
364        }
365    }
366
367    fn check_circle_collision(
368        &self,
369        entity_a: EntityId,
370        pos_a: &Position,
371        collider_a: &Collider,
372        entity_b: EntityId,
373        pos_b: &Position,
374        collider_b: &Collider,
375    ) -> Option<CollisionEvent> {
376        let radius_a = collider_a.size.x / 2.0;
377        let radius_b = collider_b.size.x / 2.0;
378        
379        let center_a = Vec2::new(pos_a.x + collider_a.offset.x, pos_a.y + collider_a.offset.y);
380        let center_b = Vec2::new(pos_b.x + collider_b.offset.x, pos_b.y + collider_b.offset.y);
381        
382        let distance_vec = center_b - center_a;
383        let distance = distance_vec.length();
384        let min_distance = radius_a + radius_b;
385
386        if distance < min_distance {
387            let normal = if distance > 0.0 {
388                distance_vec / distance
389            } else {
390                Vec2::new(1.0, 0.0)
391            };
392
393            Some(CollisionEvent {
394                entity_a,
395                entity_b,
396                contact_point: Position::new(
397                    center_a.x + normal.x * radius_a,
398                    center_a.y + normal.y * radius_a,
399                ),
400                normal,
401                penetration: min_distance - distance,
402            })
403        } else {
404            None
405        }
406    }
407
408    fn resolve_collision(&mut self, collision: CollisionEvent) {
409        let body_a = self.rigid_bodies.get(&collision.entity_a).cloned();
410        let body_b = self.rigid_bodies.get(&collision.entity_b).cloned();
411
412        if let (Some(mut body_a), Some(mut body_b)) = (body_a, body_b) {
413            if body_a.is_static && body_b.is_static {
414                return;
415            }
416
417            let total_mass = if body_a.is_static {
418                body_b.mass
419            } else if body_b.is_static {
420                body_a.mass
421            } else {
422                body_a.mass + body_b.mass
423            };
424
425            let separation = collision.normal * collision.penetration;
426
427            if !body_a.is_static {
428                let ratio_a = if body_b.is_static { 1.0 } else { body_b.mass / total_mass };
429                body_a.position.x -= separation.x * ratio_a;
430                body_a.position.y -= separation.y * ratio_a;
431            }
432
433            if !body_b.is_static {
434                let ratio_b = if body_a.is_static { 1.0 } else { body_a.mass / total_mass };
435                body_b.position.x += separation.x * ratio_b;
436                body_b.position.y += separation.y * ratio_b;
437            }
438
439            let relative_velocity = body_b.velocity - body_a.velocity;
440            let velocity_along_normal = relative_velocity.x * collision.normal.x + 
441                                       relative_velocity.y * collision.normal.y;
442
443            if velocity_along_normal > 0.0 {
444                return;
445            }
446
447            let restitution = (body_a.bounce + body_b.bounce) / 2.0;
448            let impulse_magnitude = -(1.0 + restitution) * velocity_along_normal / 
449                                   (1.0/body_a.mass + 1.0/body_b.mass);
450
451            let impulse = collision.normal * impulse_magnitude;
452
453            if !body_a.is_static {
454                body_a.velocity -= impulse / body_a.mass;
455            }
456            if !body_b.is_static {
457                body_b.velocity += impulse / body_b.mass;
458            }
459
460            self.rigid_bodies.insert(collision.entity_a, body_a);
461            self.rigid_bodies.insert(collision.entity_b, body_b);
462        }
463    }
464
465    pub fn raycast(&self, origin: Position, direction: Vec2, max_distance: f32) -> Option<(EntityId, f32, Position)> {
466        let ray = Ray::new(
467            Point::new(origin.x, origin.y),
468            Vector::new(direction.x, direction.y)
469        );
470
471        let mut closest_hit: Option<(EntityId, f32, Position)> = None;
472
473        for (&entity_id, collider) in &self.colliders {
474            if let Some(body) = self.rigid_bodies.get(&entity_id) {
475                let shape = match collider.shape {
476                    CollisionShape::Rectangle => {
477                        SharedShape::new(Cuboid::new(Vector::new(
478                            collider.size.x / 2.0,
479                            collider.size.y / 2.0,
480                        )))
481                    }
482                    CollisionShape::Circle => {
483                        SharedShape::new(Ball::new(collider.size.x / 2.0))
484                    }
485                };
486
487                let pos = Point::new(
488                    body.position.x + collider.offset.x,
489                    body.position.y + collider.offset.y,
490                );
491
492                if let Some(toi) = shape.cast_ray(&pos.into(), &ray, max_distance, true) {
493                    let hit_point = Position::new(
494                        origin.x + direction.x * toi,
495                        origin.y + direction.y * toi,
496                    );
497
498                    if closest_hit.is_none() || toi < closest_hit.as_ref().unwrap().1 {
499                        closest_hit = Some((entity_id, toi, hit_point));
500                    }
501                }
502            }
503        }
504
505        closest_hit
506    }
507
508    pub fn get_collision_events(&self) -> &[CollisionEvent] {
509        &self.collision_events
510    }
511}