bevy_tnua/builtins/
wall_slide.rs

1use crate::{
2    math::{AdjustPrecision, AsF32, Float, Vector3},
3    util::calc_angular_velchange_to_force_forward,
4    TnuaAction, TnuaActionInitiationDirective, TnuaActionLifecycleDirective,
5    TnuaActionLifecycleStatus, TnuaMotor, TnuaVelChange,
6};
7use bevy::prelude::*;
8
9/// An [action](TnuaAction) for sliding on walls.
10#[derive(Clone)]
11pub struct TnuaBuiltinWallSlide {
12    /// The entity of the wall to slide on.
13    pub wall_entity: Option<Entity>,
14
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    /// When the character slides faster than that speed, slow it down.
29    pub max_fall_speed: Float,
30
31    /// A distance to maintain from the wall.
32    ///
33    /// Specifically - the distance from
34    /// [`contact_point_with_wall`](Self::contact_point_with_wall) in the direction of the
35    /// [`normal`](Self::normal).
36    pub maintain_distance: Option<Float>,
37
38    /// The maximum speed the character is allowed to move sideways on the wall while sliding
39    /// down on it.
40    pub max_sideways_speed: Float,
41
42    /// The maximum acceleration the character is allowed to move sideways on the wall while
43    /// sliding down on it.
44    ///
45    /// Note that this also apply to the acceleration used to brake the character's horitonztal
46    /// movement when it enters the wall slide faster than
47    /// [`max_sideways_speed`](Self::max_sideways_speed).
48    pub max_sideways_acceleration: Float,
49}
50
51impl Default for TnuaBuiltinWallSlide {
52    fn default() -> Self {
53        Self {
54            wall_entity: None,
55            contact_point_with_wall: Vector3::ZERO,
56            normal: Dir3::Y,
57            force_forward: None, // obvisouly invalid value
58            max_fall_speed: 2.0,
59            maintain_distance: None,
60            max_sideways_speed: 1.0,
61            max_sideways_acceleration: 60.0,
62        }
63    }
64}
65
66impl TnuaAction for TnuaBuiltinWallSlide {
67    const NAME: &'static str = "TnuaBuiltinWallSlide";
68
69    type State = TnuaBuiltinWallSlideState;
70
71    const VIOLATES_COYOTE_TIME: bool = true;
72
73    fn apply(
74        &self,
75        _state: &mut Self::State,
76        ctx: crate::TnuaActionContext,
77        lifecycle_status: TnuaActionLifecycleStatus,
78        motor: &mut TnuaMotor,
79    ) -> TnuaActionLifecycleDirective {
80        if !lifecycle_status.is_active() {
81            return TnuaActionLifecycleDirective::Finished;
82        }
83
84        let downward_speed = -ctx
85            .tracker
86            .velocity
87            .dot(ctx.up_direction.adjust_precision());
88        let desired_upward_boost = downward_speed - self.max_fall_speed;
89        let actual_upwrad_boost = motor
90            .lin
91            .calc_boost(ctx.frame_duration)
92            .dot(ctx.up_direction.adjust_precision());
93        let upward_boost_for_compensation = desired_upward_boost - actual_upwrad_boost;
94        motor.lin += TnuaVelChange::acceleration(
95            upward_boost_for_compensation * ctx.up_direction.adjust_precision()
96                / ctx.frame_duration,
97        );
98
99        if let Some(maintain_distance) = self.maintain_distance {
100            let planar_vector = (ctx.tracker.translation - self.contact_point_with_wall)
101                .reject_from(ctx.up_direction.adjust_precision());
102            if let Ok((cling_direction, current_cling_distance)) =
103                Dir3::new_and_length(planar_vector.f32())
104            {
105                let current_cling_speed =
106                    ctx.tracker.velocity.dot(cling_direction.adjust_precision());
107                let desired_cling_speed = (maintain_distance
108                    - current_cling_distance.adjust_precision())
109                    / ctx.frame_duration;
110                let cling_boost = desired_cling_speed - current_cling_speed;
111                motor.lin.cancel_on_axis(cling_direction.adjust_precision());
112                motor.lin += TnuaVelChange::boost(cling_boost * cling_direction.adjust_precision());
113            }
114        }
115
116        let sideways_direction = self.normal.cross(*ctx.up_direction).adjust_precision();
117        let projected_sideways_velocity =
118            sideways_direction.dot(ctx.tracker.velocity + motor.lin.calc_boost(ctx.frame_duration));
119        if self.max_sideways_speed < projected_sideways_velocity.abs() {
120            let desired_sideways_velocity =
121                self.max_sideways_speed * projected_sideways_velocity.signum();
122            let desired_sideways_boost = desired_sideways_velocity - projected_sideways_velocity;
123            let desired_sideways_acceleration = desired_sideways_boost / ctx.frame_duration;
124            motor.lin +=
125                TnuaVelChange::acceleration(sideways_direction * desired_sideways_acceleration);
126        }
127
128        let sideways_acceleration =
129            sideways_direction.dot(motor.lin.calc_acceleration(ctx.frame_duration));
130        if self.max_sideways_acceleration < sideways_acceleration.abs() {
131            let desired_sideways_acceleration =
132                self.max_sideways_acceleration * sideways_acceleration.signum();
133            motor.lin += TnuaVelChange::acceleration(
134                sideways_direction * (desired_sideways_acceleration - sideways_acceleration),
135            );
136        }
137
138        if let Some(force_forward) = self.force_forward {
139            motor
140                .ang
141                .cancel_on_axis(ctx.up_direction.adjust_precision());
142            motor.ang += calc_angular_velchange_to_force_forward(
143                force_forward,
144                ctx.tracker.rotation,
145                ctx.tracker.angvel,
146                ctx.up_direction,
147                ctx.frame_duration,
148            );
149        }
150
151        TnuaActionLifecycleDirective::StillActive
152    }
153
154    fn initiation_decision(
155        &self,
156        _ctx: crate::TnuaActionContext,
157        _being_fed_for: &bevy::time::Stopwatch,
158    ) -> TnuaActionInitiationDirective {
159        TnuaActionInitiationDirective::Allow
160    }
161}
162
163#[derive(Default, Debug)]
164pub struct TnuaBuiltinWallSlideState {}