use avian3d::prelude::*; use bevy::prelude::*; #[derive(Resource, Default, Debug, Reflect)] #[reflect(Resource)] struct CyberLean { pub lean: f32, } #[derive(Debug, Resource, Reflect)] #[reflect(Resource)] pub struct CatControllerSettings { pub kp: f32, pub kd: f32, pub ki: f32, } impl Default for CatControllerSettings { fn default() -> Self { Self { kp: 15.0, kd: 1.5, ki: 0.5, } } } #[derive(Component, Debug, Clone, Copy, Reflect)] pub struct CatControllerState { pub roll_integral: f32, pub roll_prev: f32, decay_factor: f32, roll_limit: f32, } impl Default for CatControllerState { fn default() -> Self { Self { roll_integral: Default::default(), roll_prev: Default::default(), decay_factor: 0.99, roll_limit: 1.5, } } } impl CatControllerState { pub fn update_roll(&mut self, error: f32, dt: f32) -> (f32, f32) { 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 std::f32::consts::{FRAC_PI_3, FRAC_PI_4}; use avian3d::prelude::*; use bevy::prelude::*; use super::{CatControllerSettings, CatControllerState, CyberLean}; use crate::{ bike::{CyberBikeBody, CyberWheel, WheelConfig, WheelState, WHEEL_RADIUS}, input::InputState, }; 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: Query<(&LinearVelocity, &Transform), With>, wheels: Query<&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.single(); 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() { lean.lean = tan_theta.atan().clamp(-FRAC_PI_3, FRAC_PI_3); } else { //lean.lean = 0.0; } } pub(super) fn apply_lean( mut bike_query: Query<(&Transform, &mut ExternalForce, &mut CatControllerState)>, wheels: Query<&WheelState>, time: Res