use std::fmt;
use oxijolt_sys::*;
use crate::limits::{
self, is_angular_velocity, is_friction, is_gravity_factor, is_in_frame, is_linear_velocity,
is_mass,
};
use crate::math::{is_finite_non_negative, ROTATION_RULE};
use crate::owned::{JoltObject, Owned};
use crate::world::WorldTag;
use crate::{BodyError, CollisionGroup, ObjectLayer, Quat, RVec3, Shape, Vec3};
mod access;
mod buoyancy;
mod control;
mod dofs;
mod handle;
mod kinematic;
mod load;
mod lock;
mod poses;
mod structure;
pub(crate) use access::corners;
pub use buoyancy::BuoyancySettings;
pub use dofs::AllowedDofs;
pub use handle::{BodyMut, BodyRef};
pub(crate) use kinematic::kinematic_velocities;
pub(crate) use lock::{with_locked_bodies, with_locked_body, with_read_locked_body};
pub use poses::BodyPose;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BodyId {
raw: u32,
pub(crate) world: WorldTag,
}
impl BodyId {
const INDEX_MASK: u32 = 0x007f_ffff;
const SEQUENCE_SHIFT: u32 = 23;
pub(crate) fn new(raw: u32, world: WorldTag) -> Self {
Self { raw, world }
}
pub fn index(self) -> u32 {
self.raw & Self::INDEX_MASK
}
pub fn sequence(self) -> u8 {
(self.raw >> Self::SEQUENCE_SHIFT) as u8
}
pub fn to_raw(self) -> u32 {
self.raw
}
}
impl fmt::Debug for BodyId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BodyId")
.field("index", &self.index())
.field("sequence", &self.sequence())
.finish()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MotionType {
Static,
Kinematic,
Dynamic,
}
impl MotionType {
pub(crate) fn to_jph(self) -> JPH_MotionType {
match self {
Self::Static => JPH_MotionType_Static,
Self::Kinematic => JPH_MotionType_Kinematic,
Self::Dynamic => JPH_MotionType_Dynamic,
}
}
pub(crate) fn from_jph(value: JPH_MotionType) -> Self {
[Self::Static, Self::Kinematic, Self::Dynamic]
.into_iter()
.find(|motion_type| motion_type.to_jph() == value)
.unwrap_or_else(|| unreachable!("Jolt returned unknown motion type {value}"))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MotionQuality {
Discrete,
LinearCast,
}
impl MotionQuality {
fn to_jph(self) -> JPH_MotionQuality {
match self {
Self::Discrete => JPH_MotionQuality_Discrete,
Self::LinearCast => JPH_MotionQuality_LinearCast,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Activation {
Activate,
DontActivate,
}
impl Activation {
pub(crate) fn to_jph(self) -> JPH_Activation {
match self {
Self::Activate => JPH_Activation_Activate,
Self::DontActivate => JPH_Activation_DontActivate,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BodySettings {
pub(crate) motion_type: MotionType,
pub(crate) object_layer: ObjectLayer,
position: RVec3,
rotation: Quat,
linear_velocity: Vec3,
angular_velocity: Vec3,
friction: f32,
restitution: f32,
linear_damping: f32,
angular_damping: f32,
pub(crate) mass: Option<f32>,
motion_quality: MotionQuality,
gravity_factor: f32,
allow_sleeping: bool,
activation: Activation,
enhanced_internal_edge_removal: bool,
pub(crate) allowed_dofs: AllowedDofs,
pub(crate) sensor: bool,
pub(crate) user_data: u64,
pub(crate) allow_dynamic_or_kinematic: bool,
pub(crate) collision_group: Option<CollisionGroup>,
}
impl Default for BodySettings {
fn default() -> Self {
Self {
motion_type: MotionType::Dynamic,
object_layer: ObjectLayer::MOVING,
position: RVec3::ZERO,
rotation: Quat::IDENTITY,
linear_velocity: Vec3::ZERO,
angular_velocity: Vec3::ZERO,
friction: 0.2,
restitution: 0.0,
linear_damping: 0.05,
angular_damping: 0.05,
mass: None,
motion_quality: MotionQuality::Discrete,
gravity_factor: 1.0,
allow_sleeping: true,
activation: Activation::Activate,
enhanced_internal_edge_removal: false,
allowed_dofs: AllowedDofs::ALL,
sensor: false,
user_data: 0,
allow_dynamic_or_kinematic: false,
collision_group: None,
}
}
}
pub(crate) const RESTITUTION_RULE: &str = "restitution must be between 0 and 1";
pub(crate) const LINEAR_DAMPING_RULE: &str = "linear damping must be finite and not negative";
pub(crate) const DOFS_RULE: &str = "allowed DOFs must keep a translation axis";
pub(crate) const STATIC_DOFS_RULE: &str = "a static body that cannot move keeps all DOFs";
pub(crate) const SENSOR_SHAPE_RULE: &str = "a sensor cannot use a shape for static bodies only";
pub(crate) const STATIC_SHAPE_RULE: &str = "this shape can only be used by static bodies";
pub(crate) const MESH_DYNAMIC_RULE: &str = "mesh shapes cannot be used by dynamic bodies";
pub(crate) const KINEMATIC_MESH_MASS_RULE: &str = "a kinematic body with a mesh shape needs a mass";
impl BodySettings {
pub fn new_dynamic() -> Self {
Self::default()
}
pub fn new_kinematic() -> Self {
Self {
motion_type: MotionType::Kinematic,
..Self::default()
}
}
pub fn new_static() -> Self {
Self {
motion_type: MotionType::Static,
object_layer: ObjectLayer::NON_MOVING,
..Self::default()
}
}
#[must_use]
pub fn position(mut self, value: RVec3) -> Self {
self.position = value;
self
}
#[must_use]
pub fn rotation(mut self, value: Quat) -> Self {
self.rotation = value;
self
}
#[must_use]
pub fn linear_velocity(mut self, value: Vec3) -> Self {
self.linear_velocity = value;
self
}
#[must_use]
pub fn angular_velocity(mut self, value: Vec3) -> Self {
self.angular_velocity = value;
self
}
#[must_use]
pub fn object_layer(mut self, value: ObjectLayer) -> Self {
self.object_layer = value;
self
}
#[must_use]
pub fn friction(mut self, value: f32) -> Self {
self.friction = value;
self
}
#[must_use]
pub fn restitution(mut self, value: f32) -> Self {
self.restitution = value;
self
}
#[must_use]
pub fn linear_damping(mut self, value: f32) -> Self {
self.linear_damping = value;
self
}
#[must_use]
pub fn angular_damping(mut self, value: f32) -> Self {
self.angular_damping = value;
self
}
#[must_use]
pub fn mass(mut self, value: f32) -> Self {
self.mass = Some(value);
self
}
#[must_use]
pub fn motion_quality(mut self, value: MotionQuality) -> Self {
self.motion_quality = value;
self
}
#[must_use]
pub fn gravity_factor(mut self, value: f32) -> Self {
self.gravity_factor = value;
self
}
#[must_use]
pub fn allow_sleeping(mut self, value: bool) -> Self {
self.allow_sleeping = value;
self
}
#[must_use]
pub fn activation(mut self, value: Activation) -> Self {
self.activation = value;
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 allowed_dofs(mut self, value: AllowedDofs) -> Self {
self.allowed_dofs = value;
self
}
#[must_use]
pub fn sensor(mut self, value: bool) -> Self {
self.sensor = value;
self
}
#[must_use]
pub fn user_data(mut self, value: u64) -> Self {
self.user_data = value;
self
}
#[must_use]
pub fn allow_dynamic_or_kinematic(mut self, value: bool) -> Self {
self.allow_dynamic_or_kinematic = value;
self
}
#[must_use]
pub fn collision_group(mut self, value: CollisionGroup) -> Self {
self.collision_group = Some(value);
self
}
pub(crate) fn can_move(&self) -> bool {
self.motion_type != MotionType::Static || self.allow_dynamic_or_kinematic
}
fn validate(&self, object_layer_count: u32) -> Result<(), BodyError> {
if self.object_layer.get() >= object_layer_count {
return Err(BodyError::UnknownObjectLayer(self.object_layer));
}
self.validate_values()
}
pub(crate) fn validate_values(&self) -> Result<(), BodyError> {
let invalid = |what| Err(BodyError::InvalidValue(what));
if !is_in_frame(self.position) {
return invalid(limits::POSITION_RULE);
}
if !self.rotation.is_valid_rotation() {
return invalid(ROTATION_RULE);
}
if !is_linear_velocity(self.linear_velocity) {
return invalid(limits::LINEAR_VELOCITY_RULE);
}
if !is_angular_velocity(self.angular_velocity) {
return invalid(limits::ANGULAR_VELOCITY_RULE);
}
if !is_friction(self.friction) {
return invalid(limits::FRICTION_RULE);
}
if !(0.0..=1.0).contains(&self.restitution) {
return invalid(RESTITUTION_RULE);
}
if !is_finite_non_negative(self.linear_damping) {
return invalid(LINEAR_DAMPING_RULE);
}
if !is_finite_non_negative(self.angular_damping) {
return invalid("angular damping must be finite and not negative");
}
if !is_gravity_factor(self.gravity_factor) {
return invalid(limits::GRAVITY_FACTOR_RULE);
}
if let Some(mass) = self.mass {
if !is_mass(mass) {
return invalid(limits::MASS_RULE);
}
}
if !self.allowed_dofs.has_translation() {
return invalid(DOFS_RULE);
}
if self.sensor && self.motion_quality == MotionQuality::LinearCast {
return invalid("a sensor uses MotionQuality::Discrete");
}
let still = self.linear_velocity == Vec3::ZERO && self.angular_velocity == Vec3::ZERO;
if self.motion_type == MotionType::Static && self.allow_dynamic_or_kinematic && !still {
return invalid("a static body that may move starts with zero velocities");
}
Ok(())
}
}
pub(crate) const INERTIA_RULE: &str =
"mass and shape must give a finite inverse mass and a well-conditioned inertia";
const ZERO_MASS_PROPERTIES: JPH_MassProperties = JPH_MassProperties {
mass: 0.0,
inertia: JPH_Mat4 {
column: [JPH_Vec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
}; 4],
},
};
pub(crate) fn mass_properties(shape: &Shape, mass: Option<f32>) -> JPH_MassProperties {
let mut properties = ZERO_MASS_PROPERTIES;
unsafe { JPH_Shape_GetMassProperties(shape.as_ptr(), &mut properties) };
if let Some(mass) = mass {
unsafe { JPH_MassProperties_ScaleToMass(&mut properties, mass) };
}
properties
}
pub(crate) fn has_finite_inverse(properties: &JPH_MassProperties) -> bool {
let inverse_mass = 1.0 / properties.mass;
if !(properties.mass > 0.0 && inverse_mass.is_finite() && (2.5 * inverse_mass).is_finite()) {
return false;
}
let [x, y, z, _] = properties.inertia.column;
let tensor = [[x.x, y.x, z.x], [x.y, y.y, z.y], [x.z, y.z, z.z]];
if !tensor.iter().flatten().all(|value| value.is_finite()) {
return false;
}
let off_diagonal = [x.y, x.z, y.x, y.z, z.x, z.y];
if off_diagonal.iter().all(|&value| value == 0.0) {
let diagonal = [x.x, y.y, z.z];
let length_sq: f32 = diagonal.iter().map(|value| value * value).sum();
return length_sq <= 1.0e-12
|| diagonal
.iter()
.all(|&value| value > 0.0 && (1.0 / value).is_finite());
}
limits::is_rigid_body_inertia(tensor.map(|row| row.map(f64::from)))
}
pub(crate) struct CreationSettings(Owned<JPH_BodyCreationSettings>);
impl JoltObject for JPH_BodyCreationSettings {
unsafe fn destroy(ptr: *mut Self) {
unsafe { JPH_BodyCreationSettings_Destroy(ptr) };
}
}
impl CreationSettings {
pub(crate) fn new(shape: &Shape, settings: &BodySettings) -> Result<Self, BodyError> {
let position = settings.position.to_jph();
let rotation = settings.rotation.to_jph();
let this = unsafe {
Owned::from_raw(JPH_BodyCreationSettings_Create3(
shape.as_ptr(),
&position,
&rotation,
settings.motion_type.to_jph(),
settings.object_layer.get(),
))
}
.map(Self)
.ok_or(BodyError::AllocationFailed)?;
let ptr = this.0.as_ptr();
let linear_velocity = settings.linear_velocity.to_jph();
let angular_velocity = settings.angular_velocity.to_jph();
unsafe {
JPH_BodyCreationSettings_SetLinearVelocity(ptr, &linear_velocity);
JPH_BodyCreationSettings_SetAngularVelocity(ptr, &angular_velocity);
JPH_BodyCreationSettings_SetFriction(ptr, settings.friction);
JPH_BodyCreationSettings_SetRestitution(ptr, settings.restitution);
JPH_BodyCreationSettings_SetLinearDamping(ptr, settings.linear_damping);
JPH_BodyCreationSettings_SetAngularDamping(ptr, settings.angular_damping);
JPH_BodyCreationSettings_SetMotionQuality(ptr, settings.motion_quality.to_jph());
JPH_BodyCreationSettings_SetGravityFactor(ptr, settings.gravity_factor);
JPH_BodyCreationSettings_SetAllowSleeping(ptr, settings.allow_sleeping);
JPH_BodyCreationSettings_SetEnhancedInternalEdgeRemoval(
ptr,
settings.enhanced_internal_edge_removal,
);
JPH_BodyCreationSettings_SetAllowedDOFs(ptr, settings.allowed_dofs.to_jph());
JPH_BodyCreationSettings_SetIsSensor(ptr, settings.sensor);
JPH_BodyCreationSettings_SetUserData(ptr, settings.user_data);
JPH_BodyCreationSettings_SetAllowDynamicOrKinematic(
ptr,
settings.allow_dynamic_or_kinematic,
);
}
if let Some(group) = &settings.collision_group {
let group = group.to_jph();
unsafe { JPH_BodyCreationSettings_SetCollisionGroup(ptr, &group) };
}
if let Some(mass) = settings.mass {
let mass_properties = JPH_MassProperties {
mass,
..ZERO_MASS_PROPERTIES
};
unsafe {
JPH_BodyCreationSettings_SetOverrideMassProperties(
ptr,
JPH_OverrideMassProperties_CalculateInertia,
);
JPH_BodyCreationSettings_SetMassPropertiesOverride(ptr, &mass_properties);
}
}
Ok(this)
}
pub(crate) fn as_ptr(&self) -> *mut JPH_BodyCreationSettings {
self.0.as_ptr()
}
}
pub(crate) const INVALID_BODY_ID: JPH_BodyID = 0xffff_ffff;
#[cfg(test)]
mod tests;