1use crate::core::foundation::transient_native_lease;
2use crate::error::Result;
3use crate::types::{Position, ShapeId, Vec2};
4use boxdd_sys::ffi;
5
6pub(super) fn minimum_mover_radius() -> Result<f32> {
7 Ok(0.01 * crate::core::foundation::current_length_units_per_meter()?)
8}
9
10pub(super) fn check_query_vec2_valid(
11 operation: &'static str,
12 argument: &'static str,
13 value: Vec2,
14) -> Result<()> {
15 if value.is_valid() {
16 Ok(())
17 } else {
18 Err(crate::error::Error::invalid_argument(
19 operation,
20 argument,
21 "a finite vector",
22 ))
23 }
24}
25
26pub(super) fn check_query_position_valid(
27 operation: &'static str,
28 argument: &'static str,
29 value: Position,
30) -> Result<()> {
31 if value.is_valid() {
32 Ok(())
33 } else {
34 Err(crate::error::Error::invalid_argument(
35 operation,
36 argument,
37 "a finite world position",
38 ))
39 }
40}
41pub(super) fn check_query_aabb_valid(operation: &'static str, aabb: Aabb) -> Result<()> {
42 if aabb.is_valid() {
43 Ok(())
44 } else {
45 Err(crate::error::Error::invalid_argument(
46 operation,
47 "aabb",
48 "finite ordered lower and upper bounds",
49 ))
50 }
51}
52
53#[inline]
54pub(super) fn check_query_non_negative_finite_scalar(
55 operation: &'static str,
56 argument: &'static str,
57 value: f32,
58) -> Result<()> {
59 if crate::is_valid_float(value) && value >= 0.0 {
60 Ok(())
61 } else {
62 Err(crate::error::Error::invalid_argument(
63 operation,
64 argument,
65 "a finite value greater than or equal to zero",
66 ))
67 }
68}
69
70#[inline]
71pub(super) fn check_query_mover_radius_valid(operation: &'static str, radius: f32) -> Result<()> {
72 if crate::is_valid_float(radius) && radius > minimum_mover_radius()? {
73 Ok(())
74 } else {
75 Err(crate::error::Error::invalid_argument(
76 operation,
77 "radius",
78 "a finite value greater than the configured minimum mover radius",
79 ))
80 }
81}
82
83#[doc(alias = "aabb")]
88#[cfg_attr(feature = "serde", derive(serde::Serialize))]
89#[repr(C)]
90#[derive(Copy, Clone, Debug, PartialEq)]
91pub struct Aabb {
92 pub(crate) lower: Vec2,
93 pub(crate) upper: Vec2,
94}
95
96#[cfg(feature = "bytemuck")]
97unsafe impl bytemuck::Zeroable for Aabb {}
98#[cfg(feature = "bytemuck")]
99const _: () = {
100 assert!(core::mem::size_of::<Aabb>() == 16);
101 assert!(core::mem::align_of::<Aabb>() == 4);
102};
103
104impl Aabb {
105 #[inline]
106 pub fn from_raw(raw: ffi::b2AABB) -> Result<Self> {
108 let aabb = Self {
109 lower: Vec2::from_raw(raw.lowerBound),
110 upper: Vec2::from_raw(raw.upperBound),
111 };
112 check_query_aabb_valid("Aabb::from_raw", aabb)?;
113 Ok(aabb)
114 }
115
116 #[inline]
117 pub(crate) fn from_raw_unvalidated(raw: ffi::b2AABB) -> Self {
118 Self {
119 lower: Vec2::from_raw(raw.lowerBound),
120 upper: Vec2::from_raw(raw.upperBound),
121 }
122 }
123
124 #[inline]
125 pub const fn lower(self) -> Vec2 {
126 self.lower
127 }
128
129 #[inline]
130 pub const fn upper(self) -> Vec2 {
131 self.upper
132 }
133
134 #[inline]
135 pub fn into_raw(self) -> ffi::b2AABB {
136 ffi::b2AABB {
137 lowerBound: self.lower.into_raw(),
138 upperBound: self.upper.into_raw(),
139 }
140 }
141
142 #[inline]
144 pub fn new<L: Into<Vec2>, U: Into<Vec2>>(lower: L, upper: U) -> Result<Self> {
145 let aabb = Self {
146 lower: lower.into(),
147 upper: upper.into(),
148 };
149 check_query_aabb_valid("Aabb::new", aabb)?;
150 Ok(aabb)
151 }
152 #[inline]
154 pub fn from_center_half_extents<C: Into<Vec2>, H: Into<Vec2>>(
155 center: C,
156 half: H,
157 ) -> Result<Self> {
158 let c = center.into();
159 let h = half.into();
160 let aabb = Self {
161 lower: Vec2::new(c.x - h.x, c.y - h.y),
162 upper: Vec2::new(c.x + h.x, c.y + h.y),
163 };
164 check_query_aabb_valid("Aabb::from_center_half_extents", aabb)?;
165 Ok(aabb)
166 }
167}
168
169#[cfg(feature = "serde")]
170impl<'de> serde::Deserialize<'de> for Aabb {
171 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
172 where
173 D: serde::Deserializer<'de>,
174 {
175 #[derive(serde::Deserialize)]
176 struct Repr {
177 lower: Vec2,
178 upper: Vec2,
179 }
180
181 let repr = <Repr as serde::Deserialize>::deserialize(deserializer)?;
182 Self::new(repr.lower, repr.upper).map_err(serde::de::Error::custom)
183 }
184}
185
186#[cfg(feature = "mint")]
187impl From<Aabb> for (mint::Point2<f32>, mint::Point2<f32>) {
188 #[inline]
189 fn from(a: Aabb) -> Self {
190 (a.lower.into(), a.upper.into())
191 }
192}
193
194#[cfg(feature = "mint")]
195impl TryFrom<(mint::Point2<f32>, mint::Point2<f32>)> for Aabb {
196 type Error = crate::Error;
197
198 #[inline]
199 fn try_from((lower, upper): (mint::Point2<f32>, mint::Point2<f32>)) -> Result<Self> {
200 Self::new(lower, upper)
201 }
202}
203
204#[cfg(feature = "mint")]
205impl From<Aabb> for (mint::Vector2<f32>, mint::Vector2<f32>) {
206 #[inline]
207 fn from(a: Aabb) -> Self {
208 (a.lower.into(), a.upper.into())
209 }
210}
211
212#[cfg(feature = "mint")]
213impl TryFrom<(mint::Vector2<f32>, mint::Vector2<f32>)> for Aabb {
214 type Error = crate::Error;
215
216 #[inline]
217 fn try_from((lower, upper): (mint::Vector2<f32>, mint::Vector2<f32>)) -> Result<Self> {
218 Self::new(lower, upper)
219 }
220}
221
222#[cfg(feature = "glam")]
223impl From<Aabb> for (glam::Vec2, glam::Vec2) {
224 #[inline]
225 fn from(a: Aabb) -> Self {
226 (a.lower.into(), a.upper.into())
227 }
228}
229
230#[cfg(feature = "glam")]
231impl TryFrom<(glam::Vec2, glam::Vec2)> for Aabb {
232 type Error = crate::Error;
233
234 #[inline]
235 fn try_from((lower, upper): (glam::Vec2, glam::Vec2)) -> Result<Self> {
236 Self::new(lower, upper)
237 }
238}
239
240#[cfg(feature = "nalgebra")]
241impl From<Aabb> for (nalgebra::Point2<f32>, nalgebra::Point2<f32>) {
242 #[inline]
243 fn from(a: Aabb) -> Self {
244 (a.lower.into(), a.upper.into())
245 }
246}
247
248#[cfg(feature = "nalgebra")]
249impl TryFrom<(nalgebra::Point2<f32>, nalgebra::Point2<f32>)> for Aabb {
250 type Error = crate::Error;
251
252 #[inline]
253 fn try_from((lower, upper): (nalgebra::Point2<f32>, nalgebra::Point2<f32>)) -> Result<Self> {
254 Self::new(lower, upper)
255 }
256}
257
258#[cfg(feature = "nalgebra")]
259impl From<Aabb> for (nalgebra::Vector2<f32>, nalgebra::Vector2<f32>) {
260 #[inline]
261 fn from(a: Aabb) -> Self {
262 (a.lower.into(), a.upper.into())
263 }
264}
265
266#[cfg(feature = "nalgebra")]
267impl TryFrom<(nalgebra::Vector2<f32>, nalgebra::Vector2<f32>)> for Aabb {
268 type Error = crate::Error;
269
270 #[inline]
271 fn try_from((lower, upper): (nalgebra::Vector2<f32>, nalgebra::Vector2<f32>)) -> Result<Self> {
272 Self::new(lower, upper)
273 }
274}
275
276#[doc(alias = "query_filter")]
278#[derive(Copy, Clone, Debug)]
279pub struct QueryFilter(pub(crate) ffi::b2QueryFilter);
280
281impl Default for QueryFilter {
282 fn default() -> Self {
283 Self(crate::core::native_defaults::query_filter())
284 }
285}
286
287#[cfg(feature = "serde")]
288impl serde::Serialize for QueryFilter {
289 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
290 where
291 S: serde::Serializer,
292 {
293 #[derive(serde::Serialize)]
294 struct Repr {
295 category_bits: u64,
296 mask_bits: u64,
297 }
298 Repr {
299 category_bits: self.0.categoryBits,
300 mask_bits: self.0.maskBits,
301 }
302 .serialize(serializer)
303 }
304}
305
306#[cfg(feature = "serde")]
307impl<'de> serde::Deserialize<'de> for QueryFilter {
308 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
309 where
310 D: serde::Deserializer<'de>,
311 {
312 #[derive(serde::Deserialize)]
313 struct Repr {
314 category_bits: u64,
315 mask_bits: u64,
316 }
317 let r = <Repr as serde::Deserialize>::deserialize(deserializer)?;
318 Ok(Self(ffi::b2QueryFilter {
319 categoryBits: r.category_bits,
320 maskBits: r.mask_bits,
321 }))
322 }
323}
324
325impl QueryFilter {
326 #[inline]
328 pub fn new() -> Self {
329 Self::default()
330 }
331
332 pub fn category_bits(&self) -> u64 {
333 self.0.categoryBits
334 }
335
336 pub fn mask_bits(&self) -> u64 {
337 self.0.maskBits
338 }
339
340 pub fn mask(mut self, bits: u64) -> Self {
341 self.0.maskBits = bits;
342 self
343 }
344 pub fn category(mut self, bits: u64) -> Self {
345 self.0.categoryBits = bits;
346 self
347 }
348}
349
350#[doc(alias = "ray_result")]
352#[derive(Copy, Clone, Debug)]
353pub struct RayResult {
354 pub shape_id: ShapeId,
355 pub point: Position,
357 pub normal: Vec2,
362 pub fraction: f32,
364 pub hit: bool,
365}
366
367#[doc(alias = "closest_ray_cast_result")]
371#[derive(Copy, Clone, Debug)]
372pub struct ClosestRayCastResult {
373 pub hit: Option<RayResult>,
375 pub node_visits: i32,
377 pub leaf_visits: i32,
379}
380
381#[doc(alias = "plane")]
383#[cfg_attr(feature = "serde", derive(serde::Serialize))]
384#[repr(C)]
385#[derive(Copy, Clone, Debug, PartialEq)]
386pub struct Plane {
387 pub(crate) normal: Vec2,
388 pub(crate) offset: f32,
389}
390
391impl Plane {
392 #[inline]
393 pub fn new<N: Into<Vec2>>(normal: N, offset: f32) -> Result<Self> {
394 let plane = Self {
395 normal: normal.into(),
396 offset,
397 };
398 if plane.is_valid() {
399 Ok(plane)
400 } else {
401 Err(crate::Error::invalid_argument(
402 "Plane::new",
403 "normal/offset",
404 "a finite plane with a unit normal",
405 ))
406 }
407 }
408
409 #[inline]
411 pub fn is_valid(self) -> bool {
412 self.normal.is_valid()
413 && (1.0 - (self.normal.x * self.normal.x + self.normal.y * self.normal.y)).abs()
414 < 100.0 * f32::EPSILON
415 && self.offset.is_finite()
416 }
417
418 #[inline]
419 pub fn from_raw(raw: ffi::b2Plane) -> Result<Self> {
421 let plane = Self {
422 normal: Vec2::from_raw(raw.normal),
423 offset: raw.offset,
424 };
425 if plane.is_valid() {
426 Ok(plane)
427 } else {
428 Err(crate::Error::invalid_argument(
429 "Plane::from_raw",
430 "raw",
431 "a finite plane with a unit normal",
432 ))
433 }
434 }
435
436 #[inline]
437 pub(crate) fn from_raw_unvalidated(raw: ffi::b2Plane) -> Self {
438 Self {
439 normal: Vec2::from_raw(raw.normal),
440 offset: raw.offset,
441 }
442 }
443
444 #[inline]
445 pub const fn normal(self) -> Vec2 {
446 self.normal
447 }
448
449 #[inline]
450 pub const fn offset(self) -> f32 {
451 self.offset
452 }
453
454 #[inline]
455 pub fn into_raw(self) -> ffi::b2Plane {
456 ffi::b2Plane {
457 normal: self.normal.into_raw(),
458 offset: self.offset,
459 }
460 }
461}
462
463#[cfg(feature = "serde")]
464impl<'de> serde::Deserialize<'de> for Plane {
465 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
466 where
467 D: serde::Deserializer<'de>,
468 {
469 #[derive(serde::Deserialize)]
470 struct Repr {
471 normal: Vec2,
472 offset: f32,
473 }
474
475 let repr = <Repr as serde::Deserialize>::deserialize(deserializer)?;
476 Self::new(repr.normal, repr.offset).map_err(serde::de::Error::custom)
477 }
478}
479
480const _: () = {
481 assert!(core::mem::size_of::<Plane>() == core::mem::size_of::<ffi::b2Plane>());
482 assert!(core::mem::align_of::<Plane>() == core::mem::align_of::<ffi::b2Plane>());
483};
484
485#[doc(alias = "plane_result")]
487#[derive(Copy, Clone, Debug)]
488pub struct MoverPlaneResult {
489 pub shape_id: ShapeId,
490 pub plane: Plane,
491 pub point: Vec2,
493 pub hit: bool,
494}
495
496impl MoverPlaneResult {
497 #[inline]
501 pub fn into_collision_plane(
502 self,
503 push_limit: f32,
504 clip_velocity: bool,
505 ) -> Result<Option<CollisionPlane>> {
506 self.hit
507 .then(|| CollisionPlane::new(self.plane, push_limit, clip_velocity))
508 .transpose()
509 }
510
511 #[inline]
515 pub fn into_rigid_collision_plane(self) -> Result<Option<CollisionPlane>> {
516 self.into_collision_plane(CollisionPlane::RIGID_PUSH_LIMIT, true)
517 }
518}
519
520#[doc(alias = "collision_plane")]
522#[cfg_attr(feature = "serde", derive(serde::Serialize))]
523#[repr(C)]
524#[derive(Copy, Clone, Debug, PartialEq)]
525pub struct CollisionPlane {
526 pub(crate) plane: Plane,
527 pub(crate) push_limit: f32,
528 pub(crate) push: f32,
529 pub(crate) clip_velocity: bool,
530}
531
532impl CollisionPlane {
533 pub const RIGID_PUSH_LIMIT: f32 = f32::MAX;
534
535 #[inline]
536 pub fn new(plane: Plane, push_limit: f32, clip_velocity: bool) -> Result<Self> {
537 let collision_plane = Self {
538 plane,
539 push_limit,
540 push: 0.0,
541 clip_velocity,
542 };
543 check_query_collision_plane_valid("CollisionPlane::new", &collision_plane)?;
544 Ok(collision_plane)
545 }
546
547 #[inline]
548 pub fn rigid(plane: Plane) -> Result<Self> {
549 Self::new(plane, Self::RIGID_PUSH_LIMIT, true)
550 }
551
552 pub fn validate(&self) -> Result<()> {
554 check_query_collision_plane_valid("CollisionPlane::validate", self)
555 }
556
557 #[inline]
558 pub fn from_raw(raw: ffi::b2CollisionPlane) -> Result<Self> {
560 let plane = Self::from_raw_unvalidated(raw);
561 check_query_collision_plane_valid("CollisionPlane::from_raw", &plane)?;
562 Ok(plane)
563 }
564
565 #[inline]
566 pub(crate) fn from_raw_unvalidated(raw: ffi::b2CollisionPlane) -> Self {
567 Self {
568 plane: Plane::from_raw_unvalidated(raw.plane),
569 push_limit: raw.pushLimit,
570 push: raw.push,
571 clip_velocity: raw.clipVelocity,
572 }
573 }
574
575 #[inline]
576 pub const fn plane(self) -> Plane {
577 self.plane
578 }
579
580 #[inline]
581 pub const fn push_limit(self) -> f32 {
582 self.push_limit
583 }
584
585 #[inline]
586 pub const fn push(self) -> f32 {
587 self.push
588 }
589
590 #[inline]
591 pub const fn clip_velocity(self) -> bool {
592 self.clip_velocity
593 }
594
595 #[inline]
596 pub fn into_raw(self) -> ffi::b2CollisionPlane {
597 ffi::b2CollisionPlane {
598 plane: self.plane.into_raw(),
599 pushLimit: self.push_limit,
600 push: self.push,
601 clipVelocity: self.clip_velocity,
602 }
603 }
604}
605
606#[cfg(feature = "serde")]
607impl<'de> serde::Deserialize<'de> for CollisionPlane {
608 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
609 where
610 D: serde::Deserializer<'de>,
611 {
612 #[derive(serde::Deserialize)]
613 struct Repr {
614 plane: Plane,
615 push_limit: f32,
616 push: f32,
617 clip_velocity: bool,
618 }
619
620 let repr = <Repr as serde::Deserialize>::deserialize(deserializer)?;
621 let plane = Self {
622 plane: repr.plane,
623 push_limit: repr.push_limit,
624 push: repr.push,
625 clip_velocity: repr.clip_velocity,
626 };
627 check_query_collision_plane_valid("CollisionPlane::deserialize", &plane)
628 .map_err(serde::de::Error::custom)?;
629 Ok(plane)
630 }
631}
632
633#[inline]
634pub(super) fn check_query_solver_collision_plane_valid(
635 operation: &'static str,
636 plane: &CollisionPlane,
637) -> Result<()> {
638 if !plane.plane.is_valid() {
639 return Err(crate::error::Error::invalid_argument(
640 operation,
641 "planes[].plane",
642 "a finite plane with a unit normal",
643 ));
644 }
645 check_query_non_negative_finite_scalar(operation, "planes[].push_limit", plane.push_limit)
646}
647
648#[inline]
649pub(super) fn check_query_collision_plane_valid(
650 operation: &'static str,
651 plane: &CollisionPlane,
652) -> Result<()> {
653 check_query_solver_collision_plane_valid(operation, plane)?;
654 check_query_non_negative_finite_scalar(operation, "planes[].push", plane.push)
655}
656
657const _: () = {
658 assert!(
659 core::mem::size_of::<CollisionPlane>() == core::mem::size_of::<ffi::b2CollisionPlane>()
660 );
661 assert!(
662 core::mem::align_of::<CollisionPlane>() == core::mem::align_of::<ffi::b2CollisionPlane>()
663 );
664};
665
666#[doc(alias = "plane_solver_result")]
668#[cfg_attr(feature = "serde", derive(serde::Serialize))]
669#[derive(Copy, Clone, Debug, PartialEq)]
670pub struct PlaneSolverResult {
671 translation: Vec2,
672 iteration_count: i32,
673}
674
675impl PlaneSolverResult {
676 #[inline]
678 pub fn new<T: Into<Vec2>>(translation: T, iteration_count: i32) -> Result<Self> {
679 let result = Self {
680 translation: translation.into(),
681 iteration_count,
682 };
683 result.validate_for("PlaneSolverResult::new")?;
684 Ok(result)
685 }
686
687 #[inline]
689 pub fn from_raw(raw: ffi::b2PlaneSolverResult) -> Result<Self> {
690 let result = Self::from_raw_unvalidated(raw);
691 result.validate_for("PlaneSolverResult::from_raw")?;
692 Ok(result)
693 }
694
695 #[inline]
696 fn from_native(operation: &'static str, raw: ffi::b2PlaneSolverResult) -> Result<Self> {
697 let result = Self::from_raw_unvalidated(raw);
698 if !result.translation.is_valid() {
699 return Err(crate::error::Error::InvalidNativeOutput {
700 operation,
701 output: "translation",
702 constraint: "a finite vector",
703 });
704 }
705 if result.iteration_count < 0 {
706 return Err(crate::error::Error::InvalidNativeOutput {
707 operation,
708 output: "iteration_count",
709 constraint: "a non-negative native int",
710 });
711 }
712 Ok(result)
713 }
714
715 #[inline]
716 fn from_raw_unvalidated(raw: ffi::b2PlaneSolverResult) -> Self {
717 Self {
718 translation: Vec2::from_raw(raw.translation),
719 iteration_count: raw.iterationCount,
720 }
721 }
722
723 #[inline]
725 pub fn validate(&self) -> Result<()> {
726 self.validate_for("PlaneSolverResult::validate")
727 }
728
729 #[inline]
730 fn validate_for(&self, operation: &'static str) -> Result<()> {
731 check_query_vec2_valid(operation, "translation", self.translation)?;
732 if self.iteration_count < 0 {
733 return Err(crate::error::Error::invalid_argument(
734 operation,
735 "iteration_count",
736 "a non-negative native int",
737 ));
738 }
739 Ok(())
740 }
741
742 #[inline]
744 pub const fn translation(self) -> Vec2 {
745 self.translation
746 }
747
748 #[inline]
750 pub const fn iteration_count(self) -> i32 {
751 self.iteration_count
752 }
753
754 #[inline]
756 pub fn into_raw(self) -> ffi::b2PlaneSolverResult {
757 ffi::b2PlaneSolverResult {
758 translation: self.translation.into_raw(),
759 iterationCount: self.iteration_count,
760 }
761 }
762}
763
764#[cfg(feature = "serde")]
765impl<'de> serde::Deserialize<'de> for PlaneSolverResult {
766 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
767 where
768 D: serde::Deserializer<'de>,
769 {
770 #[derive(serde::Deserialize)]
771 struct Repr {
772 translation: Vec2,
773 iteration_count: i32,
774 }
775
776 let repr = <Repr as serde::Deserialize>::deserialize(deserializer)?;
777 Self::new(repr.translation, repr.iteration_count).map_err(serde::de::Error::custom)
778 }
779}
780
781#[inline]
782pub(super) fn raw_collision_planes(planes: &[CollisionPlane]) -> *const ffi::b2CollisionPlane {
783 if planes.is_empty() {
784 core::ptr::null()
785 } else {
786 planes.as_ptr().cast()
787 }
788}
789
790fn commit_native_solved_planes(
791 planes: &mut [CollisionPlane],
792 raw_planes: Vec<ffi::b2CollisionPlane>,
793) -> Result<()> {
794 debug_assert_eq!(planes.len(), raw_planes.len());
795 if raw_planes
796 .iter()
797 .copied()
798 .any(|plane| CollisionPlane::from_raw(plane).is_err())
799 {
800 return Err(crate::error::Error::InvalidNativeOutput {
801 operation: "solve_planes",
802 output: "planes",
803 constraint: "finite valid collision planes with non-negative push values",
804 });
805 }
806 for (plane, raw) in planes.iter_mut().zip(raw_planes) {
807 *plane = CollisionPlane::from_raw_unvalidated(raw);
808 }
809 Ok(())
810}
811
812#[inline]
813fn check_collision_plane_count(operation: &'static str, count: usize) -> Result<i32> {
814 i32::try_from(count).map_err(|_| {
815 crate::error::Error::invalid_argument(
816 operation,
817 "planes",
818 "a slice length representable by a native int",
819 )
820 })
821}
822
823#[inline]
828pub fn solve_planes<V: Into<Vec2>>(
829 target_delta: V,
830 planes: &mut [CollisionPlane],
831) -> Result<PlaneSolverResult> {
832 let target_delta = target_delta.into();
833 check_query_vec2_valid("solve_planes", "target_delta", target_delta)?;
834 let plane_count = check_collision_plane_count("solve_planes", planes.len())?;
835 for plane in planes.iter() {
836 check_query_solver_collision_plane_valid("solve_planes", plane)?;
837 }
838 let mut raw_planes = Vec::new();
839 raw_planes
840 .try_reserve_exact(planes.len())
841 .map_err(|_| crate::error::Error::FfiOutputAllocationFailed)?;
842 raw_planes.extend(planes.iter().copied().map(CollisionPlane::into_raw));
843 let _lease = transient_native_lease()?;
844 let raw = unsafe {
845 ffi::b2SolvePlanes(
846 target_delta.into_raw(),
847 if raw_planes.is_empty() {
848 core::ptr::null_mut()
849 } else {
850 raw_planes.as_mut_ptr()
851 },
852 plane_count,
853 )
854 };
855 let result = PlaneSolverResult::from_native("solve_planes", raw)?;
856 commit_native_solved_planes(planes, raw_planes)?;
857 Ok(result)
858}
859
860#[inline]
862pub fn clip_vector<V: Into<Vec2>>(vector: V, planes: &[CollisionPlane]) -> Result<Vec2> {
863 let vector = vector.into();
864 check_query_vec2_valid("clip_vector", "vector", vector)?;
865 let plane_count = check_collision_plane_count("clip_vector", planes.len())?;
866 for plane in planes.iter() {
867 check_query_collision_plane_valid("clip_vector", plane)?;
868 }
869 let _lease = transient_native_lease()?;
870 let clipped = Vec2::from_raw(unsafe {
871 ffi::b2ClipVector(vector.into_raw(), raw_collision_planes(planes), plane_count)
872 });
873 if clipped.is_valid() {
874 Ok(clipped)
875 } else {
876 Err(crate::error::Error::InvalidNativeOutput {
877 operation: "clip_vector",
878 output: "vector",
879 constraint: "a finite vector",
880 })
881 }
882}
883
884#[cfg(test)]
885mod tests {
886 use super::*;
887 use core::sync::atomic::{AtomicBool, Ordering};
888 use std::sync::Arc;
889
890 struct TrackedVec2 {
891 converted: Arc<AtomicBool>,
892 value: Vec2,
893 }
894
895 impl From<TrackedVec2> for Vec2 {
896 fn from(value: TrackedVec2) -> Self {
897 value.converted.store(true, Ordering::Relaxed);
898 value.value
899 }
900 }
901
902 fn tracked(value: Vec2) -> (TrackedVec2, Arc<AtomicBool>) {
903 let converted = Arc::new(AtomicBool::new(false));
904 (
905 TrackedVec2 {
906 converted: Arc::clone(&converted),
907 value,
908 },
909 converted,
910 )
911 }
912
913 #[test]
914 fn query_filter_default_is_pure_and_callback_safe() {
915 let _callback_guard = crate::core::callback_state::CallbackGuard::enter();
916 let filter = QueryFilter::default();
917 assert_eq!(filter.category_bits(), 1);
918 assert_eq!(filter.mask_bits(), u64::MAX);
919 }
920
921 #[test]
922 fn plane_validation_matches_box2d_normalization_tolerance() {
923 assert!(Plane::new([1.0, 0.0], 0.0).unwrap().is_valid());
924 assert!(Plane::new([1.000_005, 0.0], 0.0).unwrap().is_valid());
925 assert!(Plane::new([1.000_01, 0.0], 0.0).is_err());
926 assert!(Plane::new([f32::NAN, 0.0], 0.0).is_err());
927 assert!(Plane::new([1.0, 0.0], f32::INFINITY).is_err());
928 }
929
930 #[test]
931 fn invalid_native_solver_planes_are_not_partially_published() {
932 let plane = Plane::new([0.0, 1.0], 0.0).unwrap();
933 let original = [
934 CollisionPlane::new(plane, 1.0, true).unwrap(),
935 CollisionPlane::new(plane, 2.0, false).unwrap(),
936 ];
937 let mut output = original;
938 let mut first = original[0].into_raw();
939 first.push = 0.5;
940 let mut second = original[1].into_raw();
941 second.push = f32::NAN;
942
943 assert!(matches!(
944 commit_native_solved_planes(&mut output, vec![first, second]),
945 Err(crate::Error::InvalidNativeOutput {
946 operation: "solve_planes",
947 output: "planes",
948 ..
949 })
950 ));
951 assert_eq!(output, original);
952 }
953
954 #[test]
955 fn pure_mover_validation_is_callback_safe_and_native_calls_reject_reentry() {
956 let plane = Plane::new([0.0, 1.0], 0.0).unwrap();
957 let mut planes = [CollisionPlane::rigid(plane).unwrap()];
958 let _callback_guard = crate::core::callback_state::CallbackGuard::enter();
959
960 assert!(plane.is_valid());
961 assert_eq!(planes[0].validate(), Ok(()));
962
963 let (target, target_converted) = tracked(Vec2::new(0.0, -0.2));
964 assert_eq!(
965 solve_planes(target, &mut planes),
966 Err(crate::error::Error::InCallback)
967 );
968 assert!(target_converted.load(Ordering::Relaxed));
969
970 let (vector, vector_converted) = tracked(Vec2::new(0.0, -1.0));
971 assert_eq!(
972 clip_vector(vector, &planes),
973 Err(crate::error::Error::InCallback)
974 );
975 assert!(vector_converted.load(Ordering::Relaxed));
976
977 let mut invalid_planes = [CollisionPlane {
978 plane: Plane {
979 normal: Vec2::new(0.0, 2.0),
980 offset: 0.0,
981 },
982 push_limit: CollisionPlane::RIGID_PUSH_LIMIT,
983 push: 0.0,
984 clip_velocity: true,
985 }];
986 assert_eq!(
987 solve_planes(Vec2::ZERO, &mut invalid_planes),
988 Err(crate::error::Error::invalid_argument(
989 "solve_planes",
990 "planes[].plane",
991 "a finite plane with a unit normal",
992 ))
993 );
994 }
995}