use std::fmt;
use std::ptr::null;
use std::sync::Arc;
use oxijolt_sys::*;
use super::{InnerBody, INNER_BODY_INERTIA_RULE, INVALID_ID};
use crate::body::{has_finite_inverse, mass_properties};
use crate::limits;
use crate::math::{is_finite_non_negative, is_finite_positive};
use crate::{CharacterError, Shape, ShapeError, Vec3};
#[derive(Clone)]
pub struct CharacterSettings<'a> {
shape: CharacterShape<'a>,
up: Vec3,
supporting_volume_normal: Vec3,
supporting_volume_constant: f32,
max_slope_angle: f32,
enhanced_internal_edge_removal: bool,
pub(super) mass: f32,
max_strength: f32,
shape_offset: Vec3,
back_face_collision: bool,
predictive_contact_distance: f32,
max_collision_iterations: u32,
max_constraint_iterations: u32,
min_time_remaining: f32,
collision_tolerance: f32,
character_padding: f32,
max_num_hits: u32,
hit_reduction_cos_max_angle: f32,
penetration_recovery_speed: f32,
pub(super) inner_body: Option<InnerBody<'a>>,
pub(super) collide_with_characters: bool,
pub(super) user_data: u64,
}
impl fmt::Debug for CharacterSettings<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CharacterSettings")
.field("up", &self.up)
.field("max_slope_angle", &self.max_slope_angle)
.field("shape_offset", &self.shape_offset)
.field("character_padding", &self.character_padding)
.field("inner_body", &self.inner_body)
.field("collide_with_characters", &self.collide_with_characters)
.finish_non_exhaustive()
}
}
#[derive(Clone)]
enum CharacterShape<'a> {
Borrowed(&'a Shape),
Owned(Arc<Shape>),
}
impl CharacterShape<'_> {
fn get(&self) -> &Shape {
match self {
Self::Borrowed(shape) => shape,
Self::Owned(shape) => shape,
}
}
}
const DEFAULT_MAX_SLOPE_ANGLE: f32 = 50.0 * (std::f32::consts::PI / 180.0);
impl<'a> CharacterSettings<'a> {
pub fn new(shape: &'a Shape) -> Self {
Self::with_shape(CharacterShape::Borrowed(shape))
}
fn with_shape(shape: CharacterShape<'a>) -> Self {
Self {
shape,
up: Vec3::new(0.0, 1.0, 0.0),
supporting_volume_normal: Vec3::new(0.0, 1.0, 0.0),
supporting_volume_constant: -1.0e10,
max_slope_angle: DEFAULT_MAX_SLOPE_ANGLE,
enhanced_internal_edge_removal: false,
mass: 70.0,
max_strength: 100.0,
shape_offset: Vec3::ZERO,
back_face_collision: true,
predictive_contact_distance: 0.1,
max_collision_iterations: 5,
max_constraint_iterations: 15,
min_time_remaining: 1.0e-4,
collision_tolerance: 1.0e-3,
character_padding: 0.02,
max_num_hits: 256,
hit_reduction_cos_max_angle: 0.999,
penetration_recovery_speed: 1.0,
inner_body: None,
collide_with_characters: false,
user_data: 0,
}
}
#[must_use]
pub fn up(mut self, value: Vec3) -> Self {
self.up = value;
self
}
#[must_use]
pub fn supporting_volume(mut self, normal: Vec3, constant: f32) -> Self {
self.supporting_volume_normal = normal;
self.supporting_volume_constant = constant;
self
}
#[must_use]
pub fn max_slope_angle(mut self, radians: f32) -> Self {
self.max_slope_angle = radians;
self
}
#[must_use]
pub fn enhanced_internal_edge_removal(mut self, value: bool) -> Self {
self.enhanced_internal_edge_removal = value;
self
}
#[must_use]
pub fn mass(mut self, value: f32) -> Self {
self.mass = value;
self
}
#[must_use]
pub fn max_strength(mut self, value: f32) -> Self {
self.max_strength = value;
self
}
#[must_use]
pub fn shape_offset(mut self, value: Vec3) -> Self {
self.shape_offset = value;
self
}
#[must_use]
pub fn back_face_collision(mut self, value: bool) -> Self {
self.back_face_collision = value;
self
}
#[must_use]
pub fn predictive_contact_distance(mut self, value: f32) -> Self {
self.predictive_contact_distance = value;
self
}
#[must_use]
pub fn max_collision_iterations(mut self, value: u32) -> Self {
self.max_collision_iterations = value;
self
}
#[must_use]
pub fn max_constraint_iterations(mut self, value: u32) -> Self {
self.max_constraint_iterations = value;
self
}
#[must_use]
pub fn min_time_remaining(mut self, value: f32) -> Self {
self.min_time_remaining = value;
self
}
#[must_use]
pub fn collision_tolerance(mut self, value: f32) -> Self {
self.collision_tolerance = value;
self
}
#[must_use]
pub fn character_padding(mut self, value: f32) -> Self {
self.character_padding = value;
self
}
#[must_use]
pub fn max_num_hits(mut self, value: u32) -> Self {
self.max_num_hits = value;
self
}
#[must_use]
pub fn hit_reduction_cos_max_angle(mut self, value: f32) -> Self {
self.hit_reduction_cos_max_angle = value;
self
}
#[must_use]
pub fn penetration_recovery_speed(mut self, value: f32) -> Self {
self.penetration_recovery_speed = value;
self
}
#[must_use]
pub fn inner_body(mut self, value: Option<InnerBody<'a>>) -> Self {
self.inner_body = value;
self
}
#[must_use]
pub fn collide_with_characters(mut self, value: bool) -> Self {
self.collide_with_characters = value;
self
}
#[must_use]
pub fn user_data(mut self, value: u64) -> Self {
self.user_data = value;
self
}
pub(crate) fn validate(&self, object_layer_count: u32) -> Result<(), CharacterError> {
let invalid = |what| Err(CharacterError::InvalidValue(what));
if !is_unit(self.up) {
return invalid(UP_RULE);
}
if !is_unit(self.supporting_volume_normal) {
return invalid("supporting volume normal must be a finite unit vector");
}
if !self.supporting_volume_constant.is_finite() {
return invalid("supporting volume constant must be finite");
}
if !(is_finite_non_negative(self.max_slope_angle)
&& self.max_slope_angle <= std::f32::consts::FRAC_PI_2)
{
return invalid("max slope angle must be between 0 and pi/2");
}
if !(0.0..=limits::MAX_MASS).contains(&self.mass) {
return invalid("mass must be between 0 and limits::MAX_MASS");
}
if !is_finite_non_negative(self.max_strength) {
return invalid("max strength must be finite and not negative");
}
if !limits::is_local_offset(self.shape_offset) {
return invalid("shape offset must be finite and within limits::MAX_SHAPE_EXTENT");
}
if !limits::is_local_distance(self.predictive_contact_distance) {
return invalid(
"predictive contact distance must be between 0 and limits::MAX_SHAPE_EXTENT",
);
}
if self.max_collision_iterations == 0 || self.max_constraint_iterations == 0 {
return invalid("iteration counts must be at least 1");
}
if !is_finite_positive(self.min_time_remaining) {
return invalid("min time remaining must be finite and positive");
}
if !(limits::is_local_distance(self.collision_tolerance) && self.collision_tolerance > 0.0)
{
return invalid(
"collision tolerance must be positive and at most limits::MAX_SHAPE_EXTENT",
);
}
if !limits::is_local_distance(self.character_padding) {
return invalid("character padding must be between 0 and limits::MAX_SHAPE_EXTENT");
}
if self.max_num_hits == 0 {
return invalid("max num hits must be at least 1");
}
if !self.hit_reduction_cos_max_angle.is_finite() {
return invalid("hit reduction cos max angle must be finite");
}
if !is_recovery_speed(self.penetration_recovery_speed) {
return invalid(RECOVERY_SPEED_RULE);
}
if unsafe { JPH_Shape_GetType(self.shape.get().as_ptr()) } != JPH_ShapeType_Convex {
return invalid("the character shape must be convex");
}
if let Some(inner) = &self.inner_body {
if inner.object_layer.get() >= object_layer_count {
return invalid("inner body object layer does not exist in this world");
}
if inner.shape.must_be_static() {
return invalid("inner body shape can only be used by static bodies");
}
if !has_finite_inverse(&mass_properties(inner.shape, None)) {
return invalid(INNER_BODY_INERTIA_RULE);
}
}
Ok(())
}
pub(super) fn to_jph(&self, id: u32) -> JPH_CharacterVirtualSettings {
JPH_CharacterVirtualSettings {
base: JPH_CharacterBaseSettings {
up: self.up.to_jph(),
supportingVolume: JPH_Plane {
normal: self.supporting_volume_normal.to_jph(),
distance: self.supporting_volume_constant,
},
maxSlopeAngle: self.max_slope_angle,
enhancedInternalEdgeRemoval: self.enhanced_internal_edge_removal,
shape: self.shape.get().as_ptr(),
},
ID: id,
mass: self.mass,
maxStrength: self.max_strength,
shapeOffset: self.shape_offset.to_jph(),
backFaceMode: if self.back_face_collision {
JPH_BackFaceMode_CollideWithBackFaces
} else {
JPH_BackFaceMode_IgnoreBackFaces
},
predictiveContactDistance: self.predictive_contact_distance,
maxCollisionIterations: self.max_collision_iterations,
maxConstraintIterations: self.max_constraint_iterations,
minTimeRemaining: self.min_time_remaining,
collisionTolerance: self.collision_tolerance,
characterPadding: self.character_padding,
maxNumHits: self.max_num_hits,
hitReductionCosMaxAngle: self.hit_reduction_cos_max_angle,
penetrationRecoverySpeed: self.penetration_recovery_speed,
innerBodyShape: self.inner_body.map_or(null(), |inner| inner.shape.as_ptr()),
innerBodyIDOverride: INVALID_ID,
innerBodyLayer: self.inner_body.map_or(0, |inner| inner.object_layer.get()),
}
}
}
impl CharacterSettings<'static> {
pub fn humanoid(height: f32, radius: f32) -> Result<Self, ShapeError> {
if !(radius.is_finite() && radius > 0.0) {
return Err(ShapeError::InvalidValue(
"humanoid radius must be finite and positive",
));
}
if !(height.is_finite() && height > 2.0 * radius) {
return Err(ShapeError::InvalidValue(
"humanoid height must be finite and more than twice its radius",
));
}
let capsule = Shape::new_capsule(height / 2.0 - radius, radius)?;
let foot_offset = Vec3::new(0.0, height / 2.0, 0.0);
Ok(Self::with_shape(CharacterShape::Owned(Arc::new(capsule))).shape_offset(foot_offset))
}
}
pub(super) const UP_RULE: &str = "up must be a finite unit vector";
pub(super) const RECOVERY_SPEED_RULE: &str = "penetration recovery speed must be between 0 and 1";
pub(super) fn is_recovery_speed(value: f32) -> bool {
is_finite_non_negative(value) && value <= 1.0
}
pub(super) fn is_unit(v: Vec3) -> bool {
v.is_finite() && (v.dot(v) - 1.0).abs() <= 1.0e-5
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ExtendedUpdateSettings {
stick_to_floor_step_down: Vec3,
walk_stairs_step_up: Vec3,
walk_stairs_min_step_forward: f32,
walk_stairs_step_forward_test: f32,
walk_stairs_cos_angle_forward_contact: f32,
walk_stairs_step_down_extra: Vec3,
}
impl Default for ExtendedUpdateSettings {
fn default() -> Self {
Self {
stick_to_floor_step_down: Vec3::new(0.0, -0.5, 0.0),
walk_stairs_step_up: Vec3::new(0.0, 0.4, 0.0),
walk_stairs_min_step_forward: 0.02,
walk_stairs_step_forward_test: 0.15,
walk_stairs_cos_angle_forward_contact: (75.0 * (std::f32::consts::PI / 180.0_f32))
.cos(),
walk_stairs_step_down_extra: Vec3::ZERO,
}
}
}
impl ExtendedUpdateSettings {
#[must_use]
pub fn stick_to_floor_step_down(mut self, value: Vec3) -> Self {
self.stick_to_floor_step_down = value;
self
}
#[must_use]
pub fn walk_stairs_step_up(mut self, value: Vec3) -> Self {
self.walk_stairs_step_up = value;
self
}
#[must_use]
pub fn walk_stairs_min_step_forward(mut self, value: f32) -> Self {
self.walk_stairs_min_step_forward = value;
self
}
#[must_use]
pub fn walk_stairs_step_forward_test(mut self, value: f32) -> Self {
self.walk_stairs_step_forward_test = value;
self
}
#[must_use]
pub fn walk_stairs_cos_angle_forward_contact(mut self, value: f32) -> Self {
self.walk_stairs_cos_angle_forward_contact = value;
self
}
#[must_use]
pub fn walk_stairs_step_down_extra(mut self, value: Vec3) -> Self {
self.walk_stairs_step_down_extra = value;
self
}
pub(super) fn validate(&self) -> Result<(), CharacterError> {
let offsets = limits::is_local_offset(self.stick_to_floor_step_down)
&& limits::is_local_offset(self.walk_stairs_step_up)
&& limits::is_local_offset(self.walk_stairs_step_down_extra);
if !offsets {
return Err(CharacterError::InvalidValue(
"extended update steps must be finite and within limits::MAX_SHAPE_EXTENT",
));
}
if !self.walk_stairs_cos_angle_forward_contact.is_finite() {
return Err(CharacterError::InvalidValue(
"walk stairs cos angle forward contact must be finite",
));
}
if !(limits::is_local_distance(self.walk_stairs_min_step_forward)
&& limits::is_local_distance(self.walk_stairs_step_forward_test))
{
return Err(CharacterError::InvalidValue(
"walk stairs forward distances must be between 0 and limits::MAX_SHAPE_EXTENT",
));
}
Ok(())
}
pub(super) fn to_jph(self) -> JPH_ExtendedUpdateSettings {
JPH_ExtendedUpdateSettings {
stickToFloorStepDown: self.stick_to_floor_step_down.to_jph(),
walkStairsStepUp: self.walk_stairs_step_up.to_jph(),
walkStairsMinStepForward: self.walk_stairs_min_step_forward,
walkStairsStepForwardTest: self.walk_stairs_step_forward_test,
walkStairsCosAngleForwardContact: self.walk_stairs_cos_angle_forward_contact,
walkStairsStepDownExtra: self.walk_stairs_step_down_extra.to_jph(),
}
}
}