bevy_tnua/builtins/
wall_slide.rs1use 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#[derive(Clone)]
13#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
14pub struct TnuaBuiltinWallSlide {
15 pub contact_point_with_wall: Vector3,
21
22 pub normal: Dir3,
24
25 pub force_forward: Option<Dir3>,
27}
28
29#[derive(Clone, Serialize, Deserialize)]
30pub struct TnuaBuiltinWallSlideConfig {
31 pub max_fall_speed: Float,
33
34 pub maintain_distance: Option<Float>,
40
41 pub max_sideways_speed: Float,
44
45 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 {}