use std::fmt;
use crate::{AnyConstraintId, AnyVehicleId, BodyId, CharacterId, ObjectLayer, RagdollId, Vec3};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum WorldError {
InitFailed,
InvalidValue(&'static str),
InvalidLayers(&'static str),
AllocationFailed(&'static str),
}
impl fmt::Display for WorldError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InitFailed => f.write_str("Jolt initialisation failed"),
Self::InvalidValue(what) => write!(f, "invalid world value: {what}"),
Self::InvalidLayers(what) => write!(f, "invalid collision layers: {what}"),
Self::AllocationFailed(what) => write!(f, "could not create the {what}"),
}
}
}
impl std::error::Error for WorldError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ShapeError {
InitFailed,
InvalidValue(&'static str),
ConvexHull(ConvexHullError),
Mesh(MeshError),
ThinTriangles(ThinTrianglesError),
Rejected(JoltMessage),
AllocationFailed,
NoSubShape {
index: u32,
count: u32,
},
TooManySubShapes {
expanded: u32,
},
BinaryState(BinaryStateError),
}
impl fmt::Display for ShapeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InitFailed => f.write_str("Jolt initialisation failed"),
Self::InvalidValue(what) => write!(f, "invalid shape value: {what}"),
Self::ConvexHull(error) => write!(f, "invalid convex hull: {error}"),
Self::Mesh(error) => write!(f, "invalid triangle mesh: {error}"),
Self::ThinTriangles(error) => write!(f, "invalid scale: {error}"),
Self::Rejected(message) => write!(f, "Jolt rejected the shape settings: {message}"),
Self::AllocationFailed => f.write_str("could not create the shape"),
Self::NoSubShape { index, count } => {
write!(f, "compound has no sub-shape {index} (it has {count})")
}
Self::TooManySubShapes { expanded } => write!(
f,
"compound expands to {expanded} shapes, above limits::MAX_EXPANDED_SUB_SHAPES"
),
Self::BinaryState(error) => write!(f, "invalid shape binary state: {error}"),
}
}
}
impl std::error::Error for ShapeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::BinaryState(error) => Some(error),
Self::ConvexHull(error) => Some(error),
Self::Mesh(error) => Some(error),
Self::ThinTriangles(error) => Some(error),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConvexHullError {
TooFewPoints,
Degenerate,
Coplanar,
}
impl fmt::Display for ConvexHullError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::TooFewPoints => "a convex hull needs at least 4 points",
Self::Degenerate => "the points lie on or close to a line",
Self::Coplanar => {
"the points lie on or close to a plane; thicken the cloud or centre it on the shape origin"
}
})
}
}
impl std::error::Error for ConvexHullError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MeshError {
NoTriangles,
}
impl fmt::Display for MeshError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::NoTriangles => {
"no triangle is left after dropping small, thin and degenerate ones"
}
})
}
}
impl std::error::Error for MeshError {}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct ThinTrianglesError {
pub scale: Vec3,
pub max_convex_extent: f32,
}
impl PartialEq for ThinTrianglesError {
fn eq(&self, other: &Self) -> bool {
let bits = |error: &Self| {
[
error.scale.x,
error.scale.y,
error.scale.z,
error.max_convex_extent,
]
.map(f32::to_bits)
};
bits(self) == bits(other)
}
}
impl Eq for ThinTrianglesError {}
impl fmt::Display for ThinTrianglesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Vec3 { x, y, z } = self.scale;
write!(
f,
"mesh or heightfield triangles scaled by ({x}, {y}, {z}) are too thin for convex shapes up to {} m",
self.max_convex_extent
)
}
}
impl std::error::Error for ThinTrianglesError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BinaryStateError {
NotBinaryState,
OtherBuild,
Truncated,
Corrupt,
Malformed(&'static str),
Rejected(JoltMessage),
}
impl BinaryStateError {
pub const FORMAT_VERSION: u32 = 1;
}
impl fmt::Display for BinaryStateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotBinaryState => f.write_str("the bytes are not shape binary state"),
Self::OtherBuild => f.write_str("the bytes were saved by another build"),
Self::Truncated => f.write_str("the bytes end early"),
Self::Corrupt => f.write_str("the checksum does not match"),
Self::Malformed(rule) => write!(f, "malformed bytes: {rule}"),
Self::Rejected(message) => write!(f, "joltc refused the bytes: {message}"),
}
}
}
impl std::error::Error for BinaryStateError {}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct JoltMessage {
len: u8,
bytes: [u8; JoltMessage::CAPACITY],
}
impl JoltMessage {
pub const CAPACITY: usize = 79;
const CUT: &'static str = "...";
pub(crate) fn from_c_buffer(buffer: &[u8]) -> Self {
let end = buffer
.iter()
.position(|&byte| byte == 0)
.unwrap_or(buffer.len());
let text = &buffer[..end];
let (kept, cut) = if text.len() <= Self::CAPACITY {
(valid_prefix(text), "")
} else {
let room = valid_prefix(&text[..Self::CAPACITY - Self::CUT.len()]);
let words = room
.rfind(' ')
.map_or(room, |space| room[..space].trim_end());
(words, Self::CUT)
};
let mut bytes = [0; Self::CAPACITY];
bytes[..kept.len()].copy_from_slice(kept.as_bytes());
bytes[kept.len()..kept.len() + cut.len()].copy_from_slice(cut.as_bytes());
Self {
len: (kept.len() + cut.len()) as u8,
bytes,
}
}
pub fn as_str(&self) -> &str {
std::str::from_utf8(&self.bytes[..usize::from(self.len)]).unwrap_or_default()
}
}
fn valid_prefix(bytes: &[u8]) -> &str {
let valid = match std::str::from_utf8(bytes) {
Ok(_) => bytes.len(),
Err(error) => error.valid_up_to(),
};
std::str::from_utf8(&bytes[..valid]).unwrap_or_default()
}
impl fmt::Display for JoltMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for JoltMessage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ContactSettingsError {
Friction,
Restitution,
InverseMassScale,
InverseInertiaScale,
SensorBody,
SurfaceVelocity,
}
impl fmt::Display for ContactSettingsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Friction => "contact friction out of range",
Self::Restitution => "contact restitution out of range",
Self::InverseMassScale => "inverse mass scale out of range",
Self::InverseInertiaScale => "inverse inertia scale out of range",
Self::SensorBody => "a contact with a sensor body stays a sensor contact",
Self::SurfaceVelocity => "surface velocity out of range",
})
}
}
impl std::error::Error for ContactSettingsError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum CollisionGroupError {
InitFailed,
NoSubGroups,
TooManySubGroups(u32),
SubGroupOutOfRange(u32),
SameSubGroup(u32),
GroupIdOutOfRange(u32),
}
impl fmt::Display for CollisionGroupError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InitFailed => f.write_str("Jolt initialisation failed"),
Self::NoSubGroups => f.write_str("a group filter table needs a sub group"),
Self::TooManySubGroups(count) => write!(f, "too many sub groups: {count}"),
Self::SubGroupOutOfRange(id) => write!(f, "sub group {id} is not in the table"),
Self::SameSubGroup(id) => write!(f, "sub group {id} paired with itself"),
Self::GroupIdOutOfRange(id) => write!(f, "group id {id} is out of range"),
}
}
}
impl std::error::Error for CollisionGroupError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum QueryError {
InvalidValue(&'static str),
WrongWorld(BodyId),
UnknownObjectLayer(ObjectLayer),
AllocationFailed,
}
impl fmt::Display for QueryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidValue(what) => write!(f, "invalid query value: {what}"),
Self::WrongWorld(id) => write!(f, "body {id:?} of the filter belongs to another world"),
Self::UnknownObjectLayer(layer) => write!(
f,
"object layer {} of the filter does not exist in this world",
layer.get()
),
Self::AllocationFailed => f.write_str("could not create a native query object"),
}
}
}
impl std::error::Error for QueryError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum StepError {
InvalidDeltaTime,
}
impl fmt::Display for StepError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidDeltaTime => write!(
f,
"the time step must be finite and between {} and {} s",
crate::PhysicsWorld::MIN_DELTA_TIME,
crate::PhysicsWorld::MAX_DELTA_TIME
),
}
}
}
impl std::error::Error for StepError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BodyError {
NotFound(BodyId),
WrongWorld(BodyId),
UnknownObjectLayer(ObjectLayer),
InvalidValue(&'static str),
TooManyBodies,
AllocationFailed,
OwnedByCharacter(BodyId),
UsedByVehicle(BodyId),
OwnedByRagdoll(BodyId),
UsedByConstraint(BodyId),
NotRigidBody(BodyId),
NotSoftBody(BodyId),
RestrictedDofs(BodyId),
NotKinematic(BodyId),
CannotMove(BodyId),
}
impl fmt::Display for BodyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFound(id) => write!(f, "no body {id:?} in this world"),
Self::WrongWorld(id) => write!(f, "body {id:?} belongs to another world"),
Self::UnknownObjectLayer(layer) => {
write!(
f,
"object layer {} does not exist in this world",
layer.get()
)
}
Self::InvalidValue(what) => write!(f, "invalid body value: {what}"),
Self::TooManyBodies => f.write_str("the world is full"),
Self::AllocationFailed => f.write_str("could not create the body settings"),
Self::OwnedByCharacter(id) => {
write!(f, "body {id:?} is the inner body of a character")
}
Self::UsedByVehicle(id) => write!(f, "body {id:?} is the chassis of a vehicle"),
Self::OwnedByRagdoll(id) => write!(f, "body {id:?} is a part of a ragdoll"),
Self::UsedByConstraint(id) => write!(
f,
"body {id:?} is used by a constraint; remove the constraint first"
),
Self::NotRigidBody(id) => {
write!(
f,
"body {id:?} is a soft body; this operation needs a rigid body"
)
}
Self::NotSoftBody(id) => write!(f, "body {id:?} is not a soft body"),
Self::RestrictedDofs(id) => {
write!(f, "body {id:?} does not have all six degrees of freedom")
}
Self::NotKinematic(id) => write!(f, "body {id:?} is not kinematic"),
Self::CannotMove(id) => write!(f, "body {id:?} was created static and cannot move"),
}
}
}
impl std::error::Error for BodyError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum CharacterError {
NotFound(CharacterId),
WrongWorld(CharacterId),
InvalidValue(&'static str),
TooManyBodies,
TooManyCharacters,
Query(QueryError),
RestoreFailed,
}
impl fmt::Display for CharacterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFound(id) => write!(f, "no character {id:?} in this world"),
Self::WrongWorld(id) => write!(f, "character {id:?} belongs to another world"),
Self::InvalidValue(what) => write!(f, "invalid character value: {what}"),
Self::TooManyBodies => f.write_str("the world is full; no room for the inner body"),
Self::TooManyCharacters => f.write_str("the world has no character ids left"),
Self::Query(error) => write!(f, "unusable character query filter: {error}"),
Self::RestoreFailed => f.write_str("the character state could not be restored"),
}
}
}
impl std::error::Error for CharacterError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Query(error) => Some(error),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum VehicleError {
NotFound(AnyVehicleId),
WrongWorld(AnyVehicleId),
InvalidValue(&'static str),
Body(BodyError),
NotDynamic(BodyId),
AlreadyHasVehicle(BodyId),
TooManyVehicles,
LeanSpringIntegrationNotSaved,
}
impl fmt::Display for VehicleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFound(id) => write!(f, "no vehicle {id:?} in this world"),
Self::WrongWorld(id) => write!(f, "vehicle {id:?} belongs to another world"),
Self::InvalidValue(what) => write!(f, "invalid vehicle value: {what}"),
Self::Body(error) => write!(f, "unusable chassis body: {error}"),
Self::NotDynamic(id) => write!(f, "chassis body {id:?} is not dynamic"),
Self::AlreadyHasVehicle(id) => write!(f, "body {id:?} already carries a vehicle"),
Self::TooManyVehicles => f.write_str("the world has no vehicle ids left"),
Self::LeanSpringIntegrationNotSaved => f.write_str(
"a motorcycle's lean spring integration coefficient must be 0: Jolt does not save the integrated lean angle",
),
}
}
}
impl std::error::Error for VehicleError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Body(error) => Some(error),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum RagdollError {
InitFailed,
NotFound(RagdollId),
WrongWorld(RagdollId),
InvalidValue(&'static str),
UnknownObjectLayer(ObjectLayer),
Body(BodyError),
TooManyBodies,
TooManyRagdolls,
UnmappedJoint(u32),
HierarchyMismatch(u32),
DegenerateChain(u32),
}
impl fmt::Display for RagdollError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InitFailed => f.write_str("Jolt initialisation failed"),
Self::NotFound(id) => write!(f, "no ragdoll {id:?} in this world"),
Self::WrongWorld(id) => write!(f, "ragdoll {id:?} belongs to another world"),
Self::InvalidValue(what) => write!(f, "invalid ragdoll value: {what}"),
Self::UnknownObjectLayer(layer) => write!(
f,
"object layer {} of a ragdoll part does not exist in this world",
layer.get()
),
Self::Body(error) => write!(f, "unusable ragdoll part: {error}"),
Self::TooManyBodies => f.write_str("the world has no room for every ragdoll part"),
Self::TooManyRagdolls => f.write_str("the world has no ragdoll ids left"),
Self::UnmappedJoint(joint) => {
write!(
f,
"ragdoll joint {joint} has no animation joint of its name"
)
}
Self::HierarchyMismatch(joint) => write!(
f,
"the animation joint of ragdoll joint {joint} is not below its parent's"
),
Self::DegenerateChain(joint) => write!(
f,
"the mapped chain ending at ragdoll joint {joint} is too short to turn"
),
}
}
}
impl std::error::Error for RagdollError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Body(error) => Some(error),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConstraintError {
NotFound(AnyConstraintId),
WrongWorld(AnyConstraintId),
InvalidValue(&'static str),
Body(BodyError),
NotDynamic(BodyId),
UsedByConstraint(AnyConstraintId),
TooManyConstraints,
}
impl fmt::Display for ConstraintError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFound(id) => write!(f, "no constraint {id:?} in this world"),
Self::WrongWorld(id) => write!(f, "constraint {id:?} belongs to another world"),
Self::InvalidValue(what) => write!(f, "invalid constraint value: {what}"),
Self::Body(error) => write!(f, "unusable constraint body: {error}"),
Self::NotDynamic(id) => write!(f, "constraint body {id:?} is not dynamic"),
Self::UsedByConstraint(id) => write!(
f,
"constraint {id:?} is referenced by another constraint; remove that one first"
),
Self::TooManyConstraints => f.write_str("the world has no constraint ids left"),
}
}
}
impl std::error::Error for ConstraintError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Body(error) => Some(error),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SoftBodyError {
InitFailed,
InvalidValue(&'static str),
}
impl fmt::Display for SoftBodyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InitFailed => f.write_str("Jolt initialisation failed"),
Self::InvalidValue(what) => write!(f, "invalid soft body value: {what}"),
}
}
}
impl std::error::Error for SoftBodyError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum StateError {
WrongWorld,
WorldChanged,
RestoreFailed,
Body(BodyError),
}
impl fmt::Display for StateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::WrongWorld => f.write_str("the state was saved by another world"),
Self::WorldChanged => {
f.write_str("the world's objects have changed since the state was saved")
}
Self::RestoreFailed => f.write_str("the state could not be restored"),
Self::Body(error) => write!(f, "invalid body in the selection: {error}"),
}
}
}
impl std::error::Error for StateError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Body(error) => Some(error),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
World(WorldError),
Shape(ShapeError),
Body(BodyError),
Step(StepError),
Query(QueryError),
ContactSettings(ContactSettingsError),
Character(CharacterError),
Vehicle(VehicleError),
Ragdoll(RagdollError),
Constraint(ConstraintError),
SoftBody(SoftBodyError),
State(StateError),
CollisionGroup(CollisionGroupError),
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
impl From<WorldError> for Error {
fn from(error: WorldError) -> Self {
Self::World(error)
}
}
impl From<ShapeError> for Error {
fn from(error: ShapeError) -> Self {
Self::Shape(error)
}
}
impl From<BodyError> for Error {
fn from(error: BodyError) -> Self {
Self::Body(error)
}
}
impl From<StepError> for Error {
fn from(error: StepError) -> Self {
Self::Step(error)
}
}
impl From<QueryError> for Error {
fn from(error: QueryError) -> Self {
Self::Query(error)
}
}
impl From<ContactSettingsError> for Error {
fn from(error: ContactSettingsError) -> Self {
Self::ContactSettings(error)
}
}
impl From<CharacterError> for Error {
fn from(error: CharacterError) -> Self {
Self::Character(error)
}
}
impl From<VehicleError> for Error {
fn from(error: VehicleError) -> Self {
Self::Vehicle(error)
}
}
impl From<RagdollError> for Error {
fn from(error: RagdollError) -> Self {
Self::Ragdoll(error)
}
}
impl From<ConstraintError> for Error {
fn from(error: ConstraintError) -> Self {
Self::Constraint(error)
}
}
impl From<SoftBodyError> for Error {
fn from(error: SoftBodyError) -> Self {
Self::SoftBody(error)
}
}
impl From<StateError> for Error {
fn from(error: StateError) -> Self {
Self::State(error)
}
}
impl From<CollisionGroupError> for Error {
fn from(error: CollisionGroupError) -> Self {
Self::CollisionGroup(error)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::World(error) => fmt::Display::fmt(error, f),
Self::Shape(error) => fmt::Display::fmt(error, f),
Self::Body(error) => fmt::Display::fmt(error, f),
Self::Step(error) => fmt::Display::fmt(error, f),
Self::Query(error) => fmt::Display::fmt(error, f),
Self::ContactSettings(error) => fmt::Display::fmt(error, f),
Self::Character(error) => fmt::Display::fmt(error, f),
Self::Vehicle(error) => fmt::Display::fmt(error, f),
Self::Ragdoll(error) => fmt::Display::fmt(error, f),
Self::Constraint(error) => fmt::Display::fmt(error, f),
Self::SoftBody(error) => fmt::Display::fmt(error, f),
Self::State(error) => fmt::Display::fmt(error, f),
Self::CollisionGroup(error) => fmt::Display::fmt(error, f),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::World(error) => error.source(),
Self::Shape(error) => error.source(),
Self::Body(error) => error.source(),
Self::Step(error) => error.source(),
Self::Query(error) => error.source(),
Self::ContactSettings(error) => error.source(),
Self::Character(error) => error.source(),
Self::Vehicle(error) => error.source(),
Self::Ragdoll(error) => error.source(),
Self::Constraint(error) => error.source(),
Self::SoftBody(error) => error.source(),
Self::State(error) => error.source(),
Self::CollisionGroup(error) => error.source(),
}
}
}
#[cfg(test)]
mod tests;