Skip to main content

arcane_engine/physics/
types.rs

1pub type BodyId = u32;
2pub type ConstraintId = u32;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum BodyType {
6    Static,
7    Dynamic,
8    Kinematic,
9}
10
11#[derive(Debug, Clone, PartialEq)]
12pub enum Shape {
13    Circle { radius: f32 },
14    AABB { half_w: f32, half_h: f32 },
15    Polygon { vertices: Vec<(f32, f32)> },
16}
17
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct Material {
20    pub restitution: f32,
21    pub friction: f32,
22}
23
24impl Default for Material {
25    fn default() -> Self {
26        Self {
27            restitution: 0.3,
28            friction: 0.5,
29        }
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct RigidBody {
35    pub id: BodyId,
36    pub body_type: BodyType,
37    pub shape: Shape,
38    pub material: Material,
39    pub x: f32,
40    pub y: f32,
41    pub angle: f32,
42    pub vx: f32,
43    pub vy: f32,
44    pub angular_velocity: f32,
45    pub fx: f32,
46    pub fy: f32,
47    pub torque: f32,
48    pub mass: f32,
49    pub inv_mass: f32,
50    pub inertia: f32,
51    pub inv_inertia: f32,
52    pub layer: u16,
53    pub mask: u16,
54    pub sleeping: bool,
55    pub sleep_timer: f32,
56}
57
58#[derive(Debug, Clone)]
59pub struct Contact {
60    pub body_a: BodyId,
61    pub body_b: BodyId,
62    pub normal: (f32, f32),
63    pub penetration: f32,
64    pub contact_point: (f32, f32),
65    /// Accumulated normal impulse (used by warm starting)
66    pub accumulated_jn: f32,
67    /// Accumulated friction impulse (used by warm starting)
68    pub accumulated_jt: f32,
69    /// Restitution velocity bias (computed once before solver iterations)
70    pub velocity_bias: f32,
71    /// Friction tangent direction (computed once before solver iterations)
72    pub tangent: (f32, f32),
73}
74
75#[derive(Debug, Clone)]
76pub enum Constraint {
77    Distance {
78        id: ConstraintId,
79        body_a: BodyId,
80        body_b: BodyId,
81        distance: f32,
82        anchor_a: (f32, f32),
83        anchor_b: (f32, f32),
84    },
85    Revolute {
86        id: ConstraintId,
87        body_a: BodyId,
88        body_b: BodyId,
89        pivot: (f32, f32),
90    },
91}
92
93impl Constraint {
94    pub fn id(&self) -> ConstraintId {
95        match self {
96            Constraint::Distance { id, .. } => *id,
97            Constraint::Revolute { id, .. } => *id,
98        }
99    }
100}
101
102/// Compute inverse mass, inertia, and inverse inertia for a shape.
103/// Static bodies get inv_mass=0 and inv_inertia=0.
104pub fn compute_mass_and_inertia(shape: &Shape, mass: f32, body_type: BodyType) -> (f32, f32, f32) {
105    if body_type == BodyType::Static || mass <= 0.0 {
106        return (0.0, 0.0, 0.0);
107    }
108    let inv_mass = 1.0 / mass;
109    let inertia = match shape {
110        Shape::Circle { radius } => 0.5 * mass * radius * radius,
111        Shape::AABB { .. } => {
112            // AABBs don't rotate — collision detection treats them as axis-aligned
113            // regardless of body angle. Angular dynamics would create phantom forces
114            // (angular velocity leaks into linear velocity via friction at contacts).
115            // Use Polygon shapes for rotatable boxes.
116            0.0
117        }
118        Shape::Polygon { vertices } => {
119            // Approximate inertia using polygon area moment
120            compute_polygon_inertia(vertices, mass)
121        }
122    };
123    let inv_inertia = if inertia > 0.0 { 1.0 / inertia } else { 0.0 };
124    (inv_mass, inertia, inv_inertia)
125}
126
127fn compute_polygon_inertia(vertices: &[(f32, f32)], mass: f32) -> f32 {
128    let n = vertices.len();
129    if n < 3 {
130        return 0.0;
131    }
132    let mut numerator = 0.0f32;
133    let mut denominator = 0.0f32;
134    for i in 0..n {
135        let (x0, y0) = vertices[i];
136        let (x1, y1) = vertices[(i + 1) % n];
137        let cross = (x0 * y1 - x1 * y0).abs();
138        numerator += cross * (x0 * x0 + x0 * x1 + x1 * x1 + y0 * y0 + y0 * y1 + y1 * y1);
139        denominator += cross;
140    }
141    if denominator == 0.0 {
142        return 0.0;
143    }
144    mass * numerator / (6.0 * denominator)
145}
146
147/// Get the axis-aligned bounding box for a body, accounting for position.
148pub fn get_shape_aabb(body: &RigidBody) -> (f32, f32, f32, f32) {
149    match &body.shape {
150        Shape::Circle { radius } => (
151            body.x - radius,
152            body.y - radius,
153            body.x + radius,
154            body.y + radius,
155        ),
156        Shape::AABB { half_w, half_h } => {
157            if body.angle.abs() < 1e-6 {
158                (
159                    body.x - half_w,
160                    body.y - half_h,
161                    body.x + half_w,
162                    body.y + half_h,
163                )
164            } else {
165                // Rotated AABB: compute bounding box of rotated corners
166                let cos = body.angle.cos();
167                let sin = body.angle.sin();
168                let hw = (half_w * cos.abs()) + (half_h * sin.abs());
169                let hh = (half_w * sin.abs()) + (half_h * cos.abs());
170                (body.x - hw, body.y - hh, body.x + hw, body.y + hh)
171            }
172        }
173        Shape::Polygon { vertices } => {
174            if vertices.is_empty() {
175                return (body.x, body.y, body.x, body.y);
176            }
177            let cos = body.angle.cos();
178            let sin = body.angle.sin();
179            let mut min_x = f32::MAX;
180            let mut min_y = f32::MAX;
181            let mut max_x = f32::MIN;
182            let mut max_y = f32::MIN;
183            for &(vx, vy) in vertices {
184                let rx = vx * cos - vy * sin + body.x;
185                let ry = vx * sin + vy * cos + body.y;
186                min_x = min_x.min(rx);
187                min_y = min_y.min(ry);
188                max_x = max_x.max(rx);
189                max_y = max_y.max(ry);
190            }
191            (min_x, min_y, max_x, max_y)
192        }
193    }
194}