bevy_tnua/builtins/
wall_slide.rs1use 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#[derive(Clone)]
11pub struct TnuaBuiltinWallSlide {
12 pub wall_entity: Option<Entity>,
14
15 pub contact_point_with_wall: Vector3,
21
22 pub normal: Dir3,
24
25 pub force_forward: Option<Dir3>,
27
28 pub max_fall_speed: Float,
30
31 pub maintain_distance: Option<Float>,
37
38 pub max_sideways_speed: Float,
41
42 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, 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 {}