bevy_tnua/builtins/
wall_slide.rs

1use crate::{
2    TnuaAction, TnuaActionInitiationDirective, TnuaActionLifecycleDirective,
3    TnuaActionLifecycleStatus, TnuaBasis, TnuaMotor, TnuaVelChange,
4    basis_capabilities::TnuaBasisWithGround,
5    math::{AdjustPrecision, AsF32, Float, Vector3},
6    util::calc_angular_velchange_to_force_forward,
7};
8use bevy::prelude::*;
9use serde::{Deserialize, Serialize};
10
11/// An [action](TnuaAction) for sliding on walls.
12#[derive(Clone)]
13#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
14pub struct TnuaBuiltinWallSlide {
15    /// The on the wall where the character touches it.
16    ///
17    /// Note that this does not actually have to be on an actual wall. It can be a point in the
18    /// middle of the air, and the action will cause the character to pretend there is a wall there
19    /// and slide on it.
20    pub contact_point_with_wall: Vector3,
21
22    /// The wall's normal
23    pub normal: Dir3,
24
25    /// Force the character to face in a particular direction.
26    pub force_forward: Option<Dir3>,
27}
28
29#[derive(Clone, Serialize, Deserialize)]
30pub struct TnuaBuiltinWallSlideConfig {
31    /// When the character slides faster than that speed, slow it down.
32    pub max_fall_speed: Float,
33
34    /// A distance to maintain from the wall.
35    ///
36    /// Specifically - the distance from
37    /// [`contact_point_with_wall`](TnuaBuiltinWallSlide::contact_point_with_wall) in the direction
38    /// of the [`normal`](TnuaBuiltinWallSlide::normal).
39    pub maintain_distance: Option<Float>,
40
41    /// The maximum speed the character is allowed to move sideways on the wall while sliding
42    /// down on it.
43    pub max_sideways_speed: Float,
44
45    /// The maximum acceleration the character is allowed to move sideways on the wall while
46    /// sliding down on it.
47    ///
48    /// Note that this also apply to the acceleration used to brake the character's horitonztal
49    /// movement when it enters the wall slide faster than
50    /// [`max_sideways_speed`](Self::max_sideways_speed).
51    pub max_sideways_acceleration: Float,
52}
53
54impl Default for TnuaBuiltinWallSlideConfig {
55    fn default() -> Self {
56        Self {
57            max_fall_speed: 2.0,
58            maintain_distance: None,
59            max_sideways_speed: 1.0,
60            max_sideways_acceleration: 60.0,
61        }
62    }
63}
64
65impl<B: TnuaBasis> TnuaAction<B> for TnuaBuiltinWallSlide
66where
67    B: TnuaBasisWithGround,
68{
69    type Config = TnuaBuiltinWallSlideConfig;
70    type Memory = TnuaBuiltinWallSlideMemory;
71
72    fn initiation_decision(
73        &self,
74        _config: &Self::Config,
75        _sensors: &B::Sensors<'_>,
76        _ctx: crate::TnuaActionContext<B>,
77        _being_fed_for: &bevy::time::Stopwatch,
78    ) -> TnuaActionInitiationDirective {
79        TnuaActionInitiationDirective::Allow
80    }
81
82    fn apply(
83        &self,
84        config: &Self::Config,
85        _memory: &mut Self::Memory,
86        _sensors: &B::Sensors<'_>,
87        ctx: crate::TnuaActionContext<B>,
88        lifecycle_status: TnuaActionLifecycleStatus,
89        motor: &mut TnuaMotor,
90    ) -> TnuaActionLifecycleDirective {
91        if !lifecycle_status.is_active() {
92            return TnuaActionLifecycleDirective::Finished;
93        }
94
95        let downward_speed = -ctx
96            .tracker
97            .velocity
98            .dot(ctx.up_direction.adjust_precision());
99        let desired_upward_boost = downward_speed - config.max_fall_speed;
100        let actual_upwrad_boost = motor
101            .lin
102            .calc_boost(ctx.frame_duration)
103            .dot(ctx.up_direction.adjust_precision());
104        let upward_boost_for_compensation = desired_upward_boost - actual_upwrad_boost;
105        motor.lin += TnuaVelChange::acceleration(
106            upward_boost_for_compensation * ctx.up_direction.adjust_precision()
107                / ctx.frame_duration,
108        );
109
110        if let Some(maintain_distance) = config.maintain_distance {
111            let planar_vector = (ctx.tracker.translation - self.contact_point_with_wall)
112                .reject_from(ctx.up_direction.adjust_precision());
113            if let Ok((cling_direction, current_cling_distance)) =
114                Dir3::new_and_length(planar_vector.f32())
115            {
116                let current_cling_speed =
117                    ctx.tracker.velocity.dot(cling_direction.adjust_precision());
118                let desired_cling_speed = (maintain_distance
119                    - current_cling_distance.adjust_precision())
120                    / ctx.frame_duration;
121                let cling_boost = desired_cling_speed - current_cling_speed;
122                motor.lin.cancel_on_axis(cling_direction.adjust_precision());
123                motor.lin += TnuaVelChange::boost(cling_boost * cling_direction.adjust_precision());
124            }
125        }
126
127        let sideways_direction = self.normal.cross(*ctx.up_direction).adjust_precision();
128        let projected_sideways_velocity =
129            sideways_direction.dot(ctx.tracker.velocity + motor.lin.calc_boost(ctx.frame_duration));
130        if config.max_sideways_speed < projected_sideways_velocity.abs() {
131            let desired_sideways_velocity =
132                config.max_sideways_speed * projected_sideways_velocity.signum();
133            let desired_sideways_boost = desired_sideways_velocity - projected_sideways_velocity;
134            let desired_sideways_acceleration = desired_sideways_boost / ctx.frame_duration;
135            motor.lin +=
136                TnuaVelChange::acceleration(sideways_direction * desired_sideways_acceleration);
137        }
138
139        let sideways_acceleration =
140            sideways_direction.dot(motor.lin.calc_acceleration(ctx.frame_duration));
141        if config.max_sideways_acceleration < sideways_acceleration.abs() {
142            let desired_sideways_acceleration =
143                config.max_sideways_acceleration * sideways_acceleration.signum();
144            motor.lin += TnuaVelChange::acceleration(
145                sideways_direction * (desired_sideways_acceleration - sideways_acceleration),
146            );
147        }
148
149        if let Some(force_forward) = self.force_forward {
150            motor
151                .ang
152                .cancel_on_axis(ctx.up_direction.adjust_precision());
153            motor.ang += calc_angular_velchange_to_force_forward(
154                force_forward,
155                ctx.tracker.rotation,
156                ctx.tracker.angvel,
157                ctx.up_direction,
158                ctx.frame_duration,
159            );
160        }
161
162        TnuaActionLifecycleDirective::StillActive
163    }
164
165    fn influence_basis(
166        &self,
167        _config: &Self::Config,
168        _memory: &Self::Memory,
169        _ctx: crate::TnuaBasisContext,
170        _basis_input: &B,
171        _basis_config: &<B as TnuaBasis>::Config,
172        basis_memory: &mut <B as TnuaBasis>::Memory,
173    ) {
174        B::violate_coyote_time(basis_memory);
175    }
176}
177
178#[derive(Default, Debug)]
179#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
180pub struct TnuaBuiltinWallSlideMemory {}