bevy_tnua/util/
velocity_boundary.rs1use std::time::Duration;
2
3use crate::math::{AdjustPrecision, AsF32, Float, Vector3};
4use bevy::prelude::*;
5
6#[derive(Clone, Debug)]
9#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
10pub struct VelocityBoundary {
11 base: Float,
12 original_frontier: Float,
13 frontier: Float,
14 pub direction: Dir3,
15 no_push_timer: Timer,
16}
17
18impl VelocityBoundary {
19 pub fn new(
20 disruption_from: Vector3,
21 disruption_to: Vector3,
22 no_push_timeout: f32,
23 ) -> Option<Self> {
24 let Ok(disruption_direction) = Dir3::new((disruption_to - disruption_from).f32()) else {
25 return None;
26 };
27 let frontier = disruption_to.dot(disruption_direction.adjust_precision());
28 Some(Self {
29 base: disruption_from.dot(disruption_direction.adjust_precision()),
30 original_frontier: frontier,
31 frontier,
32 direction: disruption_direction,
33 no_push_timer: Timer::from_seconds(no_push_timeout, TimerMode::Once),
34 })
35 }
36
37 pub fn update(&mut self, velocity: Vector3, frame_duration: Duration) {
56 let new_frontier = velocity.dot(self.direction.adjust_precision());
57 if new_frontier < self.frontier {
58 self.frontier = new_frontier;
59 self.no_push_timer.reset();
60 } else {
61 self.no_push_timer.tick(frame_duration);
62 }
63 }
64
65 pub fn is_cleared(&self) -> bool {
66 self.no_push_timer.is_finished() || self.frontier <= self.base
67 }
68
69 pub fn calc_boost_part_on_boundary_axis_after_limit(
89 &self,
90 current_velocity: Vector3,
91 regular_boost: Vector3,
92 boost_limit_inside_barrier: Float,
93 barrier_strength_diminishing: Float,
94 ) -> Option<(Dir3, Float)> {
95 let boost = regular_boost.dot(self.direction.adjust_precision());
96 if 0.0 <= boost {
97 return None;
99 }
100 let current = current_velocity.dot(self.direction.adjust_precision());
101 let after_boost = current + boost;
102 if self.frontier <= after_boost {
103 return None;
104 }
105 let boost_before_barrier = (current - self.frontier).max(0.0);
106 let fraction_before_frontier = boost_before_barrier / -boost;
107 let fraction_after_frontier = 1.0 - fraction_before_frontier;
108 let push_inside_barrier = fraction_after_frontier * boost_limit_inside_barrier;
109 let barrier_depth = self.frontier - self.base;
110 if barrier_depth <= 0.0 {
111 return None;
112 }
113 let fraction_inside_barrier = if push_inside_barrier <= barrier_depth {
114 fraction_after_frontier
115 } else {
116 barrier_depth / boost_limit_inside_barrier
117 }
118 .clamp(0.0, 1.0);
119
120 let boost_outside_barrier = (1.0 - fraction_inside_barrier) * boost;
121 let boost_inside_barrier = fraction_inside_barrier * -boost_limit_inside_barrier;
123
124 let total_boost = boost_outside_barrier + boost_inside_barrier;
125
126 let barrier_strength = self.percentage_left().powf(barrier_strength_diminishing);
127 let total_boost = (1.0 - barrier_strength) * boost + barrier_strength * total_boost;
128
129 Some((-self.direction, -total_boost))
130 }
131
132 fn percentage_left(&self) -> Float {
133 let current_depth = self.frontier - self.base;
134 let original_depth = self.original_frontier - self.base;
135 current_depth / original_depth
136 }
137}