use avian3d::{math::Scalar, prelude::*}; use bevy::prelude::{ App, Component, FixedUpdate, IntoScheduleConfigs, Plugin, ResMut, Resource, Startup, Update, }; const DRAG_COEFF: Scalar = 0.00001; const MAX_THRUST: Scalar = 900.0; #[derive(Resource, Default, Debug)] struct CyberLean { pub lean: Scalar, } #[derive(Debug, Resource)] pub struct CatControllerSettings { pub kp: Scalar, pub kd: Scalar, pub ki: Scalar, } impl Default for CatControllerSettings { fn default() -> Self { Self { kp: 60.0, kd: 30.0, ki: 2.0, } } } #[derive(Component, Debug, Clone, Copy)] pub struct CatControllerState { pub roll_integral: Scalar, pub roll_prev: Scalar, roll_limit: Scalar, } impl Default for CatControllerState { fn default() -> Self { Self { roll_integral: Default::default(), roll_prev: Default::default(), roll_limit: 1.5, } } } impl CatControllerState { pub fn update_roll(&mut self, error: Scalar, dt: Scalar) -> (Scalar, Scalar) { let lim = self.roll_limit; self.roll_integral = (self.roll_integral + (error * dt)).min(lim).max(-lim); let derivative = (error - self.roll_prev) / dt; self.roll_prev = error; (derivative, self.roll_integral) } } mod systems { use avian3d::{ math::{Scalar, FRAC_PI_2, PI}, prelude::{ ComputedCenterOfMass, ExternalForce, ExternalTorque, Gravity, LinearVelocity, RayCaster, RayHits, RigidBodyQueryReadOnly, }, }; use bevy::{ ecs::system::{Populated, Single}, prelude::{ ButtonInput, Color, Gizmos, GlobalTransform, KeyCode, Quat, Res, ResMut, Time, Transform, Vec, Vec3, With, Without, }, }; use super::{CatControllerSettings, CatControllerState, CyberLean, DRAG_COEFF, MAX_THRUST}; use crate::{ bike::{CyberBikeBody, CyberWheel, WheelConfig, WheelState, WHEEL_RADIUS}, input::InputState, }; const FRAC_PI_3: Scalar = PI / 3.0; const FRAC_PI_4: Scalar = FRAC_PI_2 / 2.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]) } pub(super) fn calculate_lean( bike_state: Single<(&LinearVelocity, &Transform), With>, wheels: Populated<&GlobalTransform, With>, input: Res, gravity: Res, mut lean: ResMut, ) { let mut wheels = wheels.iter(); let w1 = wheels.next().unwrap(); let w2 = wheels.next().unwrap(); let base = (w1.translation() - w2.translation()).length().abs(); let (velocity, xform) = bike_state.into_inner(); let vel = velocity.dot(*xform.forward()); let v_squared = vel.powi(2); let steering_angle = yaw_to_angle(input.yaw); let radius = base / steering_angle.tan(); let gravity = gravity.0.length(); let v2_r = v_squared / radius; let tan_theta = (v2_r / gravity).clamp(-FRAC_PI_3, FRAC_PI_3); if tan_theta.is_normal() || tan_theta == 0.0 { lean.lean = tan_theta.atan().clamp(-FRAC_PI_3, FRAC_PI_3); } else if lean.lean.abs() > 0.01 { lean.lean *= 0.5; } } pub(super) fn apply_lean( bike_query: Single<(&Transform, &mut ExternalTorque, &mut CatControllerState)>, wheels: Populated<(&WheelState, &GlobalTransform, &CyberWheel)>, time: Res