#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
use crate::core::box3d_lock;
use crate::core::callback_state;
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
use crate::core::callback_state::LocalCallbackState;
use crate::core::units;
use crate::core::validation;
use crate::error::{Error, InvalidValueReason, Result};
use crate::types::{Aabb, Filter, Transform, Vec3};
use boxddd_sys::ffi;
use std::ffi::CString;
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::forget;
use std::ptr::NonNull;
use std::rc::Rc;
use std::slice;
pub const HEIGHT_FIELD_HOLE: u8 = ffi::B3_HEIGHT_FIELD_HOLE as u8;
pub const MAX_COMPOUND_MESH_MATERIALS: usize = ffi::B3_MAX_COMPOUND_MESH_MATERIALS as usize;
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SurfaceMaterial {
pub friction: f32,
pub restitution: f32,
pub rolling_resistance: f32,
pub tangent_velocity: Vec3,
pub user_material_id: u64,
pub custom_color: u32,
}
impl Default for SurfaceMaterial {
fn default() -> Self {
Self {
friction: 0.6,
restitution: 0.0,
rolling_resistance: 0.0,
tangent_velocity: Vec3::ZERO,
user_material_id: 0,
custom_color: 0,
}
}
}
impl SurfaceMaterial {
#[inline]
pub fn from_raw(raw: ffi::b3SurfaceMaterial) -> Self {
Self {
friction: raw.friction,
restitution: raw.restitution,
rolling_resistance: raw.rollingResistance,
tangent_velocity: Vec3::from_raw(raw.tangentVelocity),
user_material_id: raw.userMaterialId,
custom_color: raw.customColor,
}
}
#[inline]
pub fn into_raw(self) -> ffi::b3SurfaceMaterial {
ffi::b3SurfaceMaterial {
friction: self.friction,
restitution: self.restitution,
rollingResistance: self.rolling_resistance,
tangentVelocity: self.tangent_velocity.into_raw(),
userMaterialId: self.user_material_id,
customColor: self.custom_color,
padding: 0,
}
}
pub fn validate(self) -> Result<()> {
validation::nonnegative("surface_material.friction", self.friction)?;
validation::nonnegative("surface_material.restitution", self.restitution)?;
validation::nonnegative(
"surface_material.rolling_resistance",
self.rolling_resistance,
)?;
validation::vec3("surface_material.tangent_velocity", self.tangent_velocity)
}
}
impl Default for ShapeDef {
fn default() -> Self {
let length_units = units::current_length_units();
Self {
name: None,
materials: Vec::new(),
base_material: SurfaceMaterial::default(),
density: 1000.0 / (length_units * length_units * length_units),
explosion_scale: 1.0,
filter: Filter {
category_bits: u64::MAX,
mask_bits: u64::MAX,
group_index: 0,
},
enable_custom_filtering: false,
sensor: false,
enable_sensor_events: false,
enable_contact_events: false,
enable_hit_events: false,
enable_pre_solve_events: false,
invoke_contact_creation: true,
update_body_mass: true,
enable_speculative_contact: true,
}
}
}
#[derive(Clone, Debug)]
pub struct ShapeDef {
pub name: Option<String>,
pub materials: Vec<SurfaceMaterial>,
pub base_material: SurfaceMaterial,
pub density: f32,
pub explosion_scale: f32,
pub filter: Filter,
pub enable_custom_filtering: bool,
pub sensor: bool,
pub enable_sensor_events: bool,
pub enable_contact_events: bool,
pub enable_hit_events: bool,
pub enable_pre_solve_events: bool,
pub invoke_contact_creation: bool,
pub update_body_mass: bool,
pub enable_speculative_contact: bool,
}
impl ShapeDef {
#[inline]
pub fn builder() -> ShapeDefBuilder {
ShapeDefBuilder::new()
}
pub const fn filter(&self) -> Filter {
self.filter
}
pub const fn surface_material(&self) -> SurfaceMaterial {
self.base_material
}
pub fn validate(&self) -> Result<()> {
if let Some(name) = self.name.as_deref() {
validation::c_string_value("shape.name", name)?;
}
validation::count_i32("shape.materials", self.materials.len())?;
self.base_material.validate()?;
for material in &self.materials {
material.validate()?;
}
validation::nonnegative("shape.density", self.density)?;
validation::finite("shape.explosion_scale", self.explosion_scale)
}
pub(crate) fn prepare(
&self,
material_usage: ShapeMaterialUsage,
) -> Result<PreparedShapeDef<'_>> {
self.validate()?;
let name = validation::optional_c_string("shape.name", self.name.as_deref())?;
let materials = match material_usage {
ShapeMaterialUsage::BaseOnly => Vec::new(),
ShapeMaterialUsage::PerTriangle => self
.materials
.iter()
.copied()
.map(SurfaceMaterial::into_raw)
.collect(),
};
Ok(PreparedShapeDef {
def: self,
name,
materials,
})
}
}
#[derive(Copy, Clone, Debug)]
pub(crate) enum ShapeMaterialUsage {
BaseOnly,
PerTriangle,
}
pub(crate) struct PreparedShapeDef<'a> {
def: &'a ShapeDef,
name: Option<CString>,
materials: Vec<ffi::b3SurfaceMaterial>,
}
impl PreparedShapeDef<'_> {
pub(crate) fn lower_locked(&mut self) -> ffi::b3ShapeDef {
let mut raw = unsafe { ffi::b3DefaultShapeDef() };
raw.name = self
.name
.as_ref()
.map_or(std::ptr::null(), |name| name.as_ptr());
raw.userData = std::ptr::null_mut();
raw.materials = if self.materials.is_empty() {
std::ptr::null_mut()
} else {
self.materials.as_mut_ptr()
};
raw.materialCount = i32::try_from(self.materials.len())
.expect("validated shape material count no longer fits in i32");
raw.baseMaterial = self.def.base_material.into_raw();
raw.density = self.def.density;
raw.explosionScale = self.def.explosion_scale;
raw.filter = self.def.filter.into_raw();
raw.enableCustomFiltering = self.def.enable_custom_filtering;
raw.isSensor = self.def.sensor;
raw.enableSensorEvents = self.def.enable_sensor_events;
raw.enableContactEvents = self.def.enable_contact_events;
raw.enableHitEvents = self.def.enable_hit_events;
raw.enablePreSolveEvents = self.def.enable_pre_solve_events;
raw.invokeContactCreation = self.def.invoke_contact_creation;
raw.updateBodyMass = self.def.update_body_mass;
raw.enableSpeculativeContact = self.def.enable_speculative_contact;
raw
}
}
#[derive(Clone, Debug)]
pub struct ShapeDefBuilder {
def: ShapeDef,
}
impl ShapeDefBuilder {
#[inline]
pub fn new() -> Self {
Self {
def: ShapeDef::default(),
}
}
#[inline]
pub fn density(mut self, density: f32) -> Self {
self.def.density = density;
self
}
#[inline]
pub fn friction(mut self, friction: f32) -> Self {
self.def.base_material.friction = friction;
self
}
#[inline]
pub fn restitution(mut self, restitution: f32) -> Self {
self.def.base_material.restitution = restitution;
self
}
#[inline]
pub fn filter(mut self, filter: Filter) -> Self {
self.def.filter = filter;
self
}
#[inline]
pub fn surface_material(mut self, material: SurfaceMaterial) -> Self {
self.def.base_material = material;
self
}
#[inline]
pub fn user_material_id(mut self, user_material_id: u64) -> Self {
self.def.base_material.user_material_id = user_material_id;
self
}
#[inline]
pub fn sensor(mut self, is_sensor: bool) -> Self {
self.def.sensor = is_sensor;
self
}
#[inline]
pub fn enable_sensor_events(mut self, enabled: bool) -> Self {
self.def.enable_sensor_events = enabled;
self
}
#[inline]
pub fn enable_contact_events(mut self, enabled: bool) -> Self {
self.def.enable_contact_events = enabled;
self
}
#[inline]
pub fn enable_hit_events(mut self, enabled: bool) -> Self {
self.def.enable_hit_events = enabled;
self
}
#[inline]
pub fn enable_pre_solve_events(mut self, enabled: bool) -> Self {
self.def.enable_pre_solve_events = enabled;
self
}
#[inline]
pub fn enable_custom_filtering(mut self, enabled: bool) -> Self {
self.def.enable_custom_filtering = enabled;
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.def.name = Some(name.into());
self
}
pub fn materials(mut self, materials: impl IntoIterator<Item = SurfaceMaterial>) -> Self {
self.def.materials = materials.into_iter().collect();
self
}
pub fn explosion_scale(mut self, scale: f32) -> Self {
self.def.explosion_scale = scale;
self
}
pub fn invoke_contact_creation(mut self, enabled: bool) -> Self {
self.def.invoke_contact_creation = enabled;
self
}
pub fn update_body_mass(mut self, enabled: bool) -> Self {
self.def.update_body_mass = enabled;
self
}
pub fn enable_speculative_contact(mut self, enabled: bool) -> Self {
self.def.enable_speculative_contact = enabled;
self
}
#[inline]
pub fn build(self) -> Result<ShapeDef> {
self.def.validate()?;
Ok(self.def)
}
}
impl Default for ShapeDefBuilder {
fn default() -> Self {
Self::new()
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct Sphere {
raw: ffi::b3Sphere,
}
impl Sphere {
#[inline]
pub fn new(center: impl Into<Vec3>, radius: f32) -> Self {
Self {
raw: ffi::b3Sphere {
center: center.into().into_raw(),
radius,
},
}
}
#[inline]
pub const fn from_raw(raw: ffi::b3Sphere) -> Self {
Self { raw }
}
#[inline]
pub const fn raw(&self) -> &ffi::b3Sphere {
&self.raw
}
pub fn validate(&self) -> Result<()> {
validation::vec3("sphere.center", Vec3::from_raw(self.raw.center))?;
validation::positive("sphere.radius", self.raw.radius)
}
}
impl PartialEq for Sphere {
#[inline]
fn eq(&self, other: &Self) -> bool {
raw_vec3_eq(self.raw.center, other.raw.center) && self.raw.radius == other.raw.radius
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct Capsule {
raw: ffi::b3Capsule,
}
impl Capsule {
#[inline]
pub fn new(center1: impl Into<Vec3>, center2: impl Into<Vec3>, radius: f32) -> Self {
Self {
raw: ffi::b3Capsule {
center1: center1.into().into_raw(),
center2: center2.into().into_raw(),
radius,
},
}
}
#[inline]
pub const fn from_raw(raw: ffi::b3Capsule) -> Self {
Self { raw }
}
#[inline]
pub const fn raw(&self) -> &ffi::b3Capsule {
&self.raw
}
pub fn validate(&self) -> Result<()> {
validation::vec3("capsule.center1", Vec3::from_raw(self.raw.center1))?;
validation::vec3("capsule.center2", Vec3::from_raw(self.raw.center2))?;
validation::positive("capsule.radius", self.raw.radius)
}
}
impl PartialEq for Capsule {
fn eq(&self, other: &Self) -> bool {
raw_vec3_eq(self.raw.center1, other.raw.center1)
&& raw_vec3_eq(self.raw.center2, other.raw.center2)
&& self.raw.radius == other.raw.radius
}
}
#[derive(Copy, Clone, Debug)]
pub struct BoxHull {
raw: ffi::b3BoxHull,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ScaledBox {
pub half_widths: Vec3,
pub transform: Transform,
}
impl BoxHull {
#[inline]
pub fn cube(half_width: f32) -> Result<Self> {
validation::positive("box_hull.half_width", half_width)?;
Ok(Self {
raw: unsafe { ffi::b3MakeCubeHull(half_width) },
})
}
#[inline]
pub fn new(hx: f32, hy: f32, hz: f32) -> Result<Self> {
let half_widths = validate_box_half_widths(Vec3::new(hx, hy, hz))?;
Ok(Self {
raw: unsafe { ffi::b3MakeBoxHull(half_widths.x, half_widths.y, half_widths.z) },
})
}
#[inline]
pub fn offset(hx: f32, hy: f32, hz: f32, offset: impl Into<Vec3>) -> Result<Self> {
let half_widths = validate_box_half_widths(Vec3::new(hx, hy, hz))?;
let offset = offset.into();
validation::vec3("box_hull.offset", offset)?;
Ok(Self {
raw: unsafe {
ffi::b3MakeOffsetBoxHull(
half_widths.x,
half_widths.y,
half_widths.z,
offset.into_raw(),
)
},
})
}
#[inline]
pub fn transformed(hx: f32, hy: f32, hz: f32, transform: Transform) -> Result<Self> {
let half_widths = validate_box_half_widths(Vec3::new(hx, hy, hz))?;
validation::transform("box_hull.transform", transform)?;
Ok(Self {
raw: unsafe {
ffi::b3MakeTransformedBoxHull(
half_widths.x,
half_widths.y,
half_widths.z,
transform.into_raw(),
)
},
})
}
#[inline]
pub fn scaled(
half_widths: impl Into<Vec3>,
transform: Transform,
post_scale: impl Into<Vec3>,
) -> Result<Self> {
let half_widths = validate_box_half_widths(half_widths.into())?;
validation::transform("box_hull.transform", transform)?;
let post_scale = post_scale.into();
validation::vec3("box_hull.post_scale", post_scale)?;
Ok(Self {
raw: unsafe {
ffi::b3MakeScaledBoxHull(
half_widths.into_raw(),
transform.into_raw(),
post_scale.into_raw(),
)
},
})
}
pub fn scale_box(
half_widths: impl Into<Vec3>,
transform: Transform,
post_scale: impl Into<Vec3>,
min_half_width: f32,
) -> Result<ScaledBox> {
let mut half_widths = validate_box_half_widths(half_widths.into())?.into_raw();
validation::transform("box_hull.transform", transform)?;
let mut transform = transform.into_raw();
let post_scale = post_scale.into();
validation::vec3("box_hull.post_scale", post_scale)?;
validation::positive("box_hull.min_half_width", min_half_width)?;
unsafe {
ffi::b3ScaleBox(
&mut half_widths,
&mut transform,
post_scale.into_raw(),
min_half_width,
);
}
let half_widths = Vec3::from_raw(half_widths);
let transform = Transform::from_raw(transform);
validation::vec3("box_hull.scaled_half_widths", half_widths)
.map_err(|_| Error::NativeFailure)?;
validation::transform("box_hull.scaled_transform", transform)
.map_err(|_| Error::NativeFailure)?;
Ok(ScaledBox {
half_widths,
transform,
})
}
#[inline]
pub const fn raw(&self) -> &ffi::b3BoxHull {
&self.raw
}
#[inline]
pub const fn hull_data(&self) -> &ffi::b3HullData {
&self.raw.base
}
}
impl PartialEq for BoxHull {
fn eq(&self, other: &Self) -> bool {
raw_hull_data_eq(&self.raw.base, &other.raw.base)
&& raw_hull_vertices_eq(&self.raw.boxVertices, &other.raw.boxVertices)
&& raw_vec3_array_eq(&self.raw.boxPoints, &other.raw.boxPoints)
&& raw_hull_edges_eq(&self.raw.boxEdges, &other.raw.boxEdges)
&& raw_hull_faces_eq(&self.raw.boxFaces, &other.raw.boxFaces)
&& raw_planes_eq(&self.raw.boxPlanes, &other.raw.boxPlanes)
}
}
#[inline]
fn raw_vec3_eq(a: ffi::b3Vec3, b: ffi::b3Vec3) -> bool {
a.x == b.x && a.y == b.y && a.z == b.z
}
#[inline]
fn raw_matrix3_eq(a: &ffi::b3Matrix3, b: &ffi::b3Matrix3) -> bool {
raw_vec3_eq(a.cx, b.cx) && raw_vec3_eq(a.cy, b.cy) && raw_vec3_eq(a.cz, b.cz)
}
#[inline]
fn raw_aabb_eq(a: &ffi::b3AABB, b: &ffi::b3AABB) -> bool {
raw_vec3_eq(a.lowerBound, b.lowerBound) && raw_vec3_eq(a.upperBound, b.upperBound)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
fn clamp_height_field_query_bounds(a: Aabb, b: ffi::b3AABB) -> Result<Option<Aabb>> {
let b = Aabb::from_raw(b).validate()?;
if a.upper_bound.x < b.lower_bound.x
|| a.lower_bound.x > b.upper_bound.x
|| a.upper_bound.y < b.lower_bound.y
|| a.lower_bound.y > b.upper_bound.y
|| a.upper_bound.z < b.lower_bound.z
|| a.lower_bound.z > b.upper_bound.z
{
Ok(None)
} else {
Aabb {
lower_bound: Vec3::new(
a.lower_bound.x.max(b.lower_bound.x),
a.lower_bound.y,
a.lower_bound.z.max(b.lower_bound.z),
),
upper_bound: Vec3::new(
a.upper_bound.x.min(b.upper_bound.x),
a.upper_bound.y,
a.upper_bound.z.min(b.upper_bound.z),
),
}
.validate()
.map(Some)
}
}
fn raw_hull_data_eq(a: &ffi::b3HullData, b: &ffi::b3HullData) -> bool {
a.version == b.version
&& a.byteCount == b.byteCount
&& a.hash == b.hash
&& raw_aabb_eq(&a.aabb, &b.aabb)
&& a.surfaceArea == b.surfaceArea
&& a.volume == b.volume
&& a.innerRadius == b.innerRadius
&& raw_vec3_eq(a.center, b.center)
&& raw_matrix3_eq(&a.centralInertia, &b.centralInertia)
&& a.vertexCount == b.vertexCount
&& a.vertexOffset == b.vertexOffset
&& a.pointOffset == b.pointOffset
&& a.edgeCount == b.edgeCount
&& a.edgeOffset == b.edgeOffset
&& a.faceCount == b.faceCount
&& a.faceOffset == b.faceOffset
&& a.planeOffset == b.planeOffset
}
fn raw_hull_vertices_eq(a: &[ffi::b3HullVertex; 8], b: &[ffi::b3HullVertex; 8]) -> bool {
a.iter().zip(b).all(|(a, b)| a.edge == b.edge)
}
fn raw_vec3_array_eq(a: &[ffi::b3Vec3; 8], b: &[ffi::b3Vec3; 8]) -> bool {
a.iter().zip(b).all(|(a, b)| raw_vec3_eq(*a, *b))
}
fn raw_hull_edges_eq(a: &[ffi::b3HullHalfEdge; 24], b: &[ffi::b3HullHalfEdge; 24]) -> bool {
a.iter().zip(b).all(|(a, b)| {
a.next == b.next && a.twin == b.twin && a.origin == b.origin && a.face == b.face
})
}
fn raw_hull_faces_eq(a: &[ffi::b3HullFace; 6], b: &[ffi::b3HullFace; 6]) -> bool {
a.iter().zip(b).all(|(a, b)| a.edge == b.edge)
}
fn raw_planes_eq(a: &[ffi::b3Plane; 6], b: &[ffi::b3Plane; 6]) -> bool {
a.iter()
.zip(b)
.all(|(a, b)| raw_vec3_eq(a.normal, b.normal) && a.offset == b.offset)
}
fn triangle_area_squared(a: Vec3, b: Vec3, c: Vec3) -> f32 {
let ab = Vec3::new(b.x - a.x, b.y - a.y, b.z - a.z);
let ac = Vec3::new(c.x - a.x, c.y - a.y, c.z - a.z);
let cross = Vec3::new(
ab.y * ac.z - ab.z * ac.y,
ab.z * ac.x - ab.x * ac.z,
ab.x * ac.y - ab.y * ac.x,
);
cross.x * cross.x + cross.y * cross.y + cross.z * cross.z
}
fn min_max_finite(values: &[f32]) -> Option<(f32, f32)> {
let mut iter = values.iter().copied();
let first = iter.next()?;
if !first.is_finite() {
return None;
}
let mut min = first;
let mut max = first;
for value in iter {
if !value.is_finite() {
return None;
}
min = min.min(value);
max = max.max(value);
}
Some((min, max))
}
pub(crate) fn validate_mesh_scale(scale: Vec3) -> Result<Vec3> {
validation::vec3("shape.scale", scale)?;
if scale.x.abs() <= f32::EPSILON
|| scale.y.abs() <= f32::EPSILON
|| scale.z.abs() <= f32::EPSILON
{
Err(validation::invalid(
"shape.scale",
InvalidValueReason::OutOfRange,
))
} else {
Ok(scale)
}
}
fn validate_box_half_widths(half_widths: Vec3) -> Result<Vec3> {
validate_positive_vec3("box_hull.half_widths", half_widths)
}
fn validate_positive_vec3(context: &'static str, value: Vec3) -> Result<Vec3> {
validation::vec3(context, value)?;
if value.x <= 0.0 || value.y <= 0.0 || value.z <= 0.0 {
Err(validation::invalid(context, InvalidValueReason::OutOfRange))
} else {
Ok(value)
}
}
fn validate_height_field_dimensions(row_count: i32, column_count: i32) -> Result<usize> {
if row_count < 2 {
return Err(validation::invalid(
"height_field.row_count",
InvalidValueReason::OutOfRange,
));
}
if column_count < 2 {
return Err(validation::invalid(
"height_field.column_count",
InvalidValueReason::OutOfRange,
));
}
let sample_count = (row_count as usize)
.checked_mul(column_count as usize)
.ok_or_else(|| {
validation::invalid("height_field.sample_count", InvalidValueReason::OutOfRange)
})?;
if sample_count > i32::MAX as usize {
Err(validation::invalid(
"height_field.sample_count",
InvalidValueReason::OutOfRange,
))
} else {
Ok(sample_count)
}
}
#[derive(Debug)]
pub struct Hull {
raw: NonNull<ffi::b3HullData>,
_not_send_sync: PhantomData<Rc<()>>,
}
impl Hull {
pub fn from_points(points: impl AsRef<[Vec3]>, max_vertex_count: i32) -> Result<Self> {
let points = points.as_ref();
let point_count = validation::count_i32("hull.points", points.len())?;
if points.len() < 4 {
return Err(validation::invalid(
"hull.points",
InvalidValueReason::OutOfRange,
));
}
if max_vertex_count <= 0 {
return Err(validation::invalid(
"hull.max_vertex_count",
InvalidValueReason::OutOfRange,
));
}
for point in points {
validation::vec3("hull.points", *point)?;
}
let ptr =
unsafe { ffi::b3CreateHull(points.as_ptr().cast(), point_count, max_vertex_count) };
Self::from_ptr(ptr)
}
pub fn cylinder(height: f32, radius: f32, y_offset: f32, sides: i32) -> Result<Self> {
validation::positive("hull.cylinder.height", height)?;
validation::positive("hull.cylinder.radius", radius)?;
validation::finite("hull.cylinder.y_offset", y_offset)?;
if !(3..=32).contains(&sides) {
return Err(validation::invalid(
"hull.cylinder.sides",
InvalidValueReason::OutOfRange,
));
}
Self::from_ptr(unsafe { ffi::b3CreateCylinder(height, radius, y_offset, sides) })
}
pub fn cone(height: f32, radius1: f32, radius2: f32, slices: i32) -> Result<Self> {
validation::positive("hull.cone.height", height)?;
validation::positive("hull.cone.radius1", radius1)?;
validation::positive("hull.cone.radius2", radius2)?;
if !(4..=32).contains(&slices) {
return Err(validation::invalid(
"hull.cone.slices",
InvalidValueReason::OutOfRange,
));
}
Self::from_ptr(unsafe { ffi::b3CreateCone(height, radius1, radius2, slices) })
}
pub fn rock(radius: f32) -> Result<Self> {
validation::positive("hull.rock.radius", radius)?;
Self::from_ptr(unsafe { ffi::b3CreateRock(radius) })
}
pub fn try_clone(&self) -> Result<Self> {
Self::from_ptr(unsafe { ffi::b3CloneHull(self.as_ptr()) })
}
pub fn clone_transformed(&self, transform: Transform, scale: impl Into<Vec3>) -> Result<Self> {
let transform = transform.validate()?;
let scale = validate_mesh_scale(scale.into())?;
Self::from_ptr(unsafe {
ffi::b3CloneAndTransformHull(self.as_ptr(), transform.into_raw(), scale.into_raw())
})
}
#[inline]
pub fn as_hull_data(&self) -> &ffi::b3HullData {
unsafe { self.raw.as_ref() }
}
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::b3HullData {
self.raw.as_ptr()
}
fn from_ptr(ptr: *mut ffi::b3HullData) -> Result<Self> {
NonNull::new(ptr)
.map(|raw| Self {
raw,
_not_send_sync: PhantomData,
})
.ok_or(Error::NativeFailure)
}
}
impl Drop for Hull {
fn drop(&mut self) {
unsafe { ffi::b3DestroyHull(self.raw.as_ptr()) };
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct MeshDataOptions {
pub weld_tolerance: f32,
pub weld_vertices: bool,
pub use_median_split: bool,
pub identify_edges: bool,
}
impl MeshDataOptions {
#[inline]
pub const fn new() -> Self {
Self {
weld_tolerance: 0.0,
weld_vertices: false,
use_median_split: false,
identify_edges: true,
}
}
#[inline]
pub const fn weld_tolerance(mut self, weld_tolerance: f32) -> Self {
self.weld_tolerance = weld_tolerance;
self
}
#[inline]
pub const fn weld_vertices(mut self, weld_vertices: bool) -> Self {
self.weld_vertices = weld_vertices;
self
}
#[inline]
pub const fn use_median_split(mut self, use_median_split: bool) -> Self {
self.use_median_split = use_median_split;
self
}
#[inline]
pub const fn identify_edges(mut self, identify_edges: bool) -> Self {
self.identify_edges = identify_edges;
self
}
fn validate(self) -> Result<()> {
validation::nonnegative("mesh.weld_tolerance", self.weld_tolerance)
}
}
impl Default for MeshDataOptions {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct MeshDataBuilder {
vertices: Vec<Vec3>,
indices: Vec<i32>,
material_indices: Option<Vec<u8>>,
options: MeshDataOptions,
}
impl MeshDataBuilder {
#[inline]
pub fn new(vertices: impl Into<Vec<Vec3>>, indices: impl Into<Vec<i32>>) -> Self {
Self {
vertices: vertices.into(),
indices: indices.into(),
material_indices: None,
options: MeshDataOptions::default(),
}
}
#[inline]
pub fn material_indices(mut self, material_indices: impl Into<Vec<u8>>) -> Self {
self.material_indices = Some(material_indices.into());
self
}
#[inline]
pub fn weld_tolerance(mut self, weld_tolerance: f32) -> Self {
self.options.weld_tolerance = weld_tolerance;
self
}
#[inline]
pub fn weld_vertices(mut self, weld_vertices: bool) -> Self {
self.options.weld_vertices = weld_vertices;
self
}
#[inline]
pub fn use_median_split(mut self, use_median_split: bool) -> Self {
self.options.use_median_split = use_median_split;
self
}
#[inline]
pub fn identify_edges(mut self, identify_edges: bool) -> Self {
self.options.identify_edges = identify_edges;
self
}
#[inline]
pub fn build(self) -> Result<MeshData> {
MeshData::from_triangles(
&self.vertices,
&self.indices,
self.material_indices.as_deref(),
self.options,
)
}
}
#[derive(Debug)]
pub struct MeshData {
raw: NonNull<ffi::b3MeshData>,
_not_send_sync: PhantomData<Rc<()>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct MeshTriangleHit {
pub a: Vec3,
pub b: Vec3,
pub c: Vec3,
pub triangle_index: i32,
}
impl MeshData {
#[inline]
pub fn builder(
vertices: impl Into<Vec<Vec3>>,
indices: impl Into<Vec<i32>>,
) -> MeshDataBuilder {
MeshDataBuilder::new(vertices, indices)
}
pub fn from_triangles(
vertices: impl AsRef<[Vec3]>,
indices: impl AsRef<[i32]>,
material_indices: Option<&[u8]>,
options: MeshDataOptions,
) -> Result<Self> {
options.validate()?;
let vertices = vertices.as_ref();
let indices = indices.as_ref();
if vertices.len() < 3 {
return Err(validation::invalid(
"mesh.vertices",
InvalidValueReason::OutOfRange,
));
}
let vertex_count = validation::count_i32("mesh.vertices", vertices.len())?;
for vertex in vertices {
validation::vec3("mesh.vertices", *vertex)?;
}
if indices.is_empty() {
return Err(validation::invalid(
"mesh.indices",
InvalidValueReason::OutOfRange,
));
}
if indices.len() % 3 != 0 {
return Err(validation::invalid(
"mesh.indices",
InvalidValueReason::Malformed,
));
}
let triangle_count = indices.len() / 3;
let triangle_count_i32 = validation::count_i32("mesh.triangles", triangle_count)?;
if let Some(material_indices) = material_indices
&& material_indices.len() != triangle_count
{
return Err(validation::invalid(
"mesh.material_indices",
InvalidValueReason::InvalidCombination,
));
}
for triangle in indices.chunks_exact(3) {
let [a, b, c]: [i32; 3] = triangle.try_into().expect("chunk size is fixed");
if a < 0
|| b < 0
|| c < 0
|| a as usize >= vertices.len()
|| b as usize >= vertices.len()
|| c as usize >= vertices.len()
{
return Err(validation::invalid(
"mesh.indices",
InvalidValueReason::OutOfRange,
));
}
if triangle_area_squared(
vertices[a as usize],
vertices[b as usize],
vertices[c as usize],
) <= f32::MIN_POSITIVE
{
return Err(validation::invalid(
"mesh.triangles",
InvalidValueReason::Malformed,
));
}
}
let mut vertices: Vec<ffi::b3Vec3> =
vertices.iter().map(|vertex| vertex.into_raw()).collect();
let mut indices = indices.to_vec();
let mut materials = material_indices.map(<[u8]>::to_vec);
let def = ffi::b3MeshDef {
vertices: vertices.as_mut_ptr(),
indices: indices.as_mut_ptr(),
materialIndices: materials
.as_mut()
.map_or(std::ptr::null_mut(), |materials| materials.as_mut_ptr()),
weldTolerance: options.weld_tolerance,
vertexCount: vertex_count,
triangleCount: triangle_count_i32,
weldVertices: options.weld_vertices,
useMedianSplit: options.use_median_split,
identifyEdges: options.identify_edges,
};
Self::from_ptr(unsafe { ffi::b3CreateMesh(&def, std::ptr::null_mut(), 0) })
}
pub fn box_mesh(
center: impl Into<Vec3>,
extent: impl Into<Vec3>,
identify_edges: bool,
) -> Result<Self> {
let center = center.into();
let extent = extent.into();
validation::vec3("mesh.box.center", center)?;
let extent = validate_positive_vec3("mesh.box.extent", extent)?;
Self::from_ptr(unsafe {
ffi::b3CreateBoxMesh(center.into_raw(), extent.into_raw(), identify_edges)
})
}
pub fn grid_mesh(
x_count: i32,
z_count: i32,
cell_width: f32,
material_count: i32,
identify_edges: bool,
) -> Result<Self> {
if x_count < 2 {
return Err(validation::invalid(
"mesh.grid.x_count",
InvalidValueReason::OutOfRange,
));
}
if z_count < 2 {
return Err(validation::invalid(
"mesh.grid.z_count",
InvalidValueReason::OutOfRange,
));
}
validation::positive("mesh.grid.cell_width", cell_width)?;
if material_count <= 0 {
return Err(validation::invalid(
"mesh.grid.material_count",
InvalidValueReason::OutOfRange,
));
}
Self::from_ptr(unsafe {
ffi::b3CreateGridMesh(x_count, z_count, cell_width, material_count, identify_edges)
})
}
pub fn wave_mesh(
x_count: i32,
z_count: i32,
cell_width: f32,
amplitude: f32,
row_frequency: f32,
column_frequency: f32,
) -> Result<Self> {
if x_count < 2 {
return Err(validation::invalid(
"mesh.wave.x_count",
InvalidValueReason::OutOfRange,
));
}
if z_count < 2 {
return Err(validation::invalid(
"mesh.wave.z_count",
InvalidValueReason::OutOfRange,
));
}
validation::positive("mesh.wave.cell_width", cell_width)?;
validation::finite("mesh.wave.amplitude", amplitude)?;
validation::finite("mesh.wave.row_frequency", row_frequency)?;
validation::finite("mesh.wave.column_frequency", column_frequency)?;
Self::from_ptr(unsafe {
ffi::b3CreateWaveMesh(
x_count,
z_count,
cell_width,
amplitude,
row_frequency,
column_frequency,
)
})
}
pub fn torus_mesh(
radial_resolution: i32,
tubular_resolution: i32,
radius: f32,
thickness: f32,
) -> Result<Self> {
if radial_resolution < 3 {
return Err(validation::invalid(
"mesh.torus.radial_resolution",
InvalidValueReason::OutOfRange,
));
}
if tubular_resolution < 3 {
return Err(validation::invalid(
"mesh.torus.tubular_resolution",
InvalidValueReason::OutOfRange,
));
}
validation::positive("mesh.torus.radius", radius)?;
validation::positive("mesh.torus.thickness", thickness)?;
Self::from_ptr(unsafe {
ffi::b3CreateTorusMesh(radial_resolution, tubular_resolution, radius, thickness)
})
}
pub fn hollow_box_mesh(center: impl Into<Vec3>, extent: impl Into<Vec3>) -> Result<Self> {
let center = center.into();
let extent = extent.into();
validation::vec3("mesh.hollow_box.center", center)?;
let extent = validate_positive_vec3("mesh.hollow_box.extent", extent)?;
Self::from_ptr(unsafe { ffi::b3CreateHollowBoxMesh(center.into_raw(), extent.into_raw()) })
}
pub fn platform_mesh(
center: impl Into<Vec3>,
height: f32,
top_width: f32,
bottom_width: f32,
) -> Result<Self> {
let center = center.into();
validation::vec3("mesh.platform.center", center)?;
validation::positive("mesh.platform.height", height)?;
validation::positive("mesh.platform.top_width", top_width)?;
validation::positive("mesh.platform.bottom_width", bottom_width)?;
Self::from_ptr(unsafe {
ffi::b3CreatePlatformMesh(center.into_raw(), height, top_width, bottom_width)
})
}
#[inline]
pub fn byte_count(&self) -> i32 {
unsafe { self.raw.as_ref().byteCount }
}
#[inline]
pub fn tree_height(&self) -> i32 {
unsafe { ffi::b3GetHeight(self.raw.as_ptr()) }
}
#[inline]
pub fn vertex_count(&self) -> i32 {
unsafe { self.raw.as_ref().vertexCount }
}
#[inline]
pub fn triangle_count(&self) -> i32 {
unsafe { self.raw.as_ref().triangleCount }
}
#[inline]
pub fn material_count(&self) -> i32 {
unsafe { self.raw.as_ref().materialCount }
}
pub fn query_triangles(
&self,
bounds: Aabb,
scale: impl Into<Vec3>,
) -> Result<Vec<MeshTriangleHit>> {
let mut out = Vec::new();
self.query_triangles_into(bounds, scale, &mut out)?;
Ok(out)
}
pub fn query_triangles_into(
&self,
bounds: Aabb,
scale: impl Into<Vec3>,
out: &mut Vec<MeshTriangleHit>,
) -> Result<()> {
out.clear();
self.visit_triangles(bounds, scale, |hit| {
out.push(hit);
true
})
}
pub fn visit_triangles<F>(&self, bounds: Aabb, scale: impl Into<Vec3>, visitor: F) -> Result<()>
where
F: FnMut(MeshTriangleHit) -> bool,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (bounds, scale, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let bounds = bounds.validate()?;
let scale = validate_mesh_scale(scale.into())?;
let raw_mesh = ffi::b3Mesh {
data: self.as_ptr(),
scale: scale.into_raw(),
};
let mut ctx = MeshTriangleQueryContext {
visitor,
state: LocalCallbackState::new(),
};
let _guard = box3d_lock::lock();
unsafe {
ffi::b3QueryMesh(
&raw_mesh,
bounds.into_raw(),
Some(mesh_triangle_query_trampoline::<F>),
(&mut ctx as *mut MeshTriangleQueryContext<_>).cast(),
);
}
ctx.state.drain()
}
}
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::b3MeshData {
self.raw.as_ptr()
}
fn from_ptr(ptr: *mut ffi::b3MeshData) -> Result<Self> {
NonNull::new(ptr)
.map(|raw| Self {
raw,
_not_send_sync: PhantomData,
})
.ok_or(Error::NativeFailure)
}
}
impl Drop for MeshData {
fn drop(&mut self) {
unsafe { ffi::b3DestroyMesh(self.raw.as_ptr()) };
}
}
#[derive(Clone, Debug)]
pub struct HeightFieldBuilder {
row_count: i32,
column_count: i32,
heights: Vec<f32>,
material_indices: Option<Vec<u8>>,
clockwise_winding: bool,
}
impl HeightFieldBuilder {
#[inline]
pub fn new(row_count: i32, column_count: i32, heights: impl Into<Vec<f32>>) -> Self {
Self {
row_count,
column_count,
heights: heights.into(),
material_indices: None,
clockwise_winding: false,
}
}
#[inline]
pub fn material_indices(mut self, material_indices: impl Into<Vec<u8>>) -> Self {
self.material_indices = Some(material_indices.into());
self
}
#[inline]
pub fn clockwise_winding(mut self, clockwise_winding: bool) -> Self {
self.clockwise_winding = clockwise_winding;
self
}
#[inline]
pub fn build(self, scale: impl Into<Vec3>) -> Result<HeightField> {
HeightField::from_samples(
self.row_count,
self.column_count,
&self.heights,
scale,
self.material_indices.as_deref(),
self.clockwise_winding,
)
}
}
#[derive(Debug)]
pub struct HeightField {
raw: NonNull<ffi::b3HeightFieldData>,
_not_send_sync: PhantomData<Rc<()>>,
}
impl HeightField {
#[inline]
pub fn builder(
row_count: i32,
column_count: i32,
heights: impl Into<Vec<f32>>,
) -> HeightFieldBuilder {
HeightFieldBuilder::new(row_count, column_count, heights)
}
pub fn from_samples(
row_count: i32,
column_count: i32,
heights: impl AsRef<[f32]>,
scale: impl Into<Vec3>,
material_indices: Option<&[u8]>,
clockwise_winding: bool,
) -> Result<Self> {
let scale = scale.into();
let heights = heights.as_ref();
let sample_count = validate_height_field_dimensions(row_count, column_count)?;
if heights.len() != sample_count {
return Err(validation::invalid(
"height_field.heights",
InvalidValueReason::InvalidCombination,
));
}
validate_positive_vec3("height_field.scale", scale)?;
for height in heights {
validation::finite("height_field.heights", *height)?;
}
let cell_count = (row_count as usize - 1) * (column_count as usize - 1);
if let Some(material_indices) = material_indices
&& material_indices.len() != cell_count
{
return Err(validation::invalid(
"height_field.material_indices",
InvalidValueReason::InvalidCombination,
));
}
let mut heights = heights.to_vec();
let mut materials = material_indices.map(<[u8]>::to_vec);
let (global_minimum_height, global_maximum_height) =
min_max_finite(&heights).expect("validated height samples are non-empty and finite");
let def = ffi::b3HeightFieldDef {
heights: heights.as_mut_ptr(),
materialIndices: materials
.as_mut()
.map_or(std::ptr::null_mut(), |materials| materials.as_mut_ptr()),
scale: scale.into_raw(),
countX: column_count,
countZ: row_count,
globalMinimumHeight: global_minimum_height,
globalMaximumHeight: global_maximum_height,
clockwiseWinding: clockwise_winding,
};
Self::from_ptr(unsafe { ffi::b3CreateHeightField(&def) })
}
pub fn grid(
row_count: i32,
column_count: i32,
scale: impl Into<Vec3>,
make_holes: bool,
) -> Result<Self> {
let scale = scale.into();
validate_height_field_dimensions(row_count, column_count)?;
let scale = validate_positive_vec3("height_field.scale", scale)?;
Self::from_ptr(unsafe {
ffi::b3CreateGrid(row_count, column_count, scale.into_raw(), make_holes)
})
}
pub fn wave(
row_count: i32,
column_count: i32,
scale: impl Into<Vec3>,
row_frequency: f32,
column_frequency: f32,
make_holes: bool,
) -> Result<Self> {
let scale = scale.into();
validate_height_field_dimensions(row_count, column_count)?;
let scale = validate_positive_vec3("height_field.scale", scale)?;
validation::finite("height_field.row_frequency", row_frequency)?;
validation::finite("height_field.column_frequency", column_frequency)?;
Self::from_ptr(unsafe {
ffi::b3CreateWave(
row_count,
column_count,
scale.into_raw(),
row_frequency,
column_frequency,
make_holes,
)
})
}
#[inline]
pub fn byte_count(&self) -> i32 {
unsafe { self.raw.as_ref().byteCount }
}
#[inline]
pub fn row_count(&self) -> i32 {
unsafe { self.raw.as_ref().rowCount }
}
#[inline]
pub fn column_count(&self) -> i32 {
unsafe { self.raw.as_ref().columnCount }
}
pub fn query_triangles(&self, bounds: Aabb) -> Result<Vec<MeshTriangleHit>> {
let mut out = Vec::new();
self.query_triangles_into(bounds, &mut out)?;
Ok(out)
}
pub fn query_triangles_into(&self, bounds: Aabb, out: &mut Vec<MeshTriangleHit>) -> Result<()> {
out.clear();
self.visit_triangles(bounds, |hit| out.push(hit))
}
pub fn visit_triangles<F>(&self, bounds: Aabb, visitor: F) -> Result<()>
where
F: FnMut(MeshTriangleHit),
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (bounds, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let Some(bounds) = clamp_height_field_query_bounds(bounds.validate()?, unsafe {
self.raw.as_ref().aabb
})?
else {
return Ok(());
};
let mut ctx = HeightFieldTriangleQueryContext {
visitor,
state: LocalCallbackState::new(),
};
let _guard = box3d_lock::lock();
unsafe {
ffi::b3QueryHeightField(
self.as_ptr(),
bounds.into_raw(),
Some(height_field_triangle_query_trampoline::<F>),
(&mut ctx as *mut HeightFieldTriangleQueryContext<_>).cast(),
);
}
ctx.state.drain()
}
}
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::b3HeightFieldData {
self.raw.as_ptr()
}
fn from_ptr(ptr: *mut ffi::b3HeightFieldData) -> Result<Self> {
NonNull::new(ptr)
.map(|raw| Self {
raw,
_not_send_sync: PhantomData,
})
.ok_or(Error::NativeFailure)
}
}
impl Drop for HeightField {
fn drop(&mut self) {
unsafe { ffi::b3DestroyHeightField(self.raw.as_ptr()) };
}
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
struct MeshTriangleQueryContext<F> {
visitor: F,
state: LocalCallbackState,
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
unsafe extern "C" fn mesh_triangle_query_trampoline<F>(
a: ffi::b3Vec3,
b: ffi::b3Vec3,
c: ffi::b3Vec3,
triangle_index: i32,
context: *mut c_void,
) -> bool
where
F: FnMut(MeshTriangleHit) -> bool,
{
let ctx = unsafe { &mut *context.cast::<MeshTriangleQueryContext<F>>() };
let hit = MeshTriangleHit {
a: Vec3::from_raw(a),
b: Vec3::from_raw(b),
c: Vec3::from_raw(c),
triangle_index,
};
ctx.state.invoke(false, || (ctx.visitor)(hit))
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
struct HeightFieldTriangleQueryContext<F> {
visitor: F,
state: LocalCallbackState,
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
unsafe extern "C" fn height_field_triangle_query_trampoline<F>(
a: ffi::b3Vec3,
b: ffi::b3Vec3,
c: ffi::b3Vec3,
triangle_index: i32,
context: *mut c_void,
) -> bool
where
F: FnMut(MeshTriangleHit),
{
let ctx = unsafe { &mut *context.cast::<HeightFieldTriangleQueryContext<F>>() };
let hit = MeshTriangleHit {
a: Vec3::from_raw(a),
b: Vec3::from_raw(b),
c: Vec3::from_raw(c),
triangle_index,
};
ctx.state.invoke(false, || {
(ctx.visitor)(hit);
true
})
}
#[derive(Copy, Clone, Debug)]
pub struct ShapeHull<'a> {
raw: &'a ffi::b3HullData,
}
impl<'a> ShapeHull<'a> {
#[inline]
pub(crate) const fn from_raw(raw: &'a ffi::b3HullData) -> Self {
Self { raw }
}
#[inline]
pub const fn byte_count(&self) -> i32 {
self.raw.byteCount
}
#[inline]
pub const fn hash(&self) -> u32 {
self.raw.hash
}
#[inline]
pub const fn aabb(&self) -> Aabb {
Aabb::from_raw(self.raw.aabb)
}
#[inline]
pub const fn surface_area(&self) -> f32 {
self.raw.surfaceArea
}
#[inline]
pub const fn volume(&self) -> f32 {
self.raw.volume
}
#[inline]
pub const fn inner_radius(&self) -> f32 {
self.raw.innerRadius
}
#[inline]
pub const fn center(&self) -> Vec3 {
Vec3::from_raw(self.raw.center)
}
#[inline]
pub const fn vertex_count(&self) -> i32 {
self.raw.vertexCount
}
#[inline]
pub const fn edge_count(&self) -> i32 {
self.raw.edgeCount
}
#[inline]
pub const fn face_count(&self) -> i32 {
self.raw.faceCount
}
}
#[derive(Copy, Clone, Debug)]
pub struct ShapeMesh<'a> {
data: &'a ffi::b3MeshData,
scale: Vec3,
}
impl<'a> ShapeMesh<'a> {
#[inline]
pub(crate) fn from_raw(raw: ffi::b3Mesh) -> Option<Self> {
unsafe { raw.data.as_ref() }.map(|data| Self {
data,
scale: Vec3::from_raw(raw.scale),
})
}
#[inline]
pub const fn scale(&self) -> Vec3 {
self.scale
}
#[inline]
pub const fn byte_count(&self) -> i32 {
self.data.byteCount
}
#[inline]
pub const fn hash(&self) -> u32 {
self.data.hash
}
#[inline]
pub const fn bounds(&self) -> Aabb {
Aabb::from_raw(self.data.bounds)
}
#[inline]
pub const fn surface_area(&self) -> f32 {
self.data.surfaceArea
}
#[inline]
pub const fn tree_height(&self) -> i32 {
self.data.treeHeight
}
#[inline]
pub const fn degenerate_count(&self) -> i32 {
self.data.degenerateCount
}
#[inline]
pub const fn vertex_count(&self) -> i32 {
self.data.vertexCount
}
#[inline]
pub const fn triangle_count(&self) -> i32 {
self.data.triangleCount
}
#[inline]
pub const fn material_count(&self) -> i32 {
self.data.materialCount
}
}
#[derive(Copy, Clone, Debug)]
pub struct ShapeHeightField<'a> {
raw: &'a ffi::b3HeightFieldData,
}
impl<'a> ShapeHeightField<'a> {
#[inline]
pub(crate) const fn from_raw(raw: &'a ffi::b3HeightFieldData) -> Self {
Self { raw }
}
#[inline]
pub const fn byte_count(&self) -> i32 {
self.raw.byteCount
}
#[inline]
pub const fn hash(&self) -> u32 {
self.raw.hash
}
#[inline]
pub const fn aabb(&self) -> Aabb {
Aabb::from_raw(self.raw.aabb)
}
#[inline]
pub const fn min_height(&self) -> f32 {
self.raw.minHeight
}
#[inline]
pub const fn max_height(&self) -> f32 {
self.raw.maxHeight
}
#[inline]
pub const fn scale(&self) -> Vec3 {
Vec3::from_raw(self.raw.scale)
}
#[inline]
pub const fn column_count(&self) -> i32 {
self.raw.columnCount
}
#[inline]
pub const fn row_count(&self) -> i32 {
self.raw.rowCount
}
#[inline]
pub const fn clockwise(&self) -> bool {
self.raw.clockwise
}
}
#[derive(Debug)]
pub struct Compound {
raw: NonNull<ffi::b3CompoundData>,
_not_send_sync: PhantomData<Rc<()>>,
}
#[derive(Debug)]
pub struct CompoundBytes {
raw: NonNull<u8>,
byte_count: i32,
_not_send_sync: PhantomData<Rc<()>>,
}
impl Compound {
#[inline]
pub fn builder<'a>() -> CompoundBuilder<'a> {
CompoundBuilder::new()
}
pub fn single_sphere(sphere: Sphere, material: SurfaceMaterial) -> Result<Self> {
Self::builder().with_sphere(sphere, material)?.build()
}
#[inline]
pub fn byte_count(&self) -> i32 {
unsafe { self.raw.as_ref().byteCount }
}
#[inline]
pub fn child_count(&self) -> i32 {
unsafe {
let raw = self.raw.as_ref();
raw.capsuleCount + raw.hullCount + raw.meshCount + raw.sphereCount
}
}
#[inline]
pub fn capsule_count(&self) -> i32 {
unsafe { self.raw.as_ref().capsuleCount }
}
#[inline]
pub fn hull_count(&self) -> i32 {
unsafe { self.raw.as_ref().hullCount }
}
#[inline]
pub fn mesh_count(&self) -> i32 {
unsafe { self.raw.as_ref().meshCount }
}
#[inline]
pub fn sphere_count(&self) -> i32 {
unsafe { self.raw.as_ref().sphereCount }
}
#[inline]
pub fn material_count(&self) -> i32 {
unsafe { self.raw.as_ref().materialCount }
}
#[inline]
pub fn shared_hull_count(&self) -> i32 {
unsafe { self.raw.as_ref().sharedHullCount }
}
#[inline]
pub fn shared_mesh_count(&self) -> i32 {
unsafe { self.raw.as_ref().sharedMeshCount }
}
pub fn material(&self, index: i32) -> Result<SurfaceMaterial> {
if index < 0 || index >= self.material_count() {
return Err(validation::invalid(
"compound.material_index",
InvalidValueReason::OutOfRange,
));
}
let materials = unsafe { ffi::b3GetCompoundMaterials(self.raw.as_ptr()) };
unsafe { materials.add(index as usize).as_ref() }
.copied()
.map(SurfaceMaterial::from_raw)
.ok_or(Error::NativeFailure)
}
pub fn child(&self, index: i32) -> Result<CompoundChild<'_>> {
if index < 0 || index >= self.child_count() {
return Err(validation::invalid(
"compound.child_index",
InvalidValueReason::OutOfRange,
));
}
CompoundChild::from_raw(unsafe { ffi::b3GetCompoundChild(self.raw.as_ptr(), index) })
}
pub fn capsule_child(&self, index: i32) -> Result<CompoundCapsule> {
if index < 0 || index >= self.capsule_count() {
return Err(validation::invalid(
"compound.capsule_index",
InvalidValueReason::OutOfRange,
));
}
let raw = unsafe { ffi::b3GetCompoundCapsule(self.raw.as_ptr(), index) };
Ok(CompoundCapsule::from_raw(raw))
}
pub fn hull_child(&self, index: i32) -> Result<CompoundHull<'_>> {
if index < 0 || index >= self.hull_count() {
return Err(validation::invalid(
"compound.hull_index",
InvalidValueReason::OutOfRange,
));
}
let raw = unsafe { ffi::b3GetCompoundHull(self.raw.as_ptr(), index) };
CompoundHull::from_raw(raw)
}
pub fn mesh_child(&self, index: i32) -> Result<CompoundMesh<'_>> {
if index < 0 || index >= self.mesh_count() {
return Err(validation::invalid(
"compound.mesh_index",
InvalidValueReason::OutOfRange,
));
}
let raw = unsafe { ffi::b3GetCompoundMesh(self.raw.as_ptr(), index) };
CompoundMesh::from_raw(raw)
}
pub fn sphere_child(&self, index: i32) -> Result<CompoundSphere> {
if index < 0 || index >= self.sphere_count() {
return Err(validation::invalid(
"compound.sphere_index",
InvalidValueReason::OutOfRange,
));
}
let raw = unsafe { ffi::b3GetCompoundSphere(self.raw.as_ptr(), index) };
Ok(CompoundSphere::from_raw(raw))
}
pub fn query_aabb(&self, aabb: Aabb) -> Result<Vec<CompoundQueryHit<'_>>> {
let mut out = Vec::new();
self.query_aabb_into(aabb, &mut out)?;
Ok(out)
}
pub fn query_aabb_into<'a>(
&'a self,
aabb: Aabb,
out: &mut Vec<CompoundQueryHit<'a>>,
) -> Result<()> {
out.clear();
self.visit_query_aabb(aabb, |hit| {
out.push(hit);
true
})
}
pub fn visit_query_aabb<'a, F>(&'a self, aabb: Aabb, visitor: F) -> Result<()>
where
F: FnMut(CompoundQueryHit<'a>) -> bool,
{
callback_state::check_not_in_callback()?;
#[cfg(all(target_arch = "wasm32", boxddd_wasm_provider))]
{
let _ = (aabb, visitor);
Err(Error::UnsupportedOnWasm)
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
{
let aabb = aabb.validate()?;
let mut ctx = CompoundQueryContext {
visitor,
state: LocalCallbackState::new(),
_lifetime: PhantomData,
};
let _guard = box3d_lock::lock();
unsafe {
ffi::b3QueryCompound(
self.raw.as_ptr(),
aabb.into_raw(),
Some(compound_query_trampoline::<F>),
(&mut ctx as *mut CompoundQueryContext<'a, F>).cast(),
);
}
ctx.state.drain()
}
}
pub fn into_bytes(self) -> Result<CompoundBytes> {
let byte_count = self.byte_count();
if byte_count < 0 {
return Err(Error::NativeFailure);
}
let raw = unsafe { ffi::b3ConvertCompoundToBytes(self.raw.as_ptr()) };
let raw = NonNull::new(raw).ok_or(Error::NativeFailure)?;
forget(self);
Ok(CompoundBytes {
raw,
byte_count,
_not_send_sync: PhantomData,
})
}
#[inline]
pub(crate) fn as_ptr(&self) -> *const ffi::b3CompoundData {
self.raw.as_ptr()
}
fn from_ptr(ptr: *mut ffi::b3CompoundData) -> Result<Self> {
NonNull::new(ptr)
.map(|raw| Self {
raw,
_not_send_sync: PhantomData,
})
.ok_or(Error::NativeFailure)
}
}
impl Drop for Compound {
fn drop(&mut self) {
unsafe { ffi::b3DestroyCompound(self.raw.as_ptr()) };
}
}
impl CompoundBytes {
#[inline]
pub const fn byte_count(&self) -> i32 {
self.byte_count
}
#[inline]
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.raw.as_ptr(), self.byte_count as usize) }
}
pub fn into_compound(self) -> Result<Compound> {
let raw = unsafe { ffi::b3ConvertBytesToCompound(self.raw.as_ptr(), self.byte_count) };
let compound = Compound::from_ptr(raw)?;
forget(self);
Ok(compound)
}
}
impl Drop for CompoundBytes {
fn drop(&mut self) {
unsafe { ffi::b3DestroyCompound(self.raw.as_ptr().cast()) };
}
}
#[derive(Debug)]
pub struct CompoundBuilder<'a> {
capsules: Vec<ffi::b3CompoundCapsuleDef>,
hulls: Vec<ffi::b3CompoundHullDef>,
meshes: Vec<ffi::b3CompoundMeshDef>,
mesh_materials: Vec<Box<[ffi::b3SurfaceMaterial]>>,
spheres: Vec<ffi::b3CompoundSphereDef>,
error: Option<Error>,
_lifetime: PhantomData<&'a ()>,
}
impl<'a> CompoundBuilder<'a> {
#[inline]
pub fn new() -> Self {
Self {
capsules: Vec::new(),
hulls: Vec::new(),
meshes: Vec::new(),
mesh_materials: Vec::new(),
spheres: Vec::new(),
error: None,
_lifetime: PhantomData,
}
}
pub fn sphere(mut self, sphere: Sphere, material: SurfaceMaterial) -> Self {
if let Err(error) = self.add_sphere(sphere, material) {
self.error = Some(error);
}
self
}
pub fn with_sphere(mut self, sphere: Sphere, material: SurfaceMaterial) -> Result<Self> {
self.add_sphere(sphere, material)?;
Ok(self)
}
pub fn add_sphere(&mut self, sphere: Sphere, material: SurfaceMaterial) -> Result<&mut Self> {
sphere.validate()?;
material.validate()?;
self.validate_additional_child()?;
self.spheres.push(ffi::b3CompoundSphereDef {
sphere: *sphere.raw(),
material: material.into_raw(),
});
Ok(self)
}
pub fn capsule(mut self, capsule: Capsule, material: SurfaceMaterial) -> Self {
if let Err(error) = self.add_capsule(capsule, material) {
self.error = Some(error);
}
self
}
pub fn with_capsule(mut self, capsule: Capsule, material: SurfaceMaterial) -> Result<Self> {
self.add_capsule(capsule, material)?;
Ok(self)
}
pub fn add_capsule(
&mut self,
capsule: Capsule,
material: SurfaceMaterial,
) -> Result<&mut Self> {
capsule.validate()?;
material.validate()?;
self.validate_additional_child()?;
self.capsules.push(ffi::b3CompoundCapsuleDef {
capsule: *capsule.raw(),
material: material.into_raw(),
});
Ok(self)
}
pub fn hull(
mut self,
hull: &'a Hull,
transform: impl Into<Transform>,
material: SurfaceMaterial,
) -> Self {
if let Err(error) = self.add_hull(hull, transform, material) {
self.error = Some(error);
}
self
}
pub fn with_hull(
mut self,
hull: &'a Hull,
transform: impl Into<Transform>,
material: SurfaceMaterial,
) -> Result<Self> {
self.add_hull(hull, transform, material)?;
Ok(self)
}
pub fn add_hull(
&mut self,
hull: &'a Hull,
transform: impl Into<Transform>,
material: SurfaceMaterial,
) -> Result<&mut Self> {
self.add_hull_ptr(hull.as_ptr(), transform.into(), material)
}
pub fn box_hull(
mut self,
hull: &'a BoxHull,
transform: impl Into<Transform>,
material: SurfaceMaterial,
) -> Self {
if let Err(error) = self.add_box_hull(hull, transform, material) {
self.error = Some(error);
}
self
}
pub fn with_box_hull(
mut self,
hull: &'a BoxHull,
transform: impl Into<Transform>,
material: SurfaceMaterial,
) -> Result<Self> {
self.add_box_hull(hull, transform, material)?;
Ok(self)
}
pub fn add_box_hull(
&mut self,
hull: &'a BoxHull,
transform: impl Into<Transform>,
material: SurfaceMaterial,
) -> Result<&mut Self> {
self.add_hull_ptr(hull.hull_data(), transform.into(), material)
}
fn add_hull_ptr(
&mut self,
hull: *const ffi::b3HullData,
transform: Transform,
material: SurfaceMaterial,
) -> Result<&mut Self> {
transform.validate()?;
material.validate()?;
if hull.is_null() {
return Err(Error::NativeFailure);
}
self.validate_additional_child()?;
self.hulls.push(ffi::b3CompoundHullDef {
hull,
transform: transform.into_raw(),
material: material.into_raw(),
});
Ok(self)
}
pub fn mesh(
mut self,
mesh: &'a MeshData,
transform: impl Into<Transform>,
scale: impl Into<Vec3>,
materials: impl AsRef<[SurfaceMaterial]>,
) -> Self {
if let Err(error) = self.add_mesh(mesh, transform, scale, materials) {
self.error = Some(error);
}
self
}
pub fn with_mesh(
mut self,
mesh: &'a MeshData,
transform: impl Into<Transform>,
scale: impl Into<Vec3>,
materials: impl AsRef<[SurfaceMaterial]>,
) -> Result<Self> {
self.add_mesh(mesh, transform, scale, materials)?;
Ok(self)
}
pub fn add_mesh(
&mut self,
mesh: &'a MeshData,
transform: impl Into<Transform>,
scale: impl Into<Vec3>,
materials: impl AsRef<[SurfaceMaterial]>,
) -> Result<&mut Self> {
let transform = transform.into();
let scale = validate_mesh_scale(scale.into())?;
transform.validate()?;
let materials = materials.as_ref();
if materials.is_empty() || materials.len() > MAX_COMPOUND_MESH_MATERIALS {
return Err(validation::invalid(
"compound.mesh.materials",
InvalidValueReason::OutOfRange,
));
}
if materials.len() != mesh.material_count() as usize {
return Err(validation::invalid(
"compound.mesh.materials",
InvalidValueReason::InvalidCombination,
));
}
let raw_materials: Vec<_> = materials
.iter()
.copied()
.map(|material| {
material.validate()?;
Ok(material.into_raw())
})
.collect::<Result<_>>()?;
self.validate_additional_child()?;
self.mesh_materials.push(raw_materials.into_boxed_slice());
let material_ptr = self
.mesh_materials
.last()
.expect("just pushed mesh materials")
.as_ptr();
self.meshes.push(ffi::b3CompoundMeshDef {
meshData: mesh.as_ptr(),
transform: transform.into_raw(),
scale: scale.into_raw(),
materials: material_ptr,
materialCount: materials.len() as i32,
});
Ok(self)
}
pub fn build(mut self) -> Result<Compound> {
if let Some(error) = self.error {
return Err(error);
}
self.validate_child_capacity()?;
if self.child_count() == 0 {
return Err(validation::invalid(
"compound.children",
InvalidValueReason::OutOfRange,
));
}
let def = ffi::b3CompoundDef {
capsules: self.capsules.as_mut_ptr(),
capsuleCount: self.capsules.len() as i32,
hulls: self.hulls.as_mut_ptr(),
hullCount: self.hulls.len() as i32,
meshes: self.meshes.as_mut_ptr(),
meshCount: self.meshes.len() as i32,
spheres: self.spheres.as_mut_ptr(),
sphereCount: self.spheres.len() as i32,
};
Compound::from_ptr(unsafe { ffi::b3CreateCompound(&def) })
}
fn child_count(&self) -> usize {
self.capsules.len() + self.hulls.len() + self.meshes.len() + self.spheres.len()
}
fn validate_child_capacity(&self) -> Result<()> {
if self.child_count() < ffi::B3_MAX_CHILD_SHAPES as usize {
Ok(())
} else {
Err(validation::invalid(
"compound.children",
InvalidValueReason::OutOfRange,
))
}
}
fn validate_additional_child(&self) -> Result<()> {
if self.child_count() + 1 < ffi::B3_MAX_CHILD_SHAPES as usize {
Ok(())
} else {
Err(validation::invalid(
"compound.children",
InvalidValueReason::OutOfRange,
))
}
}
}
impl<'a> Default for CompoundBuilder<'a> {
fn default() -> Self {
Self::new()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CompoundCapsule {
pub capsule: Capsule,
pub material_index: i32,
}
impl CompoundCapsule {
#[inline]
const fn from_raw(raw: ffi::b3CompoundCapsule) -> Self {
Self {
capsule: Capsule::from_raw(raw.capsule),
material_index: raw.materialIndex,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct CompoundHull<'a> {
pub hull: ShapeHull<'a>,
pub transform: Transform,
pub material_index: i32,
}
impl<'a> CompoundHull<'a> {
fn from_raw(raw: ffi::b3CompoundHull) -> Result<Self> {
unsafe { raw.hull.as_ref() }
.map(|hull| Self {
hull: ShapeHull::from_raw(hull),
transform: Transform::from_raw(raw.transform),
material_index: raw.materialIndex,
})
.ok_or(Error::NativeFailure)
}
}
#[derive(Copy, Clone, Debug)]
pub struct CompoundMesh<'a> {
pub mesh: ShapeMesh<'a>,
pub transform: Transform,
pub material_indices: [i32; MAX_COMPOUND_MESH_MATERIALS],
}
impl<'a> CompoundMesh<'a> {
fn from_raw(raw: ffi::b3CompoundMesh) -> Result<Self> {
ShapeMesh::from_raw(ffi::b3Mesh {
data: raw.meshData,
scale: raw.scale,
})
.map(|mesh| Self {
mesh,
transform: Transform::from_raw(raw.transform),
material_indices: raw.materialIndices,
})
.ok_or(Error::NativeFailure)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct CompoundSphere {
pub sphere: Sphere,
pub material_index: i32,
}
impl CompoundSphere {
#[inline]
const fn from_raw(raw: ffi::b3CompoundSphere) -> Self {
Self {
sphere: Sphere::from_raw(raw.sphere),
material_index: raw.materialIndex,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct CompoundChild<'a> {
pub shape: CompoundChildShape<'a>,
pub transform: Transform,
pub material_indices: [i32; MAX_COMPOUND_MESH_MATERIALS],
}
impl<'a> CompoundChild<'a> {
fn from_raw(raw: ffi::b3ChildShape) -> Result<Self> {
let shape = match ShapeType::from_raw(raw.type_) {
Some(ShapeType::Capsule) => CompoundChildShape::Capsule(Capsule::from_raw(unsafe {
raw.__bindgen_anon_1.capsule
})),
Some(ShapeType::Hull) => {
let hull = unsafe { raw.__bindgen_anon_1.hull.as_ref() }
.map(ShapeHull::from_raw)
.ok_or(Error::NativeFailure)?;
CompoundChildShape::Hull(hull)
}
Some(ShapeType::Mesh) => {
let mesh = ShapeMesh::from_raw(unsafe { raw.__bindgen_anon_1.mesh })
.ok_or(Error::NativeFailure)?;
CompoundChildShape::Mesh(mesh)
}
Some(ShapeType::Sphere) => {
CompoundChildShape::Sphere(Sphere::from_raw(unsafe { raw.__bindgen_anon_1.sphere }))
}
_ => return Err(Error::NativeFailure),
};
Ok(Self {
shape,
transform: Transform::from_raw(raw.transform),
material_indices: raw.materialIndices,
})
}
#[inline]
pub const fn shape_type(&self) -> ShapeType {
self.shape.shape_type()
}
#[inline]
pub const fn primary_material_index(&self) -> i32 {
self.material_indices[0]
}
}
#[derive(Copy, Clone, Debug)]
pub struct CompoundQueryHit<'a> {
pub child_index: i32,
pub child: CompoundChild<'a>,
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
struct CompoundQueryContext<'a, F> {
visitor: F,
state: LocalCallbackState,
_lifetime: PhantomData<&'a Compound>,
}
#[cfg(not(all(target_arch = "wasm32", boxddd_wasm_provider)))]
unsafe extern "C" fn compound_query_trampoline<'a, F>(
compound: *const ffi::b3CompoundData,
child_index: i32,
context: *mut c_void,
) -> bool
where
F: FnMut(CompoundQueryHit<'a>) -> bool,
{
let ctx = unsafe { &mut *context.cast::<CompoundQueryContext<'a, F>>() };
if ctx.state.has_failed() {
return false;
}
if compound.is_null() || child_index < 0 {
return ctx.state.fail(Error::NativeFailure, false);
}
let raw_child = unsafe { ffi::b3GetCompoundChild(compound, child_index) };
let child = match CompoundChild::from_raw(raw_child) {
Ok(child) => child,
Err(error) => {
return ctx.state.fail(error, false);
}
};
let hit = CompoundQueryHit { child_index, child };
ctx.state.invoke(false, || (ctx.visitor)(hit))
}
#[derive(Copy, Clone, Debug)]
pub enum CompoundChildShape<'a> {
Capsule(Capsule),
Hull(ShapeHull<'a>),
Mesh(ShapeMesh<'a>),
Sphere(Sphere),
}
impl<'a> CompoundChildShape<'a> {
#[inline]
pub const fn shape_type(&self) -> ShapeType {
match self {
Self::Capsule(_) => ShapeType::Capsule,
Self::Hull(_) => ShapeType::Hull,
Self::Mesh(_) => ShapeType::Mesh,
Self::Sphere(_) => ShapeType::Sphere,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ShapeType {
Capsule,
Compound,
HeightField,
Hull,
Mesh,
Sphere,
}
impl ShapeType {
pub const fn from_raw(raw: ffi::b3ShapeType) -> Option<Self> {
match raw {
ffi::b3ShapeType_b3_capsuleShape => Some(Self::Capsule),
ffi::b3ShapeType_b3_compoundShape => Some(Self::Compound),
ffi::b3ShapeType_b3_heightShape => Some(Self::HeightField),
ffi::b3ShapeType_b3_hullShape => Some(Self::Hull),
ffi::b3ShapeType_b3_meshShape => Some(Self::Mesh),
ffi::b3ShapeType_b3_sphereShape => Some(Self::Sphere),
_ => None,
}
}
}