use std::f32::consts::PI;
use oxijolt_sys::*;
use super::WheelGeometry;
use crate::math::{is_finite_non_negative, is_finite_positive, is_unit};
use crate::owned::{JoltObject, Owned};
use crate::{ObjectLayer, Vec3, VehicleError};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum VehicleCollisionTester {
Ray {
object_layer: ObjectLayer,
up: Vec3,
max_slope_angle: f32,
},
CastSphere {
object_layer: ObjectLayer,
radius: f32,
up: Vec3,
max_slope_angle: f32,
},
CastCylinder {
object_layer: ObjectLayer,
convex_radius_fraction: f32,
},
}
impl VehicleCollisionTester {
pub fn ray(object_layer: ObjectLayer) -> Self {
Self::Ray {
object_layer,
up: Vec3::new(0.0, 1.0, 0.0),
max_slope_angle: 80.0_f32.to_radians(),
}
}
pub fn cast_sphere(object_layer: ObjectLayer, radius: f32) -> Self {
Self::CastSphere {
object_layer,
radius,
up: Vec3::new(0.0, 1.0, 0.0),
max_slope_angle: 80.0_f32.to_radians(),
}
}
pub fn cast_cylinder(object_layer: ObjectLayer) -> Self {
Self::CastCylinder {
object_layer,
convex_radius_fraction: 0.1,
}
}
pub fn object_layer(&self) -> ObjectLayer {
match *self {
Self::Ray { object_layer, .. }
| Self::CastSphere { object_layer, .. }
| Self::CastCylinder { object_layer, .. } => object_layer,
}
}
pub fn up(&self) -> Option<Vec3> {
match *self {
Self::Ray { up, .. } | Self::CastSphere { up, .. } => Some(up),
Self::CastCylinder { .. } => None,
}
}
pub(crate) fn with_up(self, new_up: Vec3) -> Self {
match self {
Self::Ray {
object_layer,
max_slope_angle,
..
} => Self::Ray {
object_layer,
up: new_up,
max_slope_angle,
},
Self::CastSphere {
object_layer,
radius,
max_slope_angle,
..
} => Self::CastSphere {
object_layer,
radius,
up: new_up,
max_slope_angle,
},
cylinder @ Self::CastCylinder { .. } => cylinder,
}
}
pub(crate) fn validate(
&self,
object_layer_count: u32,
mut wheels: impl Iterator<Item = WheelGeometry>,
) -> Result<(), VehicleError> {
let invalid = |what| Err(VehicleError::InvalidValue(what));
if self.object_layer().get() >= object_layer_count {
return invalid("collision tester object layer does not exist in this world");
}
let slope_ok = |angle: f32| is_finite_non_negative(angle) && angle <= PI;
match *self {
Self::Ray {
up,
max_slope_angle,
..
} => {
if !(is_unit(up) && slope_ok(max_slope_angle)) {
return invalid(
"ray tester needs a unit up and a max slope angle between 0 and pi",
);
}
}
Self::CastSphere {
radius,
up,
max_slope_angle,
..
} => {
if !(is_unit(up) && slope_ok(max_slope_angle)) {
return invalid(
"sphere tester needs a unit up and a max slope angle between 0 and pi",
);
}
if !is_finite_positive(radius) {
return invalid("sphere tester radius must be finite and positive");
}
if !wheels.all(|w| w.suspension_max_length + w.radius - radius > 0.0) {
return invalid(
"sphere tester radius must be below every wheel's max suspension length plus radius",
);
}
}
Self::CastCylinder {
convex_radius_fraction,
..
} => {
if !(convex_radius_fraction.is_finite()
&& (0.0..=1.0).contains(&convex_radius_fraction))
{
return invalid("cylinder tester convex radius fraction must be in [0, 1]");
}
if !wheels.all(|w| w.width > 0.0 && w.suspension_max_length > 0.0) {
return invalid(
"cylinder tester needs every wheel's width and max suspension length positive",
);
}
}
}
Ok(())
}
pub(crate) fn create(&self, vehicle_body: JPH_BodyID) -> Owned<JPH_VehicleCollisionTester> {
let tester: *mut JPH_VehicleCollisionTester = match *self {
Self::Ray {
object_layer,
up,
max_slope_angle,
} => {
let up = up.to_jph();
unsafe {
JPH_VehicleCollisionTesterRay_Create2(
object_layer.get(),
&up,
max_slope_angle,
vehicle_body,
)
}
.cast()
}
Self::CastSphere {
object_layer,
radius,
up,
max_slope_angle,
} => {
let up = up.to_jph();
unsafe {
JPH_VehicleCollisionTesterCastSphere_Create2(
object_layer.get(),
radius,
&up,
max_slope_angle,
vehicle_body,
)
}
.cast()
}
Self::CastCylinder {
object_layer,
convex_radius_fraction,
} => {
unsafe {
JPH_VehicleCollisionTesterCastCylinder_Create2(
object_layer.get(),
convex_radius_fraction,
vehicle_body,
)
}
.cast()
}
};
unsafe { Owned::from_raw(tester) }
.unwrap_or_else(|| unreachable!("joltc `new`s the tester"))
}
}
impl JoltObject for JPH_VehicleCollisionTester {
unsafe fn destroy(ptr: *mut Self) {
unsafe { JPH_VehicleCollisionTester_Destroy(ptr) };
}
}