cyber_rider/src/action/systems.rs
2023-02-05 14:59:41 -08:00

183 lines
6.1 KiB
Rust

use std::f32::consts::PI;
#[cfg(feature = "inspector")]
use std::time::Instant;
use bevy::prelude::{
Commands, Entity, Quat, Query, Res, ResMut, Time, Transform, Vec3, With, Without,
};
use bevy_rapier3d::{
prelude::{
CollisionGroups, ExternalForce, Group, MultibodyJoint, QueryFilter, RapierConfiguration,
RapierContext, ReadMassProperties, Velocity,
},
rapier::prelude::JointAxis,
};
#[cfg(feature = "inspector")]
use super::ActionDebugInstant;
use super::{CatControllerSettings, CatControllerState, MovementSettings, Tunneling};
use crate::{
bike::{CyberBikeBody, CyberSteering, CyberWheel, BIKE_WHEEL_COLLISION_GROUP},
input::InputState,
};
/// Disable gravity in Rapier.
pub(super) fn zero_gravity(mut config: ResMut<RapierConfiguration>) {
config.gravity = Vec3::ZERO;
}
/// The gravity vector points from the cyberbike to the center of the planet.
pub(super) fn gravity(
mut query: Query<(&Transform, &mut ExternalForce, &ReadMassProperties), With<CyberBikeBody>>,
settings: Res<MovementSettings>,
) {
let (xform, mut forces, mprops) = query.single_mut();
let grav = xform.translation.normalize() * -settings.gravity * mprops.0.mass;
forces.force = grav;
forces.torque = Vec3::ZERO;
}
/// PID-based controller for stabilizing attitude; keeps the cyberbike upright.
pub(super) fn falling_cat(
mut bike_query: Query<(&Transform, &mut ExternalForce, &mut CatControllerState)>,
time: Res<Time>,
settings: Res<CatControllerSettings>,
#[cfg(feature = "inspector")] mut debug_instant: ResMut<ActionDebugInstant>,
) {
let (xform, mut forces, mut control_vars) = bike_query.single_mut();
let wup = xform.translation.normalize();
let bike_up = xform.up();
let wright = xform.forward().cross(wup).normalize();
let roll_error = wright.dot(bike_up);
let pitch_error = wup.dot(xform.back());
// only try to correct roll if we're not totally vertical
if pitch_error.abs() < 0.8 {
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() {
forces.torque += xform.back() * mag;
}
}
// we can do pitch correction any time, it's not coupled to roll
let (derivative, integral) = control_vars.update_pitch(pitch_error, time.delta_seconds());
let mag = (settings.kp * pitch_error) + (settings.ki * integral) + (settings.kd * derivative);
if mag.is_finite() {
forces.torque += wright * mag * 0.25;
}
#[cfg(feature = "inspector")]
{
let now = Instant::now();
if (now - debug_instant.0).as_millis() > 500 {
dbg!(roll_error, pitch_error, &control_vars, mag);
debug_instant.0 = now;
}
}
control_vars.decay();
}
/// Apply forces to the cyberbike as a result of input.
pub(super) fn input_forces(
settings: Res<MovementSettings>,
input: Res<InputState>,
mut braking_query: Query<&mut MultibodyJoint, With<CyberWheel>>,
mut body_query: Query<
(&Transform, &mut ExternalForce),
(With<CyberBikeBody>, Without<CyberSteering>),
>,
mut steering_query: Query<&mut Transform, With<CyberSteering>>,
) {
let (xform, mut forces) = body_query.single_mut();
// thrust
let thrust = xform.forward() * input.throttle * settings.accel;
let point = xform.translation + xform.back();
let force = ExternalForce::at_point(thrust, point, xform.translation);
*forces += force;
// brake
for mut motor in braking_query.iter_mut() {
let factor = if input.brake { settings.accel } else { 0.0 };
motor.data.set_motor_velocity(JointAxis::X, 0.0, factor);
}
// steering
let angle = input.yaw * (PI / 2.0);
let mut steering = steering_query.single_mut();
steering.rotation = Quat::from_axis_angle(Vec3::Y, angle + 90.0f32.to_radians());
//dbg!(steering.rotation);
}
/// Don't let the wheels get stuck underneat the planet
pub(super) fn surface_fix(
mut commands: Commands,
mut wheel_query: Query<
(Entity, &Transform, &mut CollisionGroups),
(With<CyberWheel>, Without<Tunneling>),
>,
context: Res<RapierContext>,
) {
// assume the body is not below the planet surface
for (entity, xform, mut cgroups) in wheel_query.iter_mut() {
let ray_dir = xform.translation.normalize();
if let Some(hit) = context.cast_ray_and_get_normal(
xform.translation,
ray_dir,
10.0,
false,
QueryFilter::only_fixed(),
) {
cgroups.memberships = Group::NONE;
cgroups.filters = Group::NONE;
commands.entity(entity).insert(Tunneling {
frames: 6,
dir: -hit.1.normal,
});
}
}
}
pub(super) fn tunnel_out(
mut commands: Commands,
mut wheel_query: Query<
(
Entity,
&mut Tunneling,
&mut ExternalForce,
&mut CollisionGroups,
),
With<CyberWheel>,
>,
mprops: Query<&ReadMassProperties, With<CyberBikeBody>>,
settings: Res<MovementSettings>,
) {
let mprops = mprops.single();
for (entity, mut tunneling, mut force, mut cgroups) in wheel_query.iter_mut() {
if tunneling.frames == 0 {
commands.entity(entity).remove::<Tunneling>();
force.force = Vec3::ZERO;
(cgroups.memberships, cgroups.filters) = BIKE_WHEEL_COLLISION_GROUP;
continue;
}
tunneling.frames -= 1;
force.force = tunneling.dir * settings.gravity * 1.5 * mprops.0.mass;
//#[cfg(feature = "inspector")]
dbg!(&tunneling);
}
}
/// General velocity-based drag-force calculation; does not take orientation
/// into account.
pub(super) fn drag(mut query: Query<(&Velocity, &mut ExternalForce), With<CyberBikeBody>>) {
let (vels, mut forces) = query.single_mut();
if let Some(vel) = vels.linvel.try_normalize() {
let v2 = vels.linvel.length_squared();
forces.force -= vel * (v2 * 0.02);
}
}