1use crate::error::ApiResult;
2use crate::types::{ShapeId, Vec2};
3use boxdd_sys::ffi;
4
5pub(super) fn minimum_mover_radius() -> f32 {
6 0.01 * crate::length_units_per_meter()
7}
8
9pub(super) fn assert_query_vec2_valid(name: &str, value: Vec2) {
10 assert!(
11 value.is_valid(),
12 "{name} must be a valid Box2D vector, got {:?}",
13 value
14 );
15}
16pub(super) fn check_query_vec2_valid(value: Vec2) -> ApiResult<()> {
17 if value.is_valid() {
18 Ok(())
19 } else {
20 Err(crate::error::ApiError::InvalidArgument)
21 }
22}
23pub(super) fn assert_query_aabb_valid(aabb: Aabb) {
24 assert!(aabb.is_valid(), "aabb must be valid, got {:?}", aabb);
25}
26pub(super) fn check_query_aabb_valid(aabb: Aabb) -> ApiResult<()> {
27 if aabb.is_valid() {
28 Ok(())
29 } else {
30 Err(crate::error::ApiError::InvalidArgument)
31 }
32}
33
34#[inline]
35pub(super) fn assert_query_non_negative_finite_scalar(name: &str, value: f32) {
36 assert!(
37 crate::is_valid_float(value) && value >= 0.0,
38 "{name} must be finite and >= 0.0, got {value}"
39 );
40}
41
42#[inline]
43pub(super) fn check_query_non_negative_finite_scalar(value: f32) -> ApiResult<()> {
44 if crate::is_valid_float(value) && value >= 0.0 {
45 Ok(())
46 } else {
47 Err(crate::error::ApiError::InvalidArgument)
48 }
49}
50
51#[inline]
52pub(super) fn assert_query_angle_valid(angle_radians: f32) {
53 assert!(
54 crate::is_valid_float(angle_radians),
55 "angle_radians must be finite, got {angle_radians}"
56 );
57}
58
59#[inline]
60pub(super) fn check_query_angle_valid(angle_radians: f32) -> ApiResult<()> {
61 if crate::is_valid_float(angle_radians) {
62 Ok(())
63 } else {
64 Err(crate::error::ApiError::InvalidArgument)
65 }
66}
67
68#[inline]
69pub(super) fn assert_query_mover_radius_valid(radius: f32) {
70 let minimum = minimum_mover_radius();
71 assert!(
72 crate::is_valid_float(radius) && radius > minimum,
73 "mover radius must be finite and > {minimum}, got {radius}"
74 );
75}
76
77#[inline]
78pub(super) fn check_query_mover_radius_valid(radius: f32) -> ApiResult<()> {
79 if crate::is_valid_float(radius) && radius > minimum_mover_radius() {
80 Ok(())
81 } else {
82 Err(crate::error::ApiError::InvalidArgument)
83 }
84}
85
86#[doc(alias = "aabb")]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89#[repr(C)]
90#[derive(Copy, Clone, Debug, PartialEq)]
91pub struct Aabb {
92 pub lower: Vec2,
93 pub upper: Vec2,
94}
95
96#[cfg(feature = "bytemuck")]
97unsafe impl bytemuck::Zeroable for Aabb {}
98#[cfg(feature = "bytemuck")]
99unsafe impl bytemuck::Pod for Aabb {}
100
101#[cfg(feature = "bytemuck")]
102const _: () = {
103 assert!(core::mem::size_of::<Aabb>() == 16);
104 assert!(core::mem::align_of::<Aabb>() == 4);
105};
106
107impl Aabb {
108 #[inline]
109 pub fn from_raw(raw: ffi::b2AABB) -> Self {
110 Self {
111 lower: Vec2::from_raw(raw.lowerBound),
112 upper: Vec2::from_raw(raw.upperBound),
113 }
114 }
115
116 #[inline]
117 pub fn into_raw(self) -> ffi::b2AABB {
118 ffi::b2AABB {
119 lowerBound: self.lower.into_raw(),
120 upperBound: self.upper.into_raw(),
121 }
122 }
123
124 #[inline]
126 pub fn new<L: Into<Vec2>, U: Into<Vec2>>(lower: L, upper: U) -> Self {
127 Self {
128 lower: lower.into(),
129 upper: upper.into(),
130 }
131 }
132 #[inline]
134 pub fn from_center_half_extents<C: Into<Vec2>, H: Into<Vec2>>(center: C, half: H) -> Self {
135 let c = center.into();
136 let h = half.into();
137 Self {
138 lower: Vec2::new(c.x - h.x, c.y - h.y),
139 upper: Vec2::new(c.x + h.x, c.y + h.y),
140 }
141 }
142}
143
144#[cfg(feature = "mint")]
145impl From<Aabb> for (mint::Point2<f32>, mint::Point2<f32>) {
146 #[inline]
147 fn from(a: Aabb) -> Self {
148 (a.lower.into(), a.upper.into())
149 }
150}
151
152#[cfg(feature = "mint")]
153impl From<(mint::Point2<f32>, mint::Point2<f32>)> for Aabb {
154 #[inline]
155 fn from((lower, upper): (mint::Point2<f32>, mint::Point2<f32>)) -> Self {
156 Self::new(lower, upper)
157 }
158}
159
160#[cfg(feature = "mint")]
161impl From<Aabb> for (mint::Vector2<f32>, mint::Vector2<f32>) {
162 #[inline]
163 fn from(a: Aabb) -> Self {
164 (a.lower.into(), a.upper.into())
165 }
166}
167
168#[cfg(feature = "mint")]
169impl From<(mint::Vector2<f32>, mint::Vector2<f32>)> for Aabb {
170 #[inline]
171 fn from((lower, upper): (mint::Vector2<f32>, mint::Vector2<f32>)) -> Self {
172 Self::new(lower, upper)
173 }
174}
175
176#[cfg(feature = "glam")]
177impl From<Aabb> for (glam::Vec2, glam::Vec2) {
178 #[inline]
179 fn from(a: Aabb) -> Self {
180 (a.lower.into(), a.upper.into())
181 }
182}
183
184#[cfg(feature = "glam")]
185impl From<(glam::Vec2, glam::Vec2)> for Aabb {
186 #[inline]
187 fn from((lower, upper): (glam::Vec2, glam::Vec2)) -> Self {
188 Self {
189 lower: lower.into(),
190 upper: upper.into(),
191 }
192 }
193}
194
195#[cfg(feature = "cgmath")]
196impl From<Aabb> for (cgmath::Point2<f32>, cgmath::Point2<f32>) {
197 #[inline]
198 fn from(a: Aabb) -> Self {
199 (a.lower.into(), a.upper.into())
200 }
201}
202
203#[cfg(feature = "cgmath")]
204impl From<(cgmath::Point2<f32>, cgmath::Point2<f32>)> for Aabb {
205 #[inline]
206 fn from((lower, upper): (cgmath::Point2<f32>, cgmath::Point2<f32>)) -> Self {
207 Self::new(lower, upper)
208 }
209}
210
211#[cfg(feature = "cgmath")]
212impl From<Aabb> for (cgmath::Vector2<f32>, cgmath::Vector2<f32>) {
213 #[inline]
214 fn from(a: Aabb) -> Self {
215 (a.lower.into(), a.upper.into())
216 }
217}
218
219#[cfg(feature = "cgmath")]
220impl From<(cgmath::Vector2<f32>, cgmath::Vector2<f32>)> for Aabb {
221 #[inline]
222 fn from((lower, upper): (cgmath::Vector2<f32>, cgmath::Vector2<f32>)) -> Self {
223 Self::new(lower, upper)
224 }
225}
226
227#[cfg(feature = "nalgebra")]
228impl From<Aabb> for (nalgebra::Point2<f32>, nalgebra::Point2<f32>) {
229 #[inline]
230 fn from(a: Aabb) -> Self {
231 (a.lower.into(), a.upper.into())
232 }
233}
234
235#[cfg(feature = "nalgebra")]
236impl From<(nalgebra::Point2<f32>, nalgebra::Point2<f32>)> for Aabb {
237 #[inline]
238 fn from((lower, upper): (nalgebra::Point2<f32>, nalgebra::Point2<f32>)) -> Self {
239 Self::new(lower, upper)
240 }
241}
242
243#[cfg(feature = "nalgebra")]
244impl From<Aabb> for (nalgebra::Vector2<f32>, nalgebra::Vector2<f32>) {
245 #[inline]
246 fn from(a: Aabb) -> Self {
247 (a.lower.into(), a.upper.into())
248 }
249}
250
251#[cfg(feature = "nalgebra")]
252impl From<(nalgebra::Vector2<f32>, nalgebra::Vector2<f32>)> for Aabb {
253 #[inline]
254 fn from((lower, upper): (nalgebra::Vector2<f32>, nalgebra::Vector2<f32>)) -> Self {
255 Self::new(lower, upper)
256 }
257}
258
259#[doc(alias = "query_filter")]
261#[derive(Copy, Clone, Debug)]
262pub struct QueryFilter(pub(crate) ffi::b2QueryFilter);
263
264impl Default for QueryFilter {
265 fn default() -> Self {
266 Self(unsafe { ffi::b2DefaultQueryFilter() })
267 }
268}
269
270#[cfg(feature = "serde")]
271impl serde::Serialize for QueryFilter {
272 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
273 where
274 S: serde::Serializer,
275 {
276 #[derive(serde::Serialize)]
277 struct Repr {
278 category_bits: u64,
279 mask_bits: u64,
280 }
281 Repr {
282 category_bits: self.0.categoryBits,
283 mask_bits: self.0.maskBits,
284 }
285 .serialize(serializer)
286 }
287}
288
289#[cfg(feature = "serde")]
290impl<'de> serde::Deserialize<'de> for QueryFilter {
291 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
292 where
293 D: serde::Deserializer<'de>,
294 {
295 #[derive(serde::Deserialize)]
296 struct Repr {
297 category_bits: u64,
298 mask_bits: u64,
299 }
300 let r = Repr::deserialize(deserializer)?;
301 Ok(Self(ffi::b2QueryFilter {
302 categoryBits: r.category_bits,
303 maskBits: r.mask_bits,
304 }))
305 }
306}
307
308impl QueryFilter {
309 pub fn category_bits(&self) -> u64 {
310 self.0.categoryBits
311 }
312
313 pub fn mask_bits(&self) -> u64 {
314 self.0.maskBits
315 }
316
317 pub fn mask(mut self, bits: u64) -> Self {
318 self.0.maskBits = bits;
319 self
320 }
321 pub fn category(mut self, bits: u64) -> Self {
322 self.0.categoryBits = bits;
323 self
324 }
325}
326
327#[doc(alias = "ray_result")]
329#[derive(Copy, Clone, Debug)]
330pub struct RayResult {
331 pub shape_id: ShapeId,
332 pub point: Vec2,
333 pub normal: Vec2,
334 pub fraction: f32,
335 pub hit: bool,
336}
337
338impl RayResult {
339 #[inline]
340 pub fn from_raw(raw: ffi::b2RayResult) -> Self {
341 Self {
342 shape_id: ShapeId::from_raw(raw.shapeId),
343 point: Vec2::from_raw(raw.point),
344 normal: Vec2::from_raw(raw.normal),
345 fraction: raw.fraction,
346 hit: raw.hit,
347 }
348 }
349}
350
351#[doc(alias = "plane")]
353#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
354#[repr(C)]
355#[derive(Copy, Clone, Debug, PartialEq)]
356pub struct Plane {
357 pub normal: Vec2,
358 pub offset: f32,
359}
360
361impl Plane {
362 #[inline]
363 pub fn new<N: Into<Vec2>>(normal: N, offset: f32) -> Self {
364 Self {
365 normal: normal.into(),
366 offset,
367 }
368 }
369
370 #[inline]
371 pub fn is_valid(self) -> bool {
372 unsafe { ffi::b2IsValidPlane(self.into_raw()) }
373 }
374
375 #[inline]
376 pub fn from_raw(raw: ffi::b2Plane) -> Self {
377 Self {
378 normal: Vec2::from_raw(raw.normal),
379 offset: raw.offset,
380 }
381 }
382
383 #[inline]
384 pub fn into_raw(self) -> ffi::b2Plane {
385 ffi::b2Plane {
386 normal: self.normal.into_raw(),
387 offset: self.offset,
388 }
389 }
390}
391
392#[cfg(feature = "bytemuck")]
393unsafe impl bytemuck::Zeroable for Plane {}
394#[cfg(feature = "bytemuck")]
395unsafe impl bytemuck::Pod for Plane {}
396
397const _: () = {
398 assert!(core::mem::size_of::<Plane>() == core::mem::size_of::<ffi::b2Plane>());
399 assert!(core::mem::align_of::<Plane>() == core::mem::align_of::<ffi::b2Plane>());
400};
401
402#[doc(alias = "plane_result")]
404#[derive(Copy, Clone, Debug)]
405pub struct MoverPlaneResult {
406 pub shape_id: ShapeId,
407 pub plane: Plane,
408 pub point: Vec2,
409 pub hit: bool,
410}
411
412impl MoverPlaneResult {
413 #[inline]
417 pub fn into_collision_plane(
418 self,
419 push_limit: f32,
420 clip_velocity: bool,
421 ) -> Option<CollisionPlane> {
422 self.hit
423 .then(|| CollisionPlane::new(self.plane, push_limit, clip_velocity))
424 }
425
426 #[inline]
430 pub fn into_rigid_collision_plane(self) -> Option<CollisionPlane> {
431 self.into_collision_plane(CollisionPlane::RIGID_PUSH_LIMIT, true)
432 }
433}
434
435#[doc(alias = "collision_plane")]
437#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
438#[repr(C)]
439#[derive(Copy, Clone, Debug, PartialEq)]
440pub struct CollisionPlane {
441 pub plane: Plane,
442 pub push_limit: f32,
443 pub push: f32,
444 pub clip_velocity: bool,
445}
446
447impl CollisionPlane {
448 pub const RIGID_PUSH_LIMIT: f32 = f32::MAX;
449
450 #[inline]
451 pub fn new(plane: Plane, push_limit: f32, clip_velocity: bool) -> Self {
452 Self {
453 plane,
454 push_limit,
455 push: 0.0,
456 clip_velocity,
457 }
458 }
459
460 #[inline]
461 pub fn rigid(plane: Plane) -> Self {
462 Self::new(plane, Self::RIGID_PUSH_LIMIT, true)
463 }
464
465 pub fn validate(&self) -> ApiResult<()> {
467 check_query_collision_plane_valid(self)
468 }
469
470 #[inline]
471 pub fn from_raw(raw: ffi::b2CollisionPlane) -> Self {
472 Self {
473 plane: Plane::from_raw(raw.plane),
474 push_limit: raw.pushLimit,
475 push: raw.push,
476 clip_velocity: raw.clipVelocity,
477 }
478 }
479
480 #[inline]
481 pub fn into_raw(self) -> ffi::b2CollisionPlane {
482 ffi::b2CollisionPlane {
483 plane: self.plane.into_raw(),
484 pushLimit: self.push_limit,
485 push: self.push,
486 clipVelocity: self.clip_velocity,
487 }
488 }
489}
490
491#[inline]
492pub(super) fn assert_query_solver_collision_plane_valid(plane: &CollisionPlane) {
493 assert!(
494 check_query_solver_collision_plane_valid(plane).is_ok(),
495 "collision plane must be solver-valid, got {:?}",
496 plane
497 );
498}
499
500#[inline]
501pub(super) fn check_query_solver_collision_plane_valid(plane: &CollisionPlane) -> ApiResult<()> {
502 if !plane.plane.is_valid() {
503 return Err(crate::error::ApiError::InvalidArgument);
504 }
505 check_query_non_negative_finite_scalar(plane.push_limit)
506}
507
508#[inline]
509pub(super) fn assert_query_collision_plane_valid(plane: &CollisionPlane) {
510 assert!(
511 check_query_collision_plane_valid(plane).is_ok(),
512 "collision plane must be valid, got {:?}",
513 plane
514 );
515}
516
517#[inline]
518pub(super) fn check_query_collision_plane_valid(plane: &CollisionPlane) -> ApiResult<()> {
519 check_query_solver_collision_plane_valid(plane)?;
520 check_query_non_negative_finite_scalar(plane.push)
521}
522
523const _: () = {
524 assert!(
525 core::mem::size_of::<CollisionPlane>() == core::mem::size_of::<ffi::b2CollisionPlane>()
526 );
527 assert!(
528 core::mem::align_of::<CollisionPlane>() == core::mem::align_of::<ffi::b2CollisionPlane>()
529 );
530};
531
532#[doc(alias = "plane_solver_result")]
534#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
535#[derive(Copy, Clone, Debug, PartialEq)]
536pub struct PlaneSolverResult {
537 pub translation: Vec2,
538 pub iteration_count: i32,
539}
540
541impl PlaneSolverResult {
542 #[inline]
543 pub fn from_raw(raw: ffi::b2PlaneSolverResult) -> Self {
544 Self {
545 translation: Vec2::from_raw(raw.translation),
546 iteration_count: raw.iterationCount,
547 }
548 }
549}
550
551#[inline]
552pub(super) fn raw_collision_planes_mut(
553 planes: &mut [CollisionPlane],
554) -> *mut ffi::b2CollisionPlane {
555 if planes.is_empty() {
556 core::ptr::null_mut()
557 } else {
558 planes.as_mut_ptr().cast()
559 }
560}
561
562#[inline]
563pub(super) fn raw_collision_planes(planes: &[CollisionPlane]) -> *const ffi::b2CollisionPlane {
564 if planes.is_empty() {
565 core::ptr::null()
566 } else {
567 planes.as_ptr().cast()
568 }
569}
570
571#[inline]
575pub fn solve_planes<V: Into<Vec2>>(
576 target_delta: V,
577 planes: &mut [CollisionPlane],
578) -> PlaneSolverResult {
579 let target_delta = target_delta.into();
580 assert_query_vec2_valid("target_delta", target_delta);
581 for plane in planes.iter() {
582 assert_query_solver_collision_plane_valid(plane);
583 }
584 let raw = unsafe {
585 ffi::b2SolvePlanes(
586 target_delta.into_raw(),
587 raw_collision_planes_mut(planes),
588 planes.len() as i32,
589 )
590 };
591 PlaneSolverResult::from_raw(raw)
592}
593
594#[inline]
598pub fn try_solve_planes<V: Into<Vec2>>(
599 target_delta: V,
600 planes: &mut [CollisionPlane],
601) -> ApiResult<PlaneSolverResult> {
602 let target_delta = target_delta.into();
603 check_query_vec2_valid(target_delta)?;
604 for plane in planes.iter() {
605 check_query_solver_collision_plane_valid(plane)?;
606 }
607 let raw = unsafe {
608 ffi::b2SolvePlanes(
609 target_delta.into_raw(),
610 raw_collision_planes_mut(planes),
611 planes.len() as i32,
612 )
613 };
614 Ok(PlaneSolverResult::from_raw(raw))
615}
616
617#[inline]
619pub fn clip_vector<V: Into<Vec2>>(vector: V, planes: &[CollisionPlane]) -> Vec2 {
620 let vector = vector.into();
621 assert_query_vec2_valid("vector", vector);
622 for plane in planes.iter() {
623 assert_query_collision_plane_valid(plane);
624 }
625 Vec2::from_raw(unsafe {
626 ffi::b2ClipVector(
627 vector.into_raw(),
628 raw_collision_planes(planes),
629 planes.len() as i32,
630 )
631 })
632}
633
634#[inline]
638pub fn try_clip_vector<V: Into<Vec2>>(vector: V, planes: &[CollisionPlane]) -> ApiResult<Vec2> {
639 let vector = vector.into();
640 check_query_vec2_valid(vector)?;
641 for plane in planes.iter() {
642 check_query_collision_plane_valid(plane)?;
643 }
644 Ok(Vec2::from_raw(unsafe {
645 ffi::b2ClipVector(
646 vector.into_raw(),
647 raw_collision_planes(planes),
648 planes.len() as i32,
649 )
650 }))
651}