bevy_tnua/builtins/
crouch.rs1use bevy::time::Stopwatch;
2use serde::{Deserialize, Serialize};
3
4use crate::basis_capabilities::{
5 TnuaBasisWithFloating, TnuaBasisWithGround, TnuaBasisWithHeadroom, TnuaBasisWithSpring,
6};
7use crate::{TnuaAction, TnuaActionContext, TnuaBasis};
8use crate::{
9 TnuaActionInitiationDirective, TnuaActionLifecycleDirective, TnuaActionLifecycleStatus, math::*,
10};
11use crate::{TnuaMotor, TnuaVelChange};
12
13#[derive(Debug, Default)]
14#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
15pub struct TnuaBuiltinCrouch;
16
17#[derive(Clone, Serialize, Deserialize)]
18pub struct TnuaBuiltinCrouchConfig {
19 pub float_offset: Float,
26
27 pub height_change_impulse_for_duration: Float,
35
36 pub height_change_impulse_limit: Float,
38}
39
40impl Default for TnuaBuiltinCrouchConfig {
41 fn default() -> Self {
42 Self {
43 float_offset: 0.0,
44 height_change_impulse_for_duration: 0.02,
45 height_change_impulse_limit: 40.0,
46 }
47 }
48}
49
50#[derive(Default, Debug)]
51#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
52pub enum TnuaBuiltinCrouchMemory {
53 #[default]
55 Sinking,
56 Maintaining,
58 Rising,
60}
61
62impl<B: TnuaBasis> TnuaAction<B> for TnuaBuiltinCrouch
63where
64 B: TnuaBasisWithFloating,
65 B: TnuaBasisWithSpring,
66 B: TnuaBasisWithGround,
67 B: TnuaBasisWithHeadroom,
68{
69 type Config = TnuaBuiltinCrouchConfig;
70 type Memory = TnuaBuiltinCrouchMemory;
71
72 fn initiation_decision(
73 &self,
74 _config: &Self::Config,
75 sensors: &B::Sensors<'_>,
76 _ctx: TnuaActionContext<B>,
77 _being_fed_for: &Stopwatch,
78 ) -> TnuaActionInitiationDirective {
79 if B::ground_sensor(sensors).output.is_some() {
80 TnuaActionInitiationDirective::Allow
81 } else {
82 TnuaActionInitiationDirective::Delay
83 }
84 }
85
86 fn apply(
87 &self,
88 config: &Self::Config,
89 memory: &mut Self::Memory,
90 sensors: &B::Sensors<'_>,
91 ctx: TnuaActionContext<B>,
92 lifecycle_status: TnuaActionLifecycleStatus,
93 motor: &mut TnuaMotor,
94 ) -> crate::TnuaActionLifecycleDirective {
95 let Some(sensor_output) = &B::ground_sensor(sensors).output else {
96 return TnuaActionLifecycleDirective::Reschedule { after_seconds: 0.0 };
97 };
98 let spring_offset_up =
99 B::float_height(ctx.basis) - sensor_output.proximity.adjust_precision();
100 let spring_offset_down =
101 spring_offset_up.adjust_precision() + config.float_offset.adjust_precision();
102
103 match lifecycle_status {
104 TnuaActionLifecycleStatus::Initiated => {}
105 TnuaActionLifecycleStatus::CancelledFrom => {}
106 TnuaActionLifecycleStatus::StillFed => {}
107 TnuaActionLifecycleStatus::NoLongerFed => {
108 *memory = Self::Memory::Rising;
109 }
110 TnuaActionLifecycleStatus::CancelledInto => {
111 *memory = TnuaBuiltinCrouchMemory::Rising;
112 }
113 }
114
115 let can_stand = B::headroom_intrusion(ctx.basis, sensors)
116 .map(|headroom_intrusion| {
117 spring_offset_up < headroom_intrusion.end - headroom_intrusion.start
118 })
119 .unwrap_or(true);
120
121 if !can_stand && matches!(memory, TnuaBuiltinCrouchMemory::Rising) {
122 *memory = TnuaBuiltinCrouchMemory::Maintaining;
123 }
124
125 let spring_force = |spring_offset: Float| -> TnuaVelChange {
126 B::spring_force(ctx.basis, &ctx.as_basis_context(), spring_offset)
127 };
128
129 let impulse_or_spring_force = |spring_offset: Float| -> TnuaVelChange {
130 let spring_force = spring_force(spring_offset);
131 let spring_force_boost = crate::util::calc_boost(&spring_force, ctx.frame_duration);
132 let impulse_boost = config.impulse_boost(spring_offset);
133 if spring_force_boost.length_squared() < impulse_boost.powi(2) {
134 TnuaVelChange::boost(impulse_boost * ctx.up_direction.adjust_precision())
135 } else {
136 spring_force
137 }
138 };
139
140 let mut set_vel_change = |vel_change: TnuaVelChange| {
141 motor
142 .lin
143 .cancel_on_axis(ctx.up_direction.adjust_precision());
144 motor.lin += vel_change;
145 };
146
147 match memory {
148 TnuaBuiltinCrouchMemory::Sinking => {
149 if spring_offset_down < -0.01 {
150 set_vel_change(impulse_or_spring_force(spring_offset_down));
151 } else {
152 *memory = TnuaBuiltinCrouchMemory::Maintaining;
153 set_vel_change(spring_force(spring_offset_down));
154 }
155 lifecycle_status.directive_simple()
156 }
157 TnuaBuiltinCrouchMemory::Maintaining => {
158 set_vel_change(spring_force(spring_offset_down));
159 TnuaActionLifecycleDirective::StillActive
161 }
162 TnuaBuiltinCrouchMemory::Rising => {
163 if 0.01 < spring_offset_up {
164 set_vel_change(impulse_or_spring_force(spring_offset_up));
165
166 if matches!(lifecycle_status, TnuaActionLifecycleStatus::CancelledInto) {
167 TnuaActionLifecycleDirective::Reschedule { after_seconds: 0.0 }
169 } else {
170 TnuaActionLifecycleDirective::StillActive
172 }
173 } else {
174 TnuaActionLifecycleDirective::Finished
175 }
176 }
177 }
178 }
179
180 fn influence_basis(
181 &self,
182 config: &Self::Config,
183 _memory: &Self::Memory,
184 _ctx: crate::TnuaBasisContext,
185 _basis_input: &B,
186 _basis_config: &<B as TnuaBasis>::Config,
187 basis_memory: &mut <B as TnuaBasis>::Memory,
188 ) {
189 B::set_extra_headroom(basis_memory, -config.float_offset);
190 }
191}
192
193impl TnuaBuiltinCrouchConfig {
194 fn impulse_boost(&self, spring_offset: Float) -> Float {
195 let velocity_to_get_to_new_float_height =
196 spring_offset / self.height_change_impulse_for_duration;
197 velocity_to_get_to_new_float_height.clamp(
198 -self.height_change_impulse_limit,
199 self.height_change_impulse_limit,
200 )
201 }
202}
203
204