use std::collections::BTreeMap;
use std::ptr::{null_mut, NonNull};
use oxijolt_sys::*;
use super::{initialize, CompoundSubShape, Shape, ShapeSettings, SubShapeId};
use crate::{limits, Quat, ShapeError, Vec3};
pub(super) const EMPTY_COMPOUND_RULE: &str = "a compound needs at least one child";
pub(super) const CHILD_POSITION_RULE: &str =
"compound child position must be finite and within limits::MAX_SHAPE_EXTENT";
pub(super) const CHILD_ROTATION_RULE: &str =
"compound child rotation must be a finite unit quaternion";
pub(super) const SUB_SHAPE_ID_RULE: &str =
"compound hierarchy does not fit Jolt's 32-bit sub-shape ids";
const ID_BITS: u32 = 32;
pub(super) fn index_bits(count: u32) -> u32 {
ID_BITS - count.saturating_sub(1).leading_zeros()
}
pub(super) fn check_child_pose(position: Vec3, rotation: Quat) -> Result<(), ShapeError> {
if !limits::is_local_offset(position) {
return Err(ShapeError::InvalidValue(CHILD_POSITION_RULE));
}
if !rotation.is_valid_rotation() {
return Err(ShapeError::InvalidValue(CHILD_ROTATION_RULE));
}
Ok(())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct SubShapeIds {
pub(super) width: u32,
pub(super) zero_width_at: Option<u32>,
pub(super) expanded: u32,
}
pub(super) fn compound_ids(children: impl Iterator<Item = SubShapeIds>, count: u32) -> SubShapeIds {
let bits = index_bits(count);
let own = (count > 0 && bits == 0).then_some(0);
children.fold(
SubShapeIds {
width: bits,
zero_width_at: own,
expanded: 1,
},
|ids, child| SubShapeIds {
width: ids.width.max(bits + child.width),
zero_width_at: ids
.zero_width_at
.max(child.zero_width_at.map(|at| at + bits)),
expanded: ids.expanded.saturating_add(child.expanded),
},
)
}
pub(super) fn check_expanded(expanded: u32) -> Result<(), ShapeError> {
if expanded <= limits::MAX_EXPANDED_SUB_SHAPES {
Ok(())
} else {
Err(ShapeError::TooManySubShapes { expanded })
}
}
pub(super) fn fits_jolt_ids(ids: SubShapeIds) -> bool {
ids.zero_width_at.is_none_or(|at| at < ID_BITS)
}
pub(super) unsafe fn sub_shape_ids(
shape: *const JPH_Shape,
memo: &mut BTreeMap<usize, SubShapeIds>,
) -> SubShapeIds {
if let Some(&known) = memo.get(&(shape as usize)) {
return known;
}
#[cfg(test)]
walk_count::visited();
let sub_type = unsafe { JPH_Shape_GetSubType(shape) };
let compound: *const JPH_CompoundShape = shape.cast();
let is_compound =
sub_type == JPH_ShapeSubType_StaticCompound || sub_type == JPH_ShapeSubType_MutableCompound;
let count = is_compound.then(|| unsafe { JPH_CompoundShape_GetNumSubShapes(compound) });
let ids = match count {
Some(count) if count > 0 => {
let children = (0..count).map(|index| {
let mut child = std::ptr::null();
unsafe {
JPH_CompoundShape_GetSubShape(
compound,
index,
&mut child,
null_mut(),
null_mut(),
null_mut(),
);
}
unsafe { sub_shape_ids(child, memo) }
});
compound_ids(children, count)
}
_ if sub_type == JPH_ShapeSubType_Scaled
|| sub_type == JPH_ShapeSubType_OffsetCenterOfMass
|| sub_type == JPH_ShapeSubType_RotatedTranslated =>
{
let inner =
unsafe { sub_shape_ids(JPH_DecoratedShape_GetInnerShape(shape.cast()), memo) };
SubShapeIds {
expanded: inner.expanded.saturating_add(1),
..inner
}
}
_ => unsafe { leaf_ids(shape) },
};
memo.insert(shape as usize, ids);
ids
}
unsafe fn leaf_ids(shape: *const JPH_Shape) -> SubShapeIds {
#[cfg(test)]
walk_count::asked_jolt();
SubShapeIds {
width: unsafe { JPH_Shape_GetSubShapeIDBitsRecursive(shape) },
zero_width_at: None,
expanded: 1,
}
}
pub(super) struct RawCompoundChild {
pub(super) shape: *const JPH_Shape,
pub(super) position: Vec3,
pub(super) rotation: Quat,
pub(super) user_data: u32,
pub(super) ids: SubShapeIds,
}
pub(super) unsafe fn build_compound(children: &[RawCompoundChild]) -> Result<Shape, ShapeError> {
if children.is_empty() {
return Err(ShapeError::InvalidValue(EMPTY_COMPOUND_RULE));
}
let ids = compound_ids(
children.iter().map(|child| child.ids),
children.len() as u32,
);
if !fits_jolt_ids(ids) {
return Err(ShapeError::InvalidValue(SUB_SHAPE_ID_RULE));
}
check_expanded(ids.expanded)?;
initialize()?;
let settings = unsafe {
ShapeSettings::from_raw(if children.len() == 1 {
JPH_MutableCompoundShapeSettings_Create().cast()
} else {
JPH_StaticCompoundShapeSettings_Create().cast()
})
}?;
for child in children {
let position = child.position.to_jph();
let rotation = child.rotation.to_jph();
unsafe {
JPH_CompoundShapeSettings_AddShape2(
settings.as_ptr(),
&position,
&rotation,
child.shape,
child.user_data,
);
}
}
settings.create()?.within_extent_bounds()
}
pub(crate) unsafe fn compound_sub_shape_of(
root: NonNull<JPH_Shape>,
id: SubShapeId,
) -> Option<CompoundSubShape> {
let root = root.as_ptr();
let sub_type = unsafe { JPH_Shape_GetSubType(root) };
if sub_type != JPH_ShapeSubType_StaticCompound && sub_type != JPH_ShapeSubType_MutableCompound {
return None;
}
let compound: *const JPH_CompoundShape = root.cast();
let count = unsafe { JPH_CompoundShape_GetNumSubShapes(compound) };
if count == 0 {
return None;
}
let bits = index_bits(count);
let mask = ((1_u64 << bits) - 1) as u32;
if id.to_raw() & mask >= count {
return None;
}
let mut remainder: JPH_SubShapeID = 0;
let index =
unsafe { JPH_CompoundShape_GetSubShapeIndexFromID(compound, id.to_raw(), &mut remainder) };
if index >= count {
return None;
}
let mut user_data = 0;
unsafe {
JPH_CompoundShape_GetSubShape(
compound,
index,
null_mut(),
null_mut(),
null_mut(),
&mut user_data,
);
}
Some(CompoundSubShape { index, user_data })
}
#[cfg(test)]
pub(super) mod walk_count {
use std::cell::Cell;
thread_local! {
static COUNTS: Cell<(u32, u32)> = const { Cell::new((0, 0)) };
}
pub(super) fn visited() {
COUNTS.set((COUNTS.get().0 + 1, COUNTS.get().1));
}
pub(super) fn asked_jolt() {
COUNTS.set((COUNTS.get().0, COUNTS.get().1 + 1));
}
pub(in crate::shape) fn take() -> (u32, u32) {
COUNTS.replace((0, 0))
}
}