Skip to main content

mittens_engine/engine/ecs/component/
kinetic_response.rs

1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::component::Component;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum KineticResponseMode {
6    /// Kinematic "slide" resolution: push the body out of static overlaps using AABB penetration.
7    Slide,
8
9    /// Kinematic "push" behavior: accumulate velocity away from overlapping non-static colliders,
10    /// and integrate it every tick (still resolves against static overlaps).
11    Push,
12}
13
14/// Opt-in kinematic collision response behavior for a collider.
15///
16/// Preferred topology is nesting under the collider:
17///
18/// `TransformComponent -> CollisionComponent -> KineticResponseComponent`
19#[derive(Debug, Clone)]
20pub struct KineticResponseComponent {
21    pub enabled: bool,
22    pub mode: KineticResponseMode,
23
24    /// Max number of correction iterations per tick (helps resolve corner cases).
25    pub max_iterations: u32,
26
27    /// Small extra push-out added on separation to avoid jittering on exact contact.
28    pub push_out_epsilon: f32,
29
30    /// Strength of velocity acceleration applied from overlapping non-static colliders.
31    pub push_strength: f32,
32
33    /// Optional friction-like damping applied to velocity each second.
34    /// Off by default.
35    /// Effective multiplier per tick is `max(0, 1 - friction * dt)`.
36    pub friction: f32,
37
38    /// Optional additional damping applied to the Y velocity component only when resolving
39    /// a vertical (Y-axis) static overlap.
40    /// Off by default.
41    /// Effective multiplier per contact tick is `max(0, 1 - friction_y * dt)`.
42    pub friction_y: f32,
43
44    /// Clamp on velocity magnitude (world units / sec).
45    pub max_speed: f32,
46
47    /// Runtime-only velocity accumulator (not serialized).
48    pub velocity: [f32; 3],
49
50    /// Runtime-only cached gravity coefficient from the nearest enabled `GravityComponent`
51    /// ancestor.
52    ///
53    /// This is set by `KineticResponseSystem` when the component is registered.
54    pub gravity_coefficient: f32,
55
56    component: Option<ComponentId>,
57}
58
59impl KineticResponseComponent {
60    pub fn new(mode: KineticResponseMode) -> Self {
61        Self {
62            enabled: true,
63            mode,
64            max_iterations: 6,
65            push_out_epsilon: 0.001,
66            push_strength: 4.0,
67            friction: 0.0,
68            friction_y: 0.0,
69            max_speed: 6.0,
70            velocity: [0.0, 0.0, 0.0],
71            gravity_coefficient: 0.0,
72            component: None,
73        }
74    }
75
76    pub fn slide() -> Self {
77        let mut c = Self::new(KineticResponseMode::Slide);
78        c.push_strength = 0.0;
79        c.friction = 0.0;
80        c.max_speed = 0.0;
81        c
82    }
83
84    pub fn push() -> Self {
85        Self::new(KineticResponseMode::Push)
86    }
87
88    pub fn with_push_strength(mut self, push_strength: f32) -> Self {
89        self.push_strength = push_strength;
90        self
91    }
92
93    pub fn with_friction(mut self, friction: f32) -> Self {
94        self.friction = friction.max(0.0);
95        self
96    }
97
98    pub fn with_friction_y(mut self, friction_y: f32) -> Self {
99        self.friction_y = friction_y.max(0.0);
100        self
101    }
102}
103
104impl Default for KineticResponseComponent {
105    fn default() -> Self {
106        Self::slide()
107    }
108}
109
110impl Component for KineticResponseComponent {
111    fn name(&self) -> &'static str {
112        "kinetic_response"
113    }
114
115    fn set_id(&mut self, component: ComponentId) {
116        self.component = Some(component);
117    }
118
119    fn as_any(&self) -> &dyn std::any::Any {
120        self
121    }
122
123    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
124        self
125    }
126
127    fn init(&mut self, emit: &mut dyn crate::engine::ecs::SignalEmitter, component: ComponentId) {
128        emit.push_intent_now(
129            component,
130            crate::engine::ecs::IntentValue::RegisterKineticResponse {
131                component_ids: vec![component],
132            },
133        );
134    }
135
136    fn cleanup(
137        &mut self,
138        emit: &mut dyn crate::engine::ecs::SignalEmitter,
139        component: ComponentId,
140    ) {
141        emit.push_intent_now(
142            component,
143            crate::engine::ecs::IntentValue::RemoveKineticResponse {
144                component_ids: vec![component],
145            },
146        );
147    }
148
149    fn to_mms_ast(
150        &self,
151        _world: &crate::engine::ecs::World,
152    ) -> crate::scripting::ast::ComponentExpression {
153        use crate::engine::ecs::component::ce_helpers::*;
154        let ctor = match self.mode {
155            KineticResponseMode::Slide => "slide",
156            KineticResponseMode::Push => "push",
157        };
158        ce_call("KineticResponse", ctor, vec![])
159            .with_call("enabled", vec![b(self.enabled)])
160            .with_call("max_iterations", vec![num(self.max_iterations as f64)])
161            .with_call("push_out_epsilon", vec![num(self.push_out_epsilon as f64)])
162            .with_call("push_strength", vec![num(self.push_strength as f64)])
163            .with_call("friction", vec![num(self.friction as f64)])
164            .with_call("friction_y", vec![num(self.friction_y as f64)])
165            .with_call("max_speed", vec![num(self.max_speed as f64)])
166    }
167}