1use std::time::Duration;
2
3use bevy::prelude::*;
4use bevy_tnua_physics_integration_layer::data_for_backends::{
5 TnuaGhostSensor, TnuaProximitySensor,
6};
7use serde::{Deserialize, Serialize};
8
9use crate::TnuaBasis;
10use crate::basis_action_traits::TnuaBasisAccess;
11use crate::basis_capabilities::{
12 TnuaBasisWithDisplacement, TnuaBasisWithFloating, TnuaBasisWithFrameOfReferenceSurface,
13 TnuaBasisWithGround, TnuaBasisWithHeadroom, TnuaBasisWithSpring,
14};
15use crate::ghost_overrides::TnuaGhostOverwrite;
16use crate::math::*;
17use crate::sensor_sets::{ProximitySensorPreparationHelper, TnuaSensors};
18use crate::util::rotation_arc_around_axis;
19use crate::{TnuaBasisContext, TnuaMotor, TnuaVelChange};
20
21use super::walk_sensors::TnuaBuiltinWalkSensors;
22
23#[derive(Default)]
24#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
25pub struct TnuaBuiltinWalk {
26 pub desired_motion: Vector3,
30
31 pub desired_forward: Option<Dir3>,
36}
37
38#[derive(Clone, Serialize, Deserialize)]
39pub struct TnuaBuiltinWalkConfig {
40 pub speed: Float,
49
50 pub float_height: Float,
58
59 pub headroom: Option<TnuaBuiltinWalkHeadroom>,
64
65 pub cling_distance: Float,
72
73 pub spring_strength: Float,
78
79 pub spring_dampening: Float,
87
88 pub acceleration: Float,
94
95 pub air_acceleration: Float,
99
100 pub coyote_time: Float,
102
103 pub free_fall_extra_gravity: Float,
112
113 pub tilt_offset_angvel: Float,
118
119 pub tilt_offset_angacl: Float,
124
125 pub turning_angvel: Float,
127
128 pub max_slope: Float,
130}
131
132#[derive(Clone, Serialize, Deserialize)]
135pub struct TnuaBuiltinWalkHeadroom {
136 pub distance_to_collider_top: Float,
138
139 pub sensor_extra_distance: Float,
145}
146
147impl Default for TnuaBuiltinWalkHeadroom {
148 fn default() -> Self {
149 Self {
150 distance_to_collider_top: 0.0,
151 sensor_extra_distance: 0.1,
152 }
153 }
154}
155
156impl Default for TnuaBuiltinWalkConfig {
157 fn default() -> Self {
158 Self {
159 speed: 20.0,
160 float_height: 0.0,
161 headroom: None,
162 cling_distance: 1.0,
163 spring_strength: 400.0,
164 spring_dampening: 1.2,
165 acceleration: 60.0,
166 air_acceleration: 20.0,
167 coyote_time: 30.15,
168 free_fall_extra_gravity: 60.0,
169 tilt_offset_angvel: 5.0,
170 tilt_offset_angacl: 500.0,
171 turning_angvel: 10.0,
172 max_slope: float_consts::FRAC_PI_2,
173 }
174 }
175}
176
177#[derive(Debug, Clone)]
178#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
179struct StandingOnState {
180 entity: Entity,
181 entity_linvel: Vector3,
182}
183
184#[derive(Default, Debug)]
185#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
186pub struct TnuaBuiltinWalkMemory {
187 airborne_timer: Option<Timer>,
188 pub standing_offset: Vector3,
190 standing_on: Option<StandingOnState>,
191 effective_velocity: Vector3,
192 vertical_velocity: Float,
193 pub running_velocity: Vector3,
199 extra_headroom: Float,
200}
201
202impl TnuaBuiltinWalkMemory {
204 pub fn standing_on_entity(&self) -> Option<Entity> {
206 Some(self.standing_on.as_ref()?.entity)
207 }
208}
209
210impl TnuaBasis for TnuaBuiltinWalk {
211 type Config = TnuaBuiltinWalkConfig;
212
213 type Memory = TnuaBuiltinWalkMemory;
214
215 type Sensors<'a> = TnuaBuiltinWalkSensors<'a>;
216
217 fn apply(
218 &self,
219 config: &Self::Config,
220 memory: &mut Self::Memory,
221 sensors: &Self::Sensors<'_>,
222 ctx: TnuaBasisContext,
223 motor: &mut TnuaMotor,
224 ) {
225 if let Some(stopwatch) = &mut memory.airborne_timer {
226 #[allow(clippy::unnecessary_cast)]
227 stopwatch.tick(Duration::from_secs_f64(ctx.frame_duration as f64));
228 }
229
230 memory.extra_headroom = 0.0;
233
234 let climb_vectors: Option<ClimbVectors>;
235 let considered_in_air: bool;
236 let impulse_to_offset: Vector3;
237 let slipping_vector: Option<Vector3>;
238
239 if let Some(sensor_output) = &sensors.ground.output {
240 memory.effective_velocity = ctx.tracker.velocity - sensor_output.entity_linvel;
241 let sideways_unnormalized = sensor_output
242 .normal
243 .cross(*ctx.up_direction)
244 .adjust_precision();
245 if sideways_unnormalized == Vector3::ZERO {
246 climb_vectors = None;
247 } else {
248 climb_vectors = Some(ClimbVectors {
249 direction: sideways_unnormalized
250 .cross(sensor_output.normal.adjust_precision())
251 .normalize_or_zero()
252 .adjust_precision(),
253 sideways: sideways_unnormalized.normalize_or_zero().adjust_precision(),
254 });
255 }
256
257 slipping_vector = {
258 let angle_with_floor = sensor_output
259 .normal
260 .angle_between(*ctx.up_direction)
261 .adjust_precision();
262 if angle_with_floor <= config.max_slope {
263 None
264 } else {
265 Some(
266 sensor_output
267 .normal
268 .reject_from(*ctx.up_direction)
269 .adjust_precision(),
270 )
271 }
272 };
273
274 if memory.airborne_timer.is_some() {
275 considered_in_air = true;
276 impulse_to_offset = Vector3::ZERO;
277 memory.standing_on = None;
278 } else {
279 if let Some(standing_on_state) = &memory.standing_on {
280 if standing_on_state.entity != sensor_output.entity {
281 impulse_to_offset = Vector3::ZERO;
282 } else {
283 impulse_to_offset =
284 sensor_output.entity_linvel - standing_on_state.entity_linvel;
285 }
286 } else {
287 impulse_to_offset = Vector3::ZERO;
288 }
289
290 if slipping_vector.is_none() {
291 considered_in_air = false;
292 memory.standing_on = Some(StandingOnState {
293 entity: sensor_output.entity,
294 entity_linvel: sensor_output.entity_linvel,
295 });
296 } else {
297 considered_in_air = true;
298 memory.standing_on = None;
299 }
300 }
301 } else {
302 memory.effective_velocity = ctx.tracker.velocity;
303 climb_vectors = None;
304 considered_in_air = true;
305 impulse_to_offset = Vector3::ZERO;
306 slipping_vector = None;
307 memory.standing_on = None;
308 }
309 memory.effective_velocity += impulse_to_offset;
310
311 let velocity_on_plane = memory
312 .effective_velocity
313 .reject_from(ctx.up_direction.adjust_precision());
314
315 let desired_velocity = self.desired_motion * config.speed;
316
317 let desired_boost = desired_velocity - velocity_on_plane;
318
319 let safe_direction_coefficient = desired_velocity
320 .normalize_or_zero()
321 .dot(velocity_on_plane.normalize_or_zero());
322 let direction_change_factor = 1.5 - 0.5 * safe_direction_coefficient;
323
324 let relevant_acceleration_limit = if considered_in_air {
325 config.air_acceleration
326 } else {
327 config.acceleration
328 };
329 let max_acceleration = direction_change_factor * relevant_acceleration_limit;
330
331 memory.vertical_velocity = if let Some(climb_vectors) = &climb_vectors {
332 memory.effective_velocity.dot(climb_vectors.direction)
333 * climb_vectors
334 .direction
335 .dot(ctx.up_direction.adjust_precision())
336 } else {
337 0.0
338 };
339
340 let walk_vel_change = if desired_velocity == Vector3::ZERO && slipping_vector.is_none() {
341 let walk_boost = desired_boost.clamp_length_max(ctx.frame_duration * max_acceleration);
343 let walk_boost = if let Some(climb_vectors) = &climb_vectors {
344 climb_vectors.project(walk_boost)
345 } else {
346 walk_boost
347 };
348 TnuaVelChange::boost(walk_boost)
349 } else {
350 let walk_acceleration =
353 (desired_boost / ctx.frame_duration).clamp_length_max(max_acceleration);
354 let walk_acceleration =
355 if let (Some(climb_vectors), None) = (&climb_vectors, slipping_vector) {
356 climb_vectors.project(walk_acceleration)
357 } else {
358 walk_acceleration
359 };
360
361 let slipping_boost = 'slipping_boost: {
362 let Some(slipping_vector) = slipping_vector else {
363 break 'slipping_boost Vector3::ZERO;
364 };
365 let vertical_velocity = if 0.0 <= memory.vertical_velocity {
366 ctx.tracker.gravity.dot(ctx.up_direction.adjust_precision())
367 * ctx.frame_duration
368 } else {
369 memory.vertical_velocity
370 };
371
372 let Ok((slipping_direction, slipping_per_vertical_unit)) =
373 Dir3::new_and_length(slipping_vector.f32())
374 else {
375 break 'slipping_boost Vector3::ZERO;
376 };
377
378 let required_veloicty_in_slipping_direction =
379 slipping_per_vertical_unit.adjust_precision() * -vertical_velocity;
380 let expected_velocity = velocity_on_plane + walk_acceleration * ctx.frame_duration;
381 let expected_velocity_in_slipping_direction =
382 expected_velocity.dot(slipping_direction.adjust_precision());
383
384 let diff = required_veloicty_in_slipping_direction
385 - expected_velocity_in_slipping_direction;
386
387 if diff <= 0.0 {
388 break 'slipping_boost Vector3::ZERO;
389 }
390
391 slipping_direction.adjust_precision() * diff
392 };
393 TnuaVelChange {
394 acceleration: walk_acceleration,
395 boost: slipping_boost,
396 }
397 };
398
399 let upward_impulse: TnuaVelChange = 'upward_impulse: {
400 let should_disable_due_to_slipping =
401 slipping_vector.is_some() && memory.vertical_velocity <= 0.0;
402 for _ in 0..2 {
403 #[allow(clippy::unnecessary_cast)]
404 match &mut memory.airborne_timer {
405 None => {
406 if let (false, Some(sensor_output)) =
407 (should_disable_due_to_slipping, &sensors.ground.output)
408 {
409 let spring_offset =
411 config.float_height - sensor_output.proximity.adjust_precision();
412 memory.standing_offset =
413 -spring_offset * ctx.up_direction.adjust_precision();
414 break 'upward_impulse Self::spring_force(
415 &TnuaBasisAccess {
416 input: self,
417 config,
418 memory,
419 },
420 &ctx,
421 spring_offset,
422 );
423 } else {
424 memory.airborne_timer = Some(Timer::from_seconds(
425 config.coyote_time as f32,
426 TimerMode::Once,
427 ));
428 continue;
429 }
430 }
431 Some(_) => {
432 if let (false, Some(sensor_output)) =
433 (should_disable_due_to_slipping, &sensors.ground.output)
434 && sensor_output.proximity.adjust_precision() <= config.float_height
435 {
436 memory.airborne_timer = None;
437 continue;
438 }
439 if memory.vertical_velocity <= 0.0 {
440 break 'upward_impulse TnuaVelChange::acceleration(
441 -config.free_fall_extra_gravity
442 * ctx.up_direction.adjust_precision(),
443 );
444 } else {
445 break 'upward_impulse TnuaVelChange::ZERO;
446 }
447 }
448 }
449 }
450 error!("Tnua could not decide on jump state");
451 TnuaVelChange::ZERO
452 };
453
454 motor.lin = walk_vel_change + TnuaVelChange::boost(impulse_to_offset) + upward_impulse;
455 let new_velocity = memory.effective_velocity
456 + motor.lin.boost
457 + ctx.frame_duration * motor.lin.acceleration
458 - impulse_to_offset;
459 memory.running_velocity = new_velocity.reject_from(ctx.up_direction.adjust_precision());
460
461 let torque_to_fix_tilt = {
464 let tilted_up = ctx.tracker.rotation.mul_vec3(Vector3::Y);
465
466 let rotation_required_to_fix_tilt =
467 Quaternion::from_rotation_arc(tilted_up, ctx.up_direction.adjust_precision());
468
469 let desired_angvel = (rotation_required_to_fix_tilt.xyz() / ctx.frame_duration)
470 .clamp_length_max(config.tilt_offset_angvel);
471 let angular_velocity_diff = desired_angvel - ctx.tracker.angvel;
472 angular_velocity_diff.clamp_length_max(ctx.frame_duration * config.tilt_offset_angacl)
473 };
474
475 let desired_angvel = if let Some(desired_forward) = self.desired_forward {
478 let current_forward = ctx.tracker.rotation.mul_vec3(Vector3::NEG_Z);
479 let rotation_along_up_axis = rotation_arc_around_axis(
480 ctx.up_direction,
481 current_forward,
482 desired_forward.adjust_precision(),
483 )
484 .unwrap_or(0.0);
485 (rotation_along_up_axis / ctx.frame_duration)
486 .clamp(-config.turning_angvel, config.turning_angvel)
487 } else {
488 0.0
489 };
490
491 let existing_angvel = ctx.tracker.angvel.dot(ctx.up_direction.adjust_precision());
493
494 let torque_to_turn = desired_angvel - existing_angvel;
497
498 let existing_turn_torque = torque_to_fix_tilt.dot(ctx.up_direction.adjust_precision());
499 let torque_to_turn = torque_to_turn - existing_turn_torque;
500
501 motor.ang = TnuaVelChange::boost(
502 torque_to_fix_tilt + torque_to_turn * ctx.up_direction.adjust_precision(),
503 );
504 }
505
506 fn get_or_create_sensors<'a: 'b, 'b>(
507 up_direction: Dir3,
508 config: &'a Self::Config,
509 memory: &Self::Memory,
510 entities: &'a mut <Self::Sensors<'static> as TnuaSensors<'static>>::Entities,
511 proximity_sensors_query: &'b Query<(&TnuaProximitySensor, Has<TnuaGhostSensor>)>,
512 controller_entity: Entity,
513 commands: &mut Commands,
514 has_ghost_overwrites: bool,
515 ) -> Option<Self::Sensors<'b>> {
516 let ground = ProximitySensorPreparationHelper {
517 cast_direction: -up_direction,
518 cast_range: config.float_height + config.cling_distance,
519 ghost_sensor: has_ghost_overwrites,
520 ..Default::default()
521 }
522 .prepare_for(
523 &mut entities.ground,
524 proximity_sensors_query,
525 controller_entity,
526 commands,
527 );
528
529 let headroom = if let Some(headroom) = config.headroom.as_ref() {
530 ProximitySensorPreparationHelper {
531 cast_direction: up_direction,
532 cast_range: headroom.distance_to_collider_top
533 + headroom.sensor_extra_distance
534 + memory.extra_headroom,
535 ..Default::default()
536 }
537 .prepare_for(
538 &mut entities.headroom,
539 proximity_sensors_query,
540 controller_entity,
541 commands,
542 )
543 } else {
544 ProximitySensorPreparationHelper::ensure_not_existing(
545 &mut entities.headroom,
546 proximity_sensors_query,
547 commands,
548 )
549 };
550 Some(Self::Sensors {
553 ground: ground?,
554 headroom,
555 })
556 }
557
558 fn ghost_sensor_overwrites<'a>(
559 ghost_overwrites: &'a mut <Self::Sensors<'static> as TnuaSensors<'static>>::GhostOverwrites,
560 entities: &<Self::Sensors<'static> as TnuaSensors<'static>>::Entities,
561 ) -> impl Iterator<Item = (&'a mut TnuaGhostOverwrite, Entity)> {
562 [(&mut ghost_overwrites.ground, entities.ground)]
563 .into_iter()
564 .flat_map(|(o, e)| Some((o, e?)))
565 }
566}
567
568impl TnuaBasisWithFrameOfReferenceSurface for TnuaBuiltinWalk {
569 fn effective_velocity(access: &TnuaBasisAccess<Self>) -> Vector3 {
570 access.memory.effective_velocity
571 }
572
573 fn vertical_velocity(access: &TnuaBasisAccess<Self>) -> Float {
574 access.memory.vertical_velocity
575 }
576}
577impl TnuaBasisWithDisplacement for TnuaBuiltinWalk {
578 fn displacement(access: &TnuaBasisAccess<Self>) -> Option<Vector3> {
579 match access.memory.airborne_timer {
580 None => Some(access.memory.standing_offset),
581 Some(_) => None,
582 }
583 }
584}
585impl TnuaBasisWithGround for TnuaBuiltinWalk {
586 fn is_airborne(access: &TnuaBasisAccess<Self>) -> bool {
587 access
588 .memory
589 .airborne_timer
590 .as_ref()
591 .is_some_and(|timer| timer.is_finished())
592 }
593
594 fn violate_coyote_time(memory: &mut Self::Memory) {
595 if let Some(timer) = &mut memory.airborne_timer {
596 timer.set_duration(Duration::ZERO);
597 }
598 }
599
600 fn ground_sensor<'a>(sensors: &Self::Sensors<'a>) -> &'a TnuaProximitySensor {
601 sensors.ground
602 }
603}
604impl TnuaBasisWithHeadroom for TnuaBuiltinWalk {
605 fn headroom_intrusion<'a>(
606 access: &TnuaBasisAccess<Self>,
607 sensors: &Self::Sensors<'a>,
608 ) -> Option<std::ops::Range<Float>> {
609 let headroom_config = access.config.headroom.as_ref()?;
610 let headroom_sensor_output = sensors.headroom?.output.as_ref()?;
611 Some(headroom_config.distance_to_collider_top..headroom_sensor_output.proximity)
612 }
613
614 fn set_extra_headroom(memory: &mut Self::Memory, extra_headroom: Float) {
615 memory.extra_headroom = extra_headroom.max(0.0);
616 }
617}
618impl TnuaBasisWithFloating for TnuaBuiltinWalk {
619 fn float_height(access: &TnuaBasisAccess<Self>) -> Float {
620 access.config.float_height
621 }
622}
623impl TnuaBasisWithSpring for TnuaBuiltinWalk {
624 fn spring_force(
625 access: &TnuaBasisAccess<Self>,
626 ctx: &TnuaBasisContext,
627 spring_offset: Float,
628 ) -> TnuaVelChange {
629 let spring_force: Float = spring_offset * access.config.spring_strength;
630
631 let relative_velocity = access
632 .memory
633 .effective_velocity
634 .dot(ctx.up_direction.adjust_precision())
635 - access.memory.vertical_velocity;
636
637 let gravity_compensation = -ctx.tracker.gravity;
638
639 let dampening_boost = relative_velocity * access.config.spring_dampening;
640
641 TnuaVelChange {
642 acceleration: ctx.up_direction.adjust_precision() * spring_force + gravity_compensation,
643 boost: ctx.up_direction.adjust_precision() * -dampening_boost,
644 }
645 }
646}
647
648#[derive(Debug, Clone)]
649struct ClimbVectors {
650 direction: Vector3,
651 sideways: Vector3,
652}
653
654impl ClimbVectors {
655 fn project(&self, vector: Vector3) -> Vector3 {
656 let axis_direction = vector.dot(self.direction) * self.direction;
657 let axis_sideways = vector.dot(self.sideways) * self.sideways;
658 axis_direction + axis_sideways
659 }
660}