use std::array::from_fn;
use oxijolt_sys::*;
use super::wheel::WheelBase;
use super::{BuiltSettings, ControllerGuard, WheeledVehicleSettings};
use crate::math::is_finite_non_negative;
use crate::owned::{JoltObject, Owned};
use crate::{Vec3, VehicleError};
#[derive(Clone, Debug, PartialEq)]
pub struct MotorcycleSettings {
pub(super) vehicle: WheeledVehicleSettings,
pub(super) max_lean_angle: f32,
pub(crate) lean_spring_constant: f32,
pub(crate) lean_spring_damping: f32,
pub(super) lean_spring_integration_coefficient: f32,
pub(super) lean_smoothing_factor: f32,
pub(crate) lean_controller: bool,
pub(crate) lean_steering_limit: bool,
}
impl MotorcycleSettings {
pub const MAX_LEAN_ANGLE: f32 = 1.396_263_4;
pub fn new(vehicle: WheeledVehicleSettings) -> Self {
Self {
vehicle,
max_lean_angle: 45.0_f32.to_radians(),
lean_spring_constant: 5000.0,
lean_spring_damping: 1000.0,
lean_spring_integration_coefficient: 0.0,
lean_smoothing_factor: 0.8,
lean_controller: true,
lean_steering_limit: true,
}
}
#[must_use]
pub fn max_lean_angle(mut self, radians: f32) -> Self {
self.max_lean_angle = radians;
self
}
#[must_use]
pub fn lean_spring_constant(mut self, value: f32) -> Self {
self.lean_spring_constant = value;
self
}
#[must_use]
pub fn lean_spring_damping(mut self, value: f32) -> Self {
self.lean_spring_damping = value;
self
}
#[must_use]
pub fn lean_spring_integration_coefficient(mut self, value: f32) -> Self {
self.lean_spring_integration_coefficient = value;
self
}
#[must_use]
pub fn lean_smoothing_factor(mut self, value: f32) -> Self {
self.lean_smoothing_factor = value;
self
}
#[must_use]
pub fn lean_controller(mut self, enabled: bool) -> Self {
self.lean_controller = enabled;
self
}
#[must_use]
pub fn lean_steering_limit(mut self, enabled: bool) -> Self {
self.lean_steering_limit = enabled;
self
}
pub(crate) fn validate(&self, object_layer_count: u32) -> Result<(), VehicleError> {
let invalid = |what| Err(VehicleError::InvalidValue(what));
self.vehicle.validate(object_layer_count)?;
if self.vehicle.wheels.len() != 2 {
return invalid("a motorcycle needs exactly two wheels");
}
if !self.has_wheel_base() {
return invalid("a motorcycle's wheels must be apart along its forward");
}
if !(is_finite_non_negative(self.max_lean_angle)
&& self.max_lean_angle <= Self::MAX_LEAN_ANGLE)
{
return invalid(
"max lean angle must be between 0 and MotorcycleSettings::MAX_LEAN_ANGLE",
);
}
if !(is_finite_non_negative(self.lean_spring_constant)
&& is_finite_non_negative(self.lean_spring_damping))
{
return invalid("lean spring constant and damping must be finite and not negative");
}
if !(self.lean_smoothing_factor.is_finite()
&& (0.0..=1.0).contains(&self.lean_smoothing_factor))
{
return invalid("lean smoothing factor must be between 0 and 1");
}
if self.lean_spring_integration_coefficient != 0.0 {
return Err(VehicleError::LeanSpringIntegrationNotSaved);
}
Ok(())
}
fn has_wheel_base(&self) -> bool {
let forward = self.vehicle.frame.forward;
let front = along_forward(&self.vehicle.wheels[0].base, forward);
let rear = along_forward(&self.vehicle.wheels[1].base, forward);
front.iter().zip(&rear).all(|(a, b)| a != b)
}
pub(crate) fn build(&self) -> BuiltSettings {
self.vehicle
.build_with(ControllerGuard::Motorcycle(self.create_controller()))
}
pub(super) fn create_controller(&self) -> Owned<JPH_MotorcycleControllerSettings> {
let controller = unsafe { Owned::from_raw(JPH_MotorcycleControllerSettings_Create()) }
.unwrap_or_else(|| unreachable!("joltc `new`s the settings"));
let ptr = controller.as_ptr();
unsafe {
self.vehicle.fill_wheeled_controller(ptr.cast());
JPH_MotorcycleControllerSettings_SetMaxLeanAngle(ptr, self.max_lean_angle);
JPH_MotorcycleControllerSettings_SetLeanSpringConstant(ptr, self.lean_spring_constant);
JPH_MotorcycleControllerSettings_SetLeanSpringDamping(ptr, self.lean_spring_damping);
JPH_MotorcycleControllerSettings_SetLeanSpringIntegrationCoefficient(
ptr,
self.lean_spring_integration_coefficient,
);
JPH_MotorcycleControllerSettings_SetLeanSmoothingFactor(
ptr,
self.lean_smoothing_factor,
);
}
controller
}
}
fn along_forward(wheel: &WheelBase, forward: Vec3) -> [f64; 3] {
let f = <[f32; 3]>::from(forward);
let p = <[f32; 3]>::from(wheel.position);
let d = <[f32; 3]>::from(wheel.suspension_direction);
let length = wheel.suspension_max_length;
let (plain, fused, exact): ([f32; 3], [f32; 3], [f64; 3]) = match wheel.suspension_force_point {
Some(point) => {
let point = <[f32; 3]>::from(point);
(point, point, point.map(f64::from))
}
None => (
from_fn(|i| p[i] + d[i] * length),
from_fn(|i| d[i].mul_add(length, p[i])),
from_fn(|i| f64::from(p[i]) + f64::from(d[i]) * f64::from(length)),
),
};
[
f64::from(plain[0] * f[0] + plain[1] * f[1] + plain[2] * f[2]),
f64::from(fused[2].mul_add(f[2], fused[1].mul_add(f[1], fused[0] * f[0]))),
(0..3).map(|i| exact[i] * f64::from(f[i])).sum(),
]
}
impl JoltObject for JPH_MotorcycleControllerSettings {
unsafe fn destroy(ptr: *mut Self) {
unsafe { JPH_VehicleControllerSettings_Destroy(ptr.cast()) };
}
}
#[cfg(test)]
#[path = "motorcycle_tests.rs"]
mod tests;