khora_core/physics/
solver.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # Impulse Solver
16//!
17//! Pure mathematical implementation of constraint resolution using impulses.
18
19use super::{BodyType, ContactManifold};
20use crate::math::Vec3;
21
22/// Represents the physical state of a body relevant to impulse resolution.
23#[derive(Debug, Clone, Copy)]
24pub struct VelocityState {
25    /// Linear velocity vector.
26    pub linear_velocity: Vec3,
27    /// Angular velocity vector.
28    pub angular_velocity: Vec3,
29    /// Mass of the body.
30    pub mass: f32,
31    /// Type of the body (Dynamic, Static, Kinematic).
32    pub body_type: BodyType,
33}
34
35/// A mathematical solver for impulse-based constraint resolution.
36pub struct ImpulseSolver {
37    /// Coefficient of restitution (bounciness).
38    pub restitution: f32,
39    /// Percentage of penetration to resolve per frame (Baumgarte stabilization).
40    pub baumgarte_percent: f32,
41    /// Penetration allowance to avoid jitter.
42    pub slop: f32,
43}
44
45impl ImpulseSolver {
46    /// Creates a new `ImpulseSolver` with default physical constants.
47    pub fn new() -> Self {
48        Self {
49            restitution: 0.2,
50            baumgarte_percent: 0.2,
51            slop: 0.01,
52        }
53    }
54
55    /// Resolves the collision between two bodies.
56    ///
57    /// Returns the updated velocity states for both bodies.
58    pub fn resolve(
59        &self,
60        mut a: VelocityState,
61        mut b: VelocityState,
62        manifold: &ContactManifold,
63    ) -> (VelocityState, VelocityState) {
64        if a.body_type == BodyType::Static && b.body_type == BodyType::Static {
65            return (a, b);
66        }
67
68        // 1. Calculate relative velocity in the direction of the collision normal.
69        let rv = b.linear_velocity - a.linear_velocity;
70        let vel_along_normal = rv.dot(manifold.normal);
71
72        // 2. If already separating, no impulse is needed.
73        if vel_along_normal > 0.0 {
74            return (a, b);
75        }
76
77        // 3. Calculate impulse magnitude (j).
78        let inv_mass_a = if a.body_type == BodyType::Dynamic {
79            1.0 / a.mass
80        } else {
81            0.0
82        };
83        let inv_mass_b = if b.body_type == BodyType::Dynamic {
84            1.0 / b.mass
85        } else {
86            0.0
87        };
88        let total_inv_mass = inv_mass_a + inv_mass_b;
89
90        if total_inv_mass <= 0.0 {
91            return (a, b);
92        }
93
94        let mut j = -(1.0 + self.restitution) * vel_along_normal;
95        j /= total_inv_mass;
96
97        // 4. Apply impulse as a change in linear velocity.
98        let impulse = manifold.normal * j;
99        if a.body_type == BodyType::Dynamic {
100            a.linear_velocity = a.linear_velocity - (impulse * inv_mass_a);
101        }
102        if b.body_type == BodyType::Dynamic {
103            b.linear_velocity = b.linear_velocity + (impulse * inv_mass_b);
104        }
105
106        // 5. Positional correction (Linear Projection) using velocity "slack".
107        let correction_mag =
108            (manifold.depth - self.slop).max(0.0) / total_inv_mass * self.baumgarte_percent;
109        let correction = manifold.normal * correction_mag;
110
111        if a.body_type == BodyType::Dynamic {
112            a.linear_velocity = a.linear_velocity - (correction * inv_mass_a);
113        }
114        if b.body_type == BodyType::Dynamic {
115            b.linear_velocity = b.linear_velocity + (correction * inv_mass_b);
116        }
117
118        (a, b)
119    }
120}
121
122impl Default for ImpulseSolver {
123    fn default() -> Self {
124        Self::new()
125    }
126}