1use 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}