bevy_tnua/builtins/
climb.rs1use bevy::prelude::*;
2use bevy_tnua_physics_integration_layer::math::{AdjustPrecision, AsF32, Float};
3use serde::{Deserialize, Serialize};
4
5use crate::basis_capabilities::TnuaBasisWithGround;
6use crate::util::MotionHelper;
7use crate::{
8 TnuaAction, TnuaActionInitiationDirective, TnuaActionLifecycleDirective,
9 TnuaActionLifecycleStatus, TnuaMotor, math::Vector3,
10};
11use crate::{TnuaActionContext, TnuaBasis};
12
13#[derive(Clone, Default)]
15#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
16pub struct TnuaBuiltinClimb {
17 pub anchor: Vector3,
22
23 pub desired_vec_to_anchor: Vector3,
27
28 pub desired_climb_motion: Vector3,
30
31 pub desired_forward: Option<Dir3>,
33
34 pub hard_stop_up: Option<Vector3>,
40
41 pub hard_stop_down: Option<Vector3>,
47}
48
49#[derive(Clone, Serialize, Deserialize)]
50pub struct TnuaBuiltinClimbConfig {
51 pub anchor_speed: Float,
53
54 pub anchor_acceleration: Float,
57
58 pub climb_speed: Float,
64
65 pub climb_acceleration: Float,
67
68 pub coyote_time: Float,
70}
71
72impl Default for TnuaBuiltinClimbConfig {
73 fn default() -> Self {
74 Self {
75 anchor_speed: 150.0,
76 anchor_acceleration: 500.0,
77 climb_speed: 10.0,
78 climb_acceleration: 30.0,
79 coyote_time: 0.15,
80 }
81 }
82}
83
84impl<B: TnuaBasis> TnuaAction<B> for TnuaBuiltinClimb
85where
86 B: TnuaBasisWithGround,
87{
88 type Config = TnuaBuiltinClimbConfig;
89 type Memory = TnuaBuiltinClimbMemory;
90
91 fn initiation_decision(
94 &self,
95 _config: &Self::Config,
96 _sensors: &B::Sensors<'_>,
97 _ctx: TnuaActionContext<B>,
98 _being_fed_for: &bevy::time::Stopwatch,
99 ) -> TnuaActionInitiationDirective {
100 TnuaActionInitiationDirective::Allow
101 }
102
103 fn apply(
104 &self,
105 config: &Self::Config,
106 memory: &mut Self::Memory,
107 _sensors: &B::Sensors<'_>,
108 ctx: TnuaActionContext<B>,
109 lifecycle_status: TnuaActionLifecycleStatus,
110 motor: &mut TnuaMotor,
111 ) -> TnuaActionLifecycleDirective {
112 for _ in 0..2 {
115 return match memory {
116 TnuaBuiltinClimbMemory::Climbing { climbing_velocity } => {
117 if matches!(lifecycle_status, TnuaActionLifecycleStatus::NoLongerFed) {
118 *memory = TnuaBuiltinClimbMemory::Coyote(Timer::from_seconds(
119 config.coyote_time.f32(),
120 TimerMode::Once,
121 ));
122 continue;
123 }
124
125 *climbing_velocity = ctx
128 .tracker
129 .velocity
130 .project_onto(ctx.up_direction.adjust_precision());
131
132 motor
133 .lin
134 .cancel_on_axis(ctx.up_direction.adjust_precision());
135 motor.lin += ctx.negate_gravity();
136 motor.lin += ctx.adjust_vertical_velocity(
137 config.climb_speed
138 * self
139 .desired_climb_motion
140 .dot(ctx.up_direction.adjust_precision()),
141 config.climb_acceleration,
142 );
143
144 if let Some(stop_at) = self.hard_stop_up {
145 motor.lin += ctx.hard_stop(ctx.up_direction, stop_at, &motor.lin);
146 }
147 if let Some(stop_at) = self.hard_stop_down {
148 motor.lin += ctx.hard_stop(-ctx.up_direction, stop_at, &motor.lin);
149 }
150
151 let vec_to_anchor = (self.anchor - ctx.tracker.translation)
152 .reject_from(ctx.up_direction().adjust_precision());
153 let horizontal_displacement = self.desired_vec_to_anchor - vec_to_anchor;
154
155 let desired_horizontal_velocity = -horizontal_displacement / ctx.frame_duration;
156
157 motor.lin += ctx.adjust_horizontal_velocity(
158 desired_horizontal_velocity.clamp_length_max(config.anchor_speed),
159 config.anchor_acceleration,
160 );
161
162 if let Some(desired_forward) = self.desired_forward {
163 motor
164 .ang
165 .cancel_on_axis(ctx.up_direction.adjust_precision());
166 motor.ang += ctx.turn_to_direction(desired_forward, ctx.up_direction);
167 }
168
169 lifecycle_status.directive_simple()
170 }
171 TnuaBuiltinClimbMemory::Coyote(timer) => {
172 if timer.tick(ctx.frame_duration_as_duration()).is_finished() {
173 TnuaActionLifecycleDirective::Finished
174 } else {
175 lifecycle_status.directive_linger()
176 }
177 }
178 };
179 }
180 error!("Tnua could not decide on climb state");
181 TnuaActionLifecycleDirective::Finished
182 }
183
184 fn influence_basis(
185 &self,
186 _config: &Self::Config,
187 _memory: &Self::Memory,
188 _ctx: crate::TnuaBasisContext,
189 _basis_input: &B,
190 _basis_config: &<B as TnuaBasis>::Config,
191 basis_memory: &mut <B as TnuaBasis>::Memory,
192 ) {
193 B::violate_coyote_time(basis_memory);
194 }
195}
196
197#[derive(Debug)]
198#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
199pub enum TnuaBuiltinClimbMemory {
200 Climbing { climbing_velocity: Vector3 },
201 Coyote(Timer),
202}
203
204impl Default for TnuaBuiltinClimbMemory {
205 fn default() -> Self {
206 Self::Climbing {
207 climbing_velocity: Vector3::ZERO,
208 }
209 }
210}