use oxijolt_sys::*;
use super::{DriverInput, Motorcycle, MotorcycleSettings, VehicleId};
use crate::body::with_read_locked_body;
use crate::{limits, BodyId, PhysicsWorld, Quat, Vec3, VehicleError, VehicleMut, VehicleRef};
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub struct MotorcycleLean {
pub target: Vec3,
pub target_angle: f32,
pub angle: f32,
}
impl VehicleRef<'_, Motorcycle> {
fn motorcycle_controller(&self) -> *mut JPH_MotorcycleController {
self.controller().cast()
}
pub fn driver_input(&self) -> DriverInput {
unsafe { DriverInput::of(self.motorcycle_controller().cast()) }
}
pub fn lean(&self) -> MotorcycleLean {
let target = target_lean(self.controller());
let constraint = self.ptr();
let mut local_up = Vec3::ZERO.to_jph();
let mut local_forward = Vec3::ZERO.to_jph();
unsafe {
JPH_VehicleConstraint_GetLocalUp(constraint, &mut local_up);
JPH_VehicleConstraint_GetLocalForward(constraint, &mut local_forward);
}
let rotation = self
.world
.body(self.entry.body)
.unwrap_or_else(|_| {
unreachable!("the chassis cannot be removed while the vehicle exists")
})
.rotation();
let forward = rotate(rotation, wide(Vec3::from_jph(local_forward)));
let up = rotate(rotation, wide(Vec3::from_jph(local_up)));
let world_up = wide(self.world_up());
MotorcycleLean {
target,
target_angle: lean_angle(world_up, wide(target), forward),
angle: lean_angle(world_up, up, forward),
}
}
pub fn is_lean_controller_enabled(&self) -> bool {
unsafe { JPH_MotorcycleController_IsLeanControllerEnabled(self.motorcycle_controller()) }
}
pub fn is_lean_steering_limit_enabled(&self) -> bool {
unsafe { JPH_MotorcycleController_IsLeanSteeringLimitEnabled(self.motorcycle_controller()) }
}
}
impl VehicleMut<'_, Motorcycle> {
pub fn set_driver_input(&mut self, input: DriverInput) -> Result<(), VehicleError> {
input.validate()?;
unsafe { input.apply(self.controller().cast()) };
Ok(())
}
}
pub(super) fn target_lean(controller: *mut JPH_VehicleController) -> Vec3 {
let mut value = Vec3::ZERO.to_jph();
let found = unsafe { JPH_MotorcycleController_GetTargetLean(controller, &mut value) };
assert!(found, "a motorcycle's controller is a MotorcycleController");
Vec3::from_jph(value)
}
pub(super) unsafe fn set_target_lean(controller: *mut JPH_VehicleController, value: Vec3) {
let value = value.to_jph();
let stored = unsafe { JPH_MotorcycleController_SetTargetLean(controller, &value) };
assert!(
stored,
"a motorcycle's controller is a MotorcycleController"
);
}
type Wide = [f64; 3];
fn wide(v: Vec3) -> Wide {
<[f32; 3]>::from(v).map(f64::from)
}
fn dot(a: Wide, b: Wide) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn cross(a: Wide, b: Wide) -> Wide {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
fn rotate(q: Quat, v: Wide) -> Wide {
let [x, y, z, w] = <[f32; 4]>::from(q).map(f64::from);
let u = [x, y, z];
let t = cross(u, v).map(|c| 2.0 * c);
let ut = cross(u, t);
std::array::from_fn(|i| v[i] + w * t[i] + ut[i])
}
fn perpendicular(v: Wide, axis: Wide) -> Option<Wide> {
let along = dot(v, axis);
let rest: Wide = std::array::from_fn(|i| v[i] - along * axis[i]);
let length = dot(rest, rest).sqrt();
(length > 0.0 && length.is_finite()).then(|| rest.map(|c| c / length))
}
fn lean_angle(world_up: Wide, direction: Wide, forward: Wide) -> f32 {
let length = dot(forward, forward).sqrt();
let forward = forward.map(|c| c / length);
match (
perpendicular(world_up, forward),
perpendicular(direction, forward),
) {
(Some(up), Some(direction)) => {
dot(cross(up, direction), forward).atan2(dot(up, direction)) as f32
}
_ => 0.0,
}
}
impl PhysicsWorld {
pub fn create_motorcycle(
&mut self,
body: BodyId,
settings: &MotorcycleSettings,
) -> Result<VehicleId<Motorcycle>, VehicleError> {
settings.validate(self.object_layer_count)?;
self.check_chassis(body)?;
let largest_inverse_inertia =
with_read_locked_body(self.body_lock_interface, body, |chassis| {
let mut inverse_inertia = Vec3::ZERO.to_jph();
unsafe {
let motion = JPH_Body_GetMotionProperties(chassis.as_ptr());
JPH_MotionProperties_GetInverseInertiaDiagonal(motion, &mut inverse_inertia);
}
let inverse_inertia = Vec3::from_jph(inverse_inertia);
inverse_inertia
.x
.max(inverse_inertia.y)
.max(inverse_inertia.z)
})
.unwrap_or_else(|| {
unreachable!("`check_chassis` found the body and `&mut self` keeps it")
});
if !limits::is_lean_spring(
settings.lean_spring_constant,
settings.lean_spring_damping,
largest_inverse_inertia,
) {
return Err(VehicleError::InvalidValue(limits::LEAN_SPRING_RULE));
}
let id = self.attach_vehicle(body, settings.build())?;
let entry = self
.vehicles
.get_mut(&id.raw)
.unwrap_or_else(|| unreachable!("just attached"));
unsafe {
let controller: *mut JPH_MotorcycleController =
JPH_VehicleConstraint_GetController(entry.constraint.as_ptr()).cast();
JPH_MotorcycleController_EnableLeanController(controller, settings.lean_controller);
JPH_MotorcycleController_EnableLeanSteeringLimit(
controller,
settings.lean_steering_limit,
);
}
Ok(VehicleId::new(id.raw, self.tag))
}
}