cyber_rider/src/action/systems.rs
2024-07-15 17:30:26 -07:00

148 lines
4.9 KiB
Rust

use std::f32::consts::{FRAC_PI_3, FRAC_PI_4};
use avian3d::{
dynamics::solver::xpbd::AngularConstraint, parry::mass_properties::MassProperties, prelude::*,
};
use bevy::prelude::{
Commands, Entity, Quat, Query, Res, ResMut, Time, Transform, Vec3, With, Without,
};
//#[cfg(feature = "inspector")]
//use super::ActionDebugInstant;
use super::{CatControllerSettings, CatControllerState, CyberLean, MovementSettings, Tunneling};
use crate::{
bike::{CyberBikeBody, CyberSteering, CyberWheel, WheelConfig, BIKE_WHEEL_COLLISION_GROUP},
input::InputState,
};
fn yaw_to_angle(yaw: f32) -> f32 {
let v = yaw.powi(5) * FRAC_PI_4;
if v.is_normal() {
v
} else {
0.0
}
}
fn rotate_point(pt: &Vec3, rot: &Quat) -> Vec3 {
// thanks to https://danceswithcode.net/engineeringnotes/quaternions/quaternions.html
let [x, y, z] = pt.normalize().to_array();
let qpt = Quat::from_xyzw(x, y, z, 0.0);
// p' = rot^-1 * qpt * rot
let rot_qpt = rot.inverse() * qpt * *rot;
// why does this need to be inverted???
-Vec3::from_array([rot_qpt.x, rot_qpt.y, rot_qpt.z])
}
/// The gravity vector points from the cyberbike to the center of the planet.
pub(super) fn gravity(
mut query: Query<(&Transform, &mut ExternalForce), With<CyberBikeBody>>,
settings: Res<MovementSettings>,
mut gravity: ResMut<Gravity>,
#[cfg(feature = "inspector")] mut debug_instant: ResMut<ActionDebugInstant>,
) {
let (xform, mut forces) = query.single_mut();
*gravity = Gravity(xform.translation.normalize() * -settings.gravity);
forces.clear();
}
/// The desired lean angle, given steering input and speed.
pub(super) fn cyber_lean(
bike_state: Query<(&LinearVelocity, &Transform), With<CyberBikeBody>>,
wheels: Query<&Transform, With<CyberWheel>>,
input: Res<InputState>,
gravity_settings: Res<MovementSettings>,
mut lean: ResMut<CyberLean>,
) {
let (velocity, xform) = bike_state.single();
let vel = velocity.dot(*xform.forward());
let v_squared = vel.powi(2);
let steering_angle = yaw_to_angle(input.yaw);
let wheels: Vec<_> = wheels.iter().map(|w| w.translation).collect();
let wheel_base = (wheels[0] - wheels[1]).length();
let radius = wheel_base / steering_angle.tan();
let gravity = gravity_settings.gravity;
let v2_r = v_squared / radius;
let tan_theta = (v2_r / gravity).clamp(-FRAC_PI_3, FRAC_PI_3);
if tan_theta.is_finite() && !tan_theta.is_subnormal() {
lean.lean = tan_theta.atan().clamp(-FRAC_PI_4, FRAC_PI_4);
} else {
lean.lean = 0.0;
}
}
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
pub(super) fn falling_cat(
mut bike_query: Query<(&Transform, &mut ExternalTorque, &mut CatControllerState)>,
time: Res<Time>,
settings: Res<CatControllerSettings>,
lean: Res<CyberLean>,
) {
let (xform, mut torque, mut control_vars) = bike_query.single_mut();
let world_up = xform.translation.normalize();
let rot = Quat::from_axis_angle(*xform.back(), lean.lean);
let target_up = rotate_point(&world_up, &rot).normalize();
let bike_right = xform.right();
let roll_error = bike_right.dot(target_up);
let pitch_error = world_up.dot(*xform.back());
// only try to correct roll if we're not totally vertical
if pitch_error.abs() < 0.95 {
let (derivative, integral) = control_vars.update_roll(roll_error, time.delta_seconds());
let mag =
(settings.kp * roll_error) + (settings.ki * integral) + (settings.kd * derivative);
if mag.is_finite() {
torque.apply_torque(*xform.back() * mag);
}
}
}
/// Apply forces to the cyberbike as a result of input.
pub(super) fn input_forces(
settings: Res<MovementSettings>,
input: Res<InputState>,
time: Res<Time>,
axle: Query<Entity, With<CyberSteering>>,
mut braking_query: Query<&mut ExternalTorque, With<CyberWheel>>,
mut body_query: Query<
(
Entity,
&Transform,
&ColliderMassProperties,
&mut ExternalForce,
),
With<CyberBikeBody>,
>,
) {
let (bike, xform, mass, mut forces) = body_query.single_mut();
let dt = time.delta_seconds();
// thrust
let thrust = xform.forward() * input.throttle * settings.accel;
let point = xform.translation + *xform.back();
*forces.apply_force_at_point(dt * thrust, point, mass.center_of_mass.0);
// brake + thrust
for mut motor in braking_query.iter_mut() {
let factor = if input.brake {
500.00
} else {
input.throttle * settings.accel
};
let speed = if input.brake { 0.0 } else { -70.0 };
let target = dt * factor * speed;
let torque = target * Vec3::X;
motor.apply_torque(torque);
}
// steering
let _angle = yaw_to_angle(input.yaw);
let _axle = axle.single();
//steering.align_orientation(bike, steering, rotation_difference, lagrange,
// compliance, dt)
}