use std::collections::BTreeMap;
use std::fmt;
use std::ptr::{null, null_mut};
use oxijolt_sys::*;
use super::compound::{
build_compound, check_child_pose, check_expanded, compound_ids, fits_jolt_ids, sub_shape_ids,
RawCompoundChild, SubShapeIds, EMPTY_COMPOUND_RULE, SUB_SHAPE_ID_RULE,
};
use super::{initialize, CompoundChild, Shape, ShapeSettings};
use crate::owned::Owned;
use crate::{Quat, ShapeError, Vec3};
const MAX_CHILDREN: u32 = u32::MAX - 3;
const CHILD_COUNT_RULE: &str = "a mutable compound has at most u32::MAX - 3 children";
const ID_SLOTS: usize = 33;
pub struct MutableCompound {
holder: Owned<JPH_Shape>,
children: Vec<ChildEntry>,
ids: IdHistogram,
expanded: u32,
}
#[derive(Clone, Copy)]
struct ChildEntry {
position: Vec3,
rotation: Quat,
user_data: u32,
ids: SubShapeIds,
}
unsafe impl Send for MutableCompound {}
unsafe impl Sync for MutableCompound {}
impl fmt::Debug for MutableCompound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MutableCompound")
.field("sub_shape_count", &self.sub_shape_count())
.finish_non_exhaustive()
}
}
fn fits_child_count(count: usize) -> bool {
count <= MAX_CHILDREN as usize
}
fn check_children_expanded(children: u32) -> Result<(), ShapeError> {
check_expanded(children.saturating_add(1))
}
fn child_ids(shape: &Shape) -> SubShapeIds {
unsafe { sub_shape_ids(shape.as_ptr(), &mut BTreeMap::new()) }
}
impl MutableCompound {
pub fn new() -> Result<Self, ShapeError> {
Self::from_children(&[])
}
pub fn from_children(children: &[CompoundChild<'_>]) -> Result<Self, ShapeError> {
if !fits_child_count(children.len()) {
return Err(ShapeError::InvalidValue(CHILD_COUNT_RULE));
}
for child in children {
check_child_pose(child.position, child.rotation)?;
}
let mut memo = BTreeMap::new();
let mut ids = IdHistogram::new();
let mut expanded = 0_u32;
let entries: Vec<_> = children
.iter()
.map(|child| {
let child_ids = unsafe { sub_shape_ids(child.shape.as_ptr(), &mut memo) };
ids.count(child_ids, 1);
expanded = expanded.saturating_add(child_ids.expanded);
ChildEntry {
position: child.position,
rotation: child.rotation,
user_data: child.user_data,
ids: child_ids,
}
})
.collect();
if !ids.fits(entries.len() as u32, None) {
return Err(ShapeError::InvalidValue(SUB_SHAPE_ID_RULE));
}
check_children_expanded(expanded)?;
Ok(Self {
holder: holder_of(children)?,
children: entries,
ids,
expanded,
})
}
pub fn sub_shape_count(&self) -> u32 {
self.children.len() as u32
}
pub fn sub_shape_user_data(&self, index: u32) -> Option<u32> {
self.children
.get(index as usize)
.map(|entry| entry.user_data)
}
pub fn add_shape(&mut self, child: &CompoundChild<'_>) -> Result<u32, ShapeError> {
let index = self.sub_shape_count();
if !fits_child_count(self.children.len() + 1) {
return Err(ShapeError::InvalidValue(CHILD_COUNT_RULE));
}
check_child_pose(child.position, child.rotation)?;
let ids = child_ids(child.shape);
if !self.ids.fits(index + 1, Some(ids)) {
return Err(ShapeError::InvalidValue(SUB_SHAPE_ID_RULE));
}
let expanded = self.expanded.saturating_add(ids.expanded);
check_children_expanded(expanded)?;
let (position, rotation) = (Vec3::ZERO.to_jph(), Quat::IDENTITY.to_jph());
unsafe {
JPH_MutableCompoundShape_AddShape(
self.holder.as_ptr().cast(),
&position,
&rotation,
child.shape.as_ptr(),
0,
u32::MAX,
);
}
self.ids.count(ids, 1);
self.expanded = expanded;
self.children.push(ChildEntry {
position: child.position,
rotation: child.rotation,
user_data: child.user_data,
ids,
});
Ok(index)
}
pub fn remove_shape(&mut self, index: u32) -> Result<(), ShapeError> {
self.check_index(index)?;
unsafe { JPH_MutableCompoundShape_RemoveShape(self.holder.as_ptr().cast(), index) };
let entry = self.children.remove(index as usize);
self.ids.count(entry.ids, -1);
self.expanded -= entry.ids.expanded;
Ok(())
}
pub fn modify_shape(
&mut self,
index: u32,
position: Vec3,
rotation: Quat,
shape: Option<&Shape>,
) -> Result<(), ShapeError> {
self.check_index(index)?;
check_child_pose(position, rotation)?;
if let Some(shape) = shape {
let ids = child_ids(shape);
if !self.ids.fits(self.sub_shape_count(), Some(ids)) {
return Err(ShapeError::InvalidValue(SUB_SHAPE_ID_RULE));
}
let old = self.children[index as usize].ids;
let expanded = (self.expanded - old.expanded).saturating_add(ids.expanded);
check_children_expanded(expanded)?;
let (zero, identity) = (Vec3::ZERO.to_jph(), Quat::IDENTITY.to_jph());
unsafe {
JPH_MutableCompoundShape_ModifyShape2(
self.holder.as_ptr().cast(),
index,
&zero,
&identity,
shape.as_ptr(),
);
}
self.ids.count(old, -1);
self.ids.count(ids, 1);
self.expanded = expanded;
self.children[index as usize].ids = ids;
}
let entry = &mut self.children[index as usize];
entry.position = position;
entry.rotation = rotation;
Ok(())
}
pub fn to_shape(&self) -> Result<Shape, ShapeError> {
if self.children.is_empty() {
return Err(ShapeError::InvalidValue(EMPTY_COMPOUND_RULE));
}
let raw: Vec<_> = (0..self.sub_shape_count())
.zip(&self.children)
.map(|(index, entry)| RawCompoundChild {
shape: self.child_shape(index),
position: entry.position,
rotation: entry.rotation,
user_data: entry.user_data,
ids: entry.ids,
})
.collect();
unsafe { build_compound(&raw) }
}
fn child_shape(&self, index: u32) -> *const JPH_Shape {
debug_assert!(index < self.sub_shape_count());
let mut shape = null();
unsafe {
JPH_CompoundShape_GetSubShape(
self.holder.as_ptr().cast(),
index,
&mut shape,
null_mut(),
null_mut(),
null_mut(),
);
}
shape
}
fn check_index(&self, index: u32) -> Result<(), ShapeError> {
let count = self.sub_shape_count();
if index < count {
Ok(())
} else {
Err(ShapeError::NoSubShape { index, count })
}
}
}
struct IdHistogram {
widths: [u32; ID_SLOTS],
zero_widths: [u32; ID_SLOTS],
}
impl IdHistogram {
fn new() -> Self {
Self {
widths: [0; ID_SLOTS],
zero_widths: [0; ID_SLOTS],
}
}
fn count(&mut self, ids: SubShapeIds, by: i32) {
let slot = |bits: u32| (bits as usize).min(ID_SLOTS - 1);
let width = &mut self.widths[slot(ids.width)];
*width = width.wrapping_add_signed(by);
if let Some(at) = ids.zero_width_at {
let zero = &mut self.zero_widths[slot(at)];
*zero = zero.wrapping_add_signed(by);
}
}
fn fits(&self, count: u32, extra: Option<SubShapeIds>) -> bool {
let highest = |slots: &[u32; ID_SLOTS]| slots.iter().rposition(|&n| n > 0);
let counted = SubShapeIds {
width: highest(&self.widths).map_or(0, |slot| slot as u32),
zero_width_at: highest(&self.zero_widths).map(|slot| slot as u32),
expanded: 0,
};
let ids = compound_ids([Some(counted), extra].into_iter().flatten(), count);
ids.width <= 32 && fits_jolt_ids(ids)
}
}
fn holder_of(children: &[CompoundChild<'_>]) -> Result<Owned<JPH_Shape>, ShapeError> {
initialize()?;
let settings =
unsafe { ShapeSettings::from_raw(JPH_MutableCompoundShapeSettings_Create().cast()) }?;
let (position, rotation) = (Vec3::ZERO.to_jph(), Quat::IDENTITY.to_jph());
for child in children {
unsafe {
JPH_CompoundShapeSettings_AddShape2(
settings.as_ptr(),
&position,
&rotation,
child.shape.as_ptr(),
0,
);
}
}
let Shape(holder) = settings.create()?;
Ok(holder)
}
#[cfg(test)]
mod tests;