mod command_impl_helpers;
mod velocity_boundary;
use bevy::prelude::*;
use bevy_tnua_physics_integration_layer::{
data_for_backends::TnuaVelChange,
math::{AdjustPrecision, Float, Quaternion, Vector2, Vector3},
};
pub use command_impl_helpers::MotionHelper;
pub use velocity_boundary::VelocityBoundary;
pub struct SegmentedJumpInitialVelocityCalculator {
height: Float,
kinetic_energy: Float,
}
#[derive(thiserror::Error, Debug)]
#[error("Engergy or velocity retrived while not all height was coverted")]
pub struct LeftoverHeight;
impl SegmentedJumpInitialVelocityCalculator {
pub fn new(total_height: Float) -> Self {
Self {
height: total_height,
kinetic_energy: 0.0,
}
}
pub fn add_segment(&mut self, gravity: Float, velocity_threshold: Float) -> &mut Self {
if self.height <= 0.0 {
return self;
}
let kinetic_energy_at_velocity_threshold = 0.5 * velocity_threshold.powi(2);
let transferred_energy = kinetic_energy_at_velocity_threshold - self.kinetic_energy;
if transferred_energy <= 0.0 {
return self;
}
let segment_height = transferred_energy / gravity;
if self.height < segment_height {
self.add_final_segment(gravity);
} else {
self.kinetic_energy += transferred_energy;
self.height -= segment_height;
}
self
}
pub fn add_final_segment(&mut self, gravity: Float) -> &mut Self {
self.kinetic_energy += self.height * gravity;
self.height = 0.0;
self
}
pub fn kinetic_energy(&self) -> Result<Float, LeftoverHeight> {
if 0.0 < self.height {
Err(LeftoverHeight)
} else {
Ok(self.kinetic_energy)
}
}
pub fn kinetic_energy_to_velocity(kinetic_energy: Float) -> Float {
(2.0 * kinetic_energy).sqrt()
}
pub fn required_initial_velocity(&self) -> Result<Float, LeftoverHeight> {
Ok(Self::kinetic_energy_to_velocity(self.kinetic_energy()?))
}
}
pub struct SegmentedJumpDurationCalculator {
velocity: Float,
duration: Float,
}
impl SegmentedJumpDurationCalculator {
pub fn new(initial_velocity: Float) -> Self {
Self {
velocity: initial_velocity,
duration: 0.0,
}
}
pub fn add_segment(&mut self, gravity: Float, velocity_threshold: Float) -> &mut Self {
if velocity_threshold < self.velocity {
let lost_velocity = self.velocity - velocity_threshold;
self.velocity = velocity_threshold;
self.duration += lost_velocity / gravity;
}
self
}
pub fn duration(&self) -> Float {
self.duration
}
}
pub fn rotation_arc_around_axis(
around_axis: Dir3,
current_forward: Vector3,
desired_forward: Vector3,
) -> Option<Float> {
let around_axis: Vector3 = around_axis.adjust_precision();
let rotation_plane_x = current_forward.reject_from(around_axis).try_normalize()?;
let rotation_plane_y = around_axis.cross(rotation_plane_x);
let desired_forward_in_plane_coords = Vector2::new(
rotation_plane_x.dot(desired_forward),
rotation_plane_y.dot(desired_forward),
)
.try_normalize()?;
let rotation_to_set_forward =
Quaternion::from_rotation_arc_2d(Vector2::X, desired_forward_in_plane_coords);
Some(rotation_to_set_forward.xyz().z)
}
pub(crate) fn calc_boost(
vel_change: &bevy_tnua_physics_integration_layer::data_for_backends::TnuaVelChange,
frame_duration: Float,
) -> Vector3 {
vel_change.acceleration * frame_duration + vel_change.boost
}
pub fn calc_angular_velchange_to_force_forward(
force_forward: Dir3,
current_rotation: Quaternion,
current_angvel: Vector3,
up_direction: Dir3,
frame_duration: Float,
) -> TnuaVelChange {
let current_forward = current_rotation.mul_vec3(Vector3::NEG_Z);
let rotation_along_up_axis = rotation_arc_around_axis(
up_direction,
current_forward,
force_forward.adjust_precision(),
)
.unwrap_or(0.0);
let desired_angvel = rotation_along_up_axis / frame_duration;
let existing_angvel = current_angvel.dot(up_direction.adjust_precision());
let torque_to_turn = desired_angvel - existing_angvel;
TnuaVelChange::boost(torque_to_turn * up_direction.adjust_precision())
}