use crate::{
collision::CastOutput,
core::math::Transform,
error::{ApiError, ApiResult},
query::Aabb,
types::{MassData, Vec2},
};
use boxdd_sys::ffi;
use core::fmt;
use smallvec::SmallVec;
mod capsule;
mod chain_segment;
mod circle;
mod polygon;
mod segment;
pub const MAX_POLYGON_VERTICES: usize = ffi::B2_MAX_POLYGON_VERTICES as usize;
const MAX_POLYGON_INPUT_POINTS: usize = MAX_POLYGON_VERTICES + 1;
const _: () = {
assert!(core::mem::size_of::<Vec2>() == core::mem::size_of::<ffi::b2Vec2>());
assert!(core::mem::align_of::<Vec2>() == core::mem::align_of::<ffi::b2Vec2>());
assert!(core::mem::size_of::<ChainSegment>() == core::mem::size_of::<ffi::b2ChainSegment>());
assert!(core::mem::align_of::<ChainSegment>() == core::mem::align_of::<ffi::b2ChainSegment>());
};
#[inline]
fn make_ray_input<VO: Into<Vec2>, VT: Into<Vec2>>(
origin: VO,
translation: VT,
) -> ffi::b2RayCastInput {
let origin = origin.into();
let translation = translation.into();
assert_valid_geometry_vec2("origin", origin);
assert_valid_geometry_vec2("translation", translation);
raw_ray_input(origin, translation)
}
#[inline]
fn try_make_ray_input<VO: Into<Vec2>, VT: Into<Vec2>>(
origin: VO,
translation: VT,
) -> ApiResult<ffi::b2RayCastInput> {
let origin = origin.into();
let translation = translation.into();
check_valid_geometry_vec2(origin)?;
check_valid_geometry_vec2(translation)?;
Ok(raw_ray_input(origin, translation))
}
#[inline]
fn raw_ray_input(origin: Vec2, translation: Vec2) -> ffi::b2RayCastInput {
ffi::b2RayCastInput {
origin: origin.into_raw(),
translation: translation.into_raw(),
maxFraction: 1.0,
}
}
#[inline]
fn collect_polygon_points<I, P>(
points: I,
) -> Option<SmallVec<[ffi::b2Vec2; MAX_POLYGON_INPUT_POINTS]>>
where
I: IntoIterator<Item = P>,
P: Into<Vec2>,
{
let mut pts: SmallVec<[ffi::b2Vec2; MAX_POLYGON_INPUT_POINTS]> =
SmallVec::with_capacity(MAX_POLYGON_INPUT_POINTS);
for point in points {
if pts.len() == MAX_POLYGON_INPUT_POINTS {
return None;
}
let point = point.into();
if !point.is_valid() {
return None;
}
pts.push(point.into_raw());
}
if pts.is_empty() || pts.len() > MAX_POLYGON_VERTICES {
return None;
}
Some(pts)
}
#[inline]
fn compute_hull_from_points<I, P>(points: I) -> Option<ffi::b2Hull>
where
I: IntoIterator<Item = P>,
P: Into<Vec2>,
{
let pts = collect_polygon_points(points)?;
let hull = unsafe { ffi::b2ComputeHull(pts.as_ptr(), pts.len() as i32) };
(hull.count > 0).then_some(hull)
}
#[inline]
fn geometry_scalar_is_non_negative_finite(value: f32) -> bool {
crate::is_valid_float(value) && value >= 0.0
}
#[inline]
fn geometry_vec2_is_valid(value: Vec2) -> bool {
value.is_valid()
}
#[inline]
fn geometry_density_is_valid(value: f32) -> bool {
geometry_scalar_is_non_negative_finite(value)
}
#[inline]
fn minimum_shape_segment_length_squared() -> f32 {
let linear_slop = 0.005 * crate::length_units_per_meter();
linear_slop * linear_slop
}
#[inline]
fn point_pair_has_minimum_separation(a: Vec2, b: Vec2) -> bool {
let dx = b.x - a.x;
let dy = b.y - a.y;
dx * dx + dy * dy > minimum_shape_segment_length_squared()
}
#[inline]
fn geometry_is_valid_or_err(valid: bool) -> ApiResult<()> {
if valid {
Ok(())
} else {
Err(ApiError::InvalidArgument)
}
}
#[track_caller]
fn assert_valid_geometry_vec2(name: &str, value: Vec2) {
assert!(
geometry_vec2_is_valid(value),
"{name} must be a valid Box2D vector, got {:?}",
value
);
}
#[inline]
fn check_valid_geometry_vec2(value: Vec2) -> ApiResult<()> {
geometry_is_valid_or_err(geometry_vec2_is_valid(value))
}
#[track_caller]
fn assert_non_negative_finite_density(density: f32) {
assert!(
geometry_density_is_valid(density),
"density must be finite and >= 0.0, got {density}"
);
}
#[inline]
fn check_non_negative_finite_density(density: f32) -> ApiResult<()> {
geometry_is_valid_or_err(geometry_density_is_valid(density))
}
#[track_caller]
fn assert_positive_finite_polygon_scalar(name: &str, value: f32) {
assert!(
crate::is_valid_float(value) && value > 0.0,
"{name} must be finite and > 0.0, got {value}"
);
}
#[inline]
fn check_positive_finite_polygon_scalar(value: f32) -> ApiResult<()> {
geometry_is_valid_or_err(crate::is_valid_float(value) && value > 0.0)
}
#[track_caller]
fn assert_non_negative_finite_polygon_scalar(name: &str, value: f32) {
assert!(
geometry_scalar_is_non_negative_finite(value),
"{name} must be finite and >= 0.0, got {value}"
);
}
#[inline]
fn check_non_negative_finite_polygon_scalar(value: f32) -> ApiResult<()> {
geometry_is_valid_or_err(geometry_scalar_is_non_negative_finite(value))
}
#[track_caller]
fn assert_transform_valid(transform: Transform) {
assert!(
transform.is_valid(),
"transform must be a valid Box2D transform, got {:?}",
transform
);
}
#[inline]
fn check_transform_valid(transform: Transform) -> ApiResult<()> {
geometry_is_valid_or_err(transform.is_valid())
}
#[inline]
fn circle_helper_geometry_is_valid(circle: Circle) -> bool {
circle.is_valid()
}
#[track_caller]
fn assert_circle_helper_geometry_valid(circle: Circle) {
assert!(
circle_helper_geometry_is_valid(circle),
"circle must contain valid Box2D geometry, got {:?}",
circle
);
}
#[inline]
fn check_circle_helper_geometry_valid(circle: Circle) -> ApiResult<()> {
geometry_is_valid_or_err(circle_helper_geometry_is_valid(circle))
}
#[inline]
fn segment_helper_geometry_is_valid(segment: Segment) -> bool {
segment.point1.is_valid() && segment.point2.is_valid()
}
#[track_caller]
fn assert_segment_helper_geometry_valid(segment: Segment) {
assert!(
segment_helper_geometry_is_valid(segment),
"segment must contain valid Box2D coordinates, got {:?}",
segment
);
}
#[inline]
fn check_segment_helper_geometry_valid(segment: Segment) -> ApiResult<()> {
geometry_is_valid_or_err(segment_helper_geometry_is_valid(segment))
}
#[inline]
fn capsule_helper_geometry_is_valid(capsule: Capsule) -> bool {
capsule.center1.is_valid()
&& capsule.center2.is_valid()
&& geometry_scalar_is_non_negative_finite(capsule.radius)
}
#[track_caller]
fn assert_capsule_helper_geometry_valid(capsule: Capsule) {
assert!(
capsule_helper_geometry_is_valid(capsule),
"capsule must contain valid Box2D geometry, got {:?}",
capsule
);
}
#[inline]
fn check_capsule_helper_geometry_valid(capsule: Capsule) -> ApiResult<()> {
geometry_is_valid_or_err(capsule_helper_geometry_is_valid(capsule))
}
#[inline]
fn polygon_helper_geometry_is_valid(polygon: Polygon) -> bool {
polygon.is_valid()
}
#[track_caller]
fn assert_polygon_helper_geometry_valid(polygon: Polygon) {
assert!(
polygon_helper_geometry_is_valid(polygon),
"polygon must contain valid Box2D geometry, got {:?}",
polygon
);
}
#[inline]
fn check_polygon_helper_geometry_valid(polygon: Polygon) -> ApiResult<()> {
geometry_is_valid_or_err(polygon_helper_geometry_is_valid(polygon))
}
#[inline]
fn try_compute_hull_from_points<I, P>(points: I) -> ApiResult<ffi::b2Hull>
where
I: IntoIterator<Item = P>,
P: Into<Vec2>,
{
compute_hull_from_points(points).ok_or(ApiError::InvalidArgument)
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Circle {
pub center: Vec2,
pub radius: f32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Segment {
pub point1: Vec2,
pub point2: Vec2,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
#[derive(Copy, Clone)]
pub struct ChainSegment {
pub ghost1: Vec2,
pub segment: Segment,
pub ghost2: Vec2,
#[cfg_attr(feature = "serde", serde(skip, default))]
chain_id: i32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Capsule {
pub center1: Vec2,
pub center2: Vec2,
pub radius: f32,
}
#[doc(alias = "polygon")]
#[derive(Copy, Clone)]
pub struct Polygon {
raw: ffi::b2Polygon,
}
#[inline]
pub fn circle<C: Into<Vec2>>(center: C, radius: f32) -> Circle {
Circle::new(center, radius)
}
#[inline]
pub fn segment<P1: Into<Vec2>, P2: Into<Vec2>>(point1: P1, point2: P2) -> Segment {
Segment::new(point1, point2)
}
#[inline]
pub fn chain_segment<G1, P1, P2, G2>(ghost1: G1, point1: P1, point2: P2, ghost2: G2) -> ChainSegment
where
G1: Into<Vec2>,
P1: Into<Vec2>,
P2: Into<Vec2>,
G2: Into<Vec2>,
{
ChainSegment::new(ghost1, point1, point2, ghost2)
}
#[inline]
pub fn capsule<C1: Into<Vec2>, C2: Into<Vec2>>(center1: C1, center2: C2, radius: f32) -> Capsule {
Capsule::new(center1, center2, radius)
}
#[inline]
pub fn box_polygon(half_width: f32, half_height: f32) -> Polygon {
Polygon::box_polygon(half_width, half_height)
}
#[inline]
pub fn try_box_polygon(half_width: f32, half_height: f32) -> ApiResult<Polygon> {
Polygon::try_box_polygon(half_width, half_height)
}
#[inline]
pub fn square_polygon(half_width: f32) -> Polygon {
Polygon::square_polygon(half_width)
}
#[inline]
pub fn try_square_polygon(half_width: f32) -> ApiResult<Polygon> {
Polygon::try_square_polygon(half_width)
}
#[inline]
pub fn rounded_box_polygon(half_width: f32, half_height: f32, radius: f32) -> Polygon {
Polygon::rounded_box_polygon(half_width, half_height, radius)
}
#[inline]
pub fn try_rounded_box_polygon(
half_width: f32,
half_height: f32,
radius: f32,
) -> ApiResult<Polygon> {
Polygon::try_rounded_box_polygon(half_width, half_height, radius)
}
#[inline]
pub fn offset_box_polygon(half_width: f32, half_height: f32, transform: Transform) -> Polygon {
Polygon::offset_box_polygon(half_width, half_height, transform)
}
#[inline]
pub fn try_offset_box_polygon(
half_width: f32,
half_height: f32,
transform: Transform,
) -> ApiResult<Polygon> {
Polygon::try_offset_box_polygon(half_width, half_height, transform)
}
#[inline]
pub fn offset_rounded_box_polygon(
half_width: f32,
half_height: f32,
radius: f32,
transform: Transform,
) -> Polygon {
Polygon::offset_rounded_box_polygon(half_width, half_height, radius, transform)
}
#[inline]
pub fn try_offset_rounded_box_polygon(
half_width: f32,
half_height: f32,
radius: f32,
transform: Transform,
) -> ApiResult<Polygon> {
Polygon::try_offset_rounded_box_polygon(half_width, half_height, radius, transform)
}
#[inline]
pub fn polygon_from_points<I, P>(points: I, radius: f32) -> Option<Polygon>
where
I: IntoIterator<Item = P>,
P: Into<Vec2>,
{
Polygon::from_points(points, radius)
}
#[inline]
pub fn try_polygon_from_points<I, P>(points: I, radius: f32) -> ApiResult<Polygon>
where
I: IntoIterator<Item = P>,
P: Into<Vec2>,
{
Polygon::try_from_points(points, radius)
}
#[inline]
pub fn offset_polygon_from_points<I, P>(
points: I,
radius: f32,
transform: Transform,
) -> Option<Polygon>
where
I: IntoIterator<Item = P>,
P: Into<Vec2>,
{
Polygon::offset_from_points(points, radius, transform)
}
#[inline]
pub fn try_offset_polygon_from_points<I, P>(
points: I,
radius: f32,
transform: Transform,
) -> ApiResult<Polygon>
where
I: IntoIterator<Item = P>,
P: Into<Vec2>,
{
Polygon::try_offset_from_points(points, radius, transform)
}
#[inline]
pub fn polygon_hull_is_valid<I, P>(points: I) -> bool
where
I: IntoIterator<Item = P>,
P: Into<Vec2>,
{
Polygon::hull_is_valid(points)
}