boxdd/
query.rs

1//! Broad-phase queries and casting helpers.
2//!
3//! - AABB overlap: collect matching shape ids.
4//! - Ray casts: closest or all hits along a path.
5//! - Shape overlap / casting: build a temporary proxy from points + radius (accepts `Into<Vec2>` points).
6//! - Offset proxies: apply translation + rotation to the proxy for queries in local frames.
7//!
8//! Note: Box2D proxies support at most `B2_MAX_POLYGON_VERTICES` points (8). Extra points are ignored.
9//!
10//! Filters: use `QueryFilter` to restrict categories/masks.
11use crate::types::{ShapeId, Vec2};
12use crate::world::{World, WorldHandle};
13use boxdd_sys::ffi;
14use std::any::Any;
15
16const MAX_PROXY_POINTS: usize = ffi::B2_MAX_POLYGON_VERTICES as usize;
17
18fn collect_proxy_points<I, P>(points: I) -> Vec<ffi::b2Vec2>
19where
20    I: IntoIterator<Item = P>,
21    P: Into<Vec2>,
22{
23    let mut out: Vec<ffi::b2Vec2> = Vec::with_capacity(MAX_PROXY_POINTS);
24    for p in points.into_iter().take(MAX_PROXY_POINTS) {
25        out.push(ffi::b2Vec2::from(p.into()));
26    }
27    out
28}
29
30fn overlap_aabb_impl(world: ffi::b2WorldId, aabb: Aabb, filter: QueryFilter) -> Vec<ShapeId> {
31    struct Ctx<'a> {
32        out: &'a mut Vec<ShapeId>,
33        panicked: &'a mut bool,
34        panic: &'a mut Option<Box<dyn Any + Send + 'static>>,
35    }
36    unsafe extern "C" fn cb(shape_id: ffi::b2ShapeId, ctx: *mut core::ffi::c_void) -> bool {
37        let ctx = unsafe { &mut *(ctx as *mut Ctx<'_>) };
38        if *ctx.panicked {
39            return false;
40        }
41        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
42            ctx.out.push(shape_id);
43            true
44        }));
45        match r {
46            Ok(v) => v,
47            Err(p) => {
48                *ctx.panicked = true;
49                *ctx.panic = Some(p);
50                false
51            }
52        }
53    }
54
55    let mut out: Vec<ShapeId> = Vec::new();
56    let mut panicked = false;
57    let mut panic: Option<Box<dyn Any + Send + 'static>> = None;
58    let mut ctx = Ctx {
59        out: &mut out,
60        panicked: &mut panicked,
61        panic: &mut panic,
62    };
63    unsafe {
64        let _ = ffi::b2World_OverlapAABB(
65            world,
66            aabb.into(),
67            filter.0,
68            Some(cb),
69            &mut ctx as *mut _ as *mut _,
70        );
71    }
72    if let Some(p) = panic.take() {
73        std::panic::resume_unwind(p);
74    }
75    out
76}
77
78fn cast_ray_closest_impl<VO: Into<Vec2>, VT: Into<Vec2>>(
79    world: ffi::b2WorldId,
80    origin: VO,
81    translation: VT,
82    filter: QueryFilter,
83) -> RayResult {
84    let o: ffi::b2Vec2 = origin.into().into();
85    let t: ffi::b2Vec2 = translation.into().into();
86    let raw = unsafe { ffi::b2World_CastRayClosest(world, o, t, filter.0) };
87    RayResult::from(raw)
88}
89
90fn cast_ray_all_impl<VO: Into<Vec2>, VT: Into<Vec2>>(
91    world: ffi::b2WorldId,
92    origin: VO,
93    translation: VT,
94    filter: QueryFilter,
95) -> Vec<RayResult> {
96    struct Ctx<'a> {
97        out: &'a mut Vec<RayResult>,
98        panicked: &'a mut bool,
99        panic: &'a mut Option<Box<dyn Any + Send + 'static>>,
100    }
101    #[allow(clippy::unnecessary_cast)]
102    unsafe extern "C" fn cb(
103        shape_id: ffi::b2ShapeId,
104        point: ffi::b2Vec2,
105        normal: ffi::b2Vec2,
106        fraction: f32,
107        ctx: *mut core::ffi::c_void,
108    ) -> f32 {
109        let ctx = unsafe { &mut *(ctx as *mut Ctx<'_>) };
110        if *ctx.panicked {
111            return 0.0;
112        }
113        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
114            ctx.out.push(RayResult {
115                shape_id,
116                point: point.into(),
117                normal: normal.into(),
118                fraction,
119                hit: true,
120            });
121            1.0f32
122        }));
123        match r {
124            Ok(v) => v,
125            Err(p) => {
126                *ctx.panicked = true;
127                *ctx.panic = Some(p);
128                0.0
129            }
130        }
131    }
132    let mut out: Vec<RayResult> = Vec::new();
133    let mut panicked = false;
134    let mut panic: Option<Box<dyn Any + Send + 'static>> = None;
135    let mut ctx = Ctx {
136        out: &mut out,
137        panicked: &mut panicked,
138        panic: &mut panic,
139    };
140    let o: ffi::b2Vec2 = origin.into().into();
141    let t: ffi::b2Vec2 = translation.into().into();
142    unsafe {
143        let _ = ffi::b2World_CastRay(
144            world,
145            o,
146            t,
147            filter.0,
148            Some(cb),
149            &mut ctx as *mut _ as *mut _,
150        );
151    }
152    if let Some(p) = panic.take() {
153        std::panic::resume_unwind(p);
154    }
155    out
156}
157
158fn overlap_polygon_points_impl<I, P>(
159    world: ffi::b2WorldId,
160    points: I,
161    radius: f32,
162    filter: QueryFilter,
163) -> Vec<ShapeId>
164where
165    I: IntoIterator<Item = P>,
166    P: Into<Vec2>,
167{
168    let pts: Vec<ffi::b2Vec2> = collect_proxy_points(points);
169    if pts.is_empty() {
170        return Vec::new();
171    }
172    let proxy = unsafe { ffi::b2MakeProxy(pts.as_ptr(), pts.len() as i32, radius) };
173    let mut out = Vec::new();
174    let mut panicked = false;
175    let mut panic: Option<Box<dyn Any + Send + 'static>> = None;
176    struct Ctx<'a> {
177        out: &'a mut Vec<ShapeId>,
178        panicked: &'a mut bool,
179        panic: &'a mut Option<Box<dyn Any + Send + 'static>>,
180    }
181    unsafe extern "C" fn cb(shape_id: ffi::b2ShapeId, ctx: *mut core::ffi::c_void) -> bool {
182        let ctx = unsafe { &mut *(ctx as *mut Ctx<'_>) };
183        if *ctx.panicked {
184            return false;
185        }
186        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
187            ctx.out.push(shape_id);
188            true
189        }));
190        match r {
191            Ok(v) => v,
192            Err(p) => {
193                *ctx.panicked = true;
194                *ctx.panic = Some(p);
195                false
196            }
197        }
198    }
199    let mut ctx = Ctx {
200        out: &mut out,
201        panicked: &mut panicked,
202        panic: &mut panic,
203    };
204    unsafe {
205        let _ = ffi::b2World_OverlapShape(
206            world,
207            &proxy,
208            filter.0,
209            Some(cb),
210            &mut ctx as *mut _ as *mut _,
211        );
212    }
213    if let Some(p) = panic.take() {
214        std::panic::resume_unwind(p);
215    }
216    out
217}
218
219fn cast_shape_points_impl<I, P, VT>(
220    world: ffi::b2WorldId,
221    points: I,
222    radius: f32,
223    translation: VT,
224    filter: QueryFilter,
225) -> Vec<RayResult>
226where
227    I: IntoIterator<Item = P>,
228    P: Into<Vec2>,
229    VT: Into<Vec2>,
230{
231    struct Ctx<'a> {
232        out: &'a mut Vec<RayResult>,
233        panicked: &'a mut bool,
234        panic: &'a mut Option<Box<dyn Any + Send + 'static>>,
235    }
236    #[allow(clippy::unnecessary_cast)]
237    unsafe extern "C" fn cb(
238        shape_id: ffi::b2ShapeId,
239        point: ffi::b2Vec2,
240        normal: ffi::b2Vec2,
241        fraction: f32,
242        ctx: *mut core::ffi::c_void,
243    ) -> f32 {
244        let ctx = unsafe { &mut *(ctx as *mut Ctx<'_>) };
245        if *ctx.panicked {
246            return 0.0;
247        }
248        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
249            ctx.out.push(RayResult {
250                shape_id,
251                point: point.into(),
252                normal: normal.into(),
253                fraction,
254                hit: true,
255            });
256            1.0f32
257        }));
258        match r {
259            Ok(v) => v,
260            Err(p) => {
261                *ctx.panicked = true;
262                *ctx.panic = Some(p);
263                0.0
264            }
265        }
266    }
267    let pts: Vec<ffi::b2Vec2> = collect_proxy_points(points);
268    if pts.is_empty() {
269        return Vec::new();
270    }
271    let proxy = unsafe { ffi::b2MakeProxy(pts.as_ptr(), pts.len() as i32, radius) };
272    let mut out: Vec<RayResult> = Vec::new();
273    let mut panicked = false;
274    let mut panic: Option<Box<dyn Any + Send + 'static>> = None;
275    let mut ctx = Ctx {
276        out: &mut out,
277        panicked: &mut panicked,
278        panic: &mut panic,
279    };
280    let t: ffi::b2Vec2 = translation.into().into();
281    unsafe {
282        let _ = ffi::b2World_CastShape(
283            world,
284            &proxy,
285            t,
286            filter.0,
287            Some(cb),
288            &mut ctx as *mut _ as *mut _,
289        );
290    }
291    if let Some(p) = panic.take() {
292        std::panic::resume_unwind(p);
293    }
294    out
295}
296
297fn cast_mover_impl<V1: Into<Vec2>, V2: Into<Vec2>, VT: Into<Vec2>>(
298    world: ffi::b2WorldId,
299    c1: V1,
300    c2: V2,
301    radius: f32,
302    translation: VT,
303    filter: QueryFilter,
304) -> f32 {
305    let cap = ffi::b2Capsule {
306        center1: c1.into().into(),
307        center2: c2.into().into(),
308        radius,
309    };
310    let t: ffi::b2Vec2 = translation.into().into();
311    unsafe { ffi::b2World_CastMover(world, &cap, t, filter.0) }
312}
313
314fn overlap_polygon_points_with_offset_impl<I, P, V, A>(
315    world: ffi::b2WorldId,
316    points: I,
317    radius: f32,
318    position: V,
319    angle_radians: A,
320    filter: QueryFilter,
321) -> Vec<ShapeId>
322where
323    I: IntoIterator<Item = P>,
324    P: Into<Vec2>,
325    V: Into<Vec2>,
326    A: Into<f32>,
327{
328    let pts: Vec<ffi::b2Vec2> = collect_proxy_points(points);
329    if pts.is_empty() {
330        return Vec::new();
331    }
332    let (s, c) = angle_radians.into().sin_cos();
333    let pos: ffi::b2Vec2 = position.into().into();
334    let proxy = unsafe {
335        ffi::b2MakeOffsetProxy(
336            pts.as_ptr(),
337            pts.len() as i32,
338            radius,
339            pos,
340            ffi::b2Rot { c, s },
341        )
342    };
343    let mut out = Vec::new();
344    let mut panicked = false;
345    let mut panic: Option<Box<dyn Any + Send + 'static>> = None;
346    struct Ctx<'a> {
347        out: &'a mut Vec<ShapeId>,
348        panicked: &'a mut bool,
349        panic: &'a mut Option<Box<dyn Any + Send + 'static>>,
350    }
351    unsafe extern "C" fn cb(shape_id: ffi::b2ShapeId, ctx: *mut core::ffi::c_void) -> bool {
352        let ctx = unsafe { &mut *(ctx as *mut Ctx<'_>) };
353        if *ctx.panicked {
354            return false;
355        }
356        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
357            ctx.out.push(shape_id);
358            true
359        }));
360        match r {
361            Ok(v) => v,
362            Err(p) => {
363                *ctx.panicked = true;
364                *ctx.panic = Some(p);
365                false
366            }
367        }
368    }
369    let mut ctx = Ctx {
370        out: &mut out,
371        panicked: &mut panicked,
372        panic: &mut panic,
373    };
374    unsafe {
375        let _ = ffi::b2World_OverlapShape(
376            world,
377            &proxy,
378            filter.0,
379            Some(cb),
380            &mut ctx as *mut _ as *mut _,
381        );
382    }
383    if let Some(p) = panic.take() {
384        std::panic::resume_unwind(p);
385    }
386    out
387}
388
389fn cast_shape_points_with_offset_impl<I, P, V, A, VT>(
390    world: ffi::b2WorldId,
391    points: I,
392    radius: f32,
393    position: V,
394    angle_radians: A,
395    translation: VT,
396    filter: QueryFilter,
397) -> Vec<RayResult>
398where
399    I: IntoIterator<Item = P>,
400    P: Into<Vec2>,
401    V: Into<Vec2>,
402    A: Into<f32>,
403    VT: Into<Vec2>,
404{
405    struct Ctx<'a> {
406        out: &'a mut Vec<RayResult>,
407        panicked: &'a mut bool,
408        panic: &'a mut Option<Box<dyn Any + Send + 'static>>,
409    }
410    #[allow(clippy::unnecessary_cast)]
411    unsafe extern "C" fn cb(
412        shape_id: ffi::b2ShapeId,
413        point: ffi::b2Vec2,
414        normal: ffi::b2Vec2,
415        fraction: f32,
416        ctx: *mut core::ffi::c_void,
417    ) -> f32 {
418        let ctx = unsafe { &mut *(ctx as *mut Ctx<'_>) };
419        if *ctx.panicked {
420            return 0.0;
421        }
422        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
423            ctx.out.push(RayResult {
424                shape_id,
425                point: point.into(),
426                normal: normal.into(),
427                fraction,
428                hit: true,
429            });
430            1.0f32
431        }));
432        match r {
433            Ok(v) => v,
434            Err(p) => {
435                *ctx.panicked = true;
436                *ctx.panic = Some(p);
437                0.0
438            }
439        }
440    }
441    let pts: Vec<ffi::b2Vec2> = collect_proxy_points(points);
442    if pts.is_empty() {
443        return Vec::new();
444    }
445    let (s, c) = angle_radians.into().sin_cos();
446    let pos: ffi::b2Vec2 = position.into().into();
447    let proxy = unsafe {
448        ffi::b2MakeOffsetProxy(
449            pts.as_ptr(),
450            pts.len() as i32,
451            radius,
452            pos,
453            ffi::b2Rot { c, s },
454        )
455    };
456    let mut out: Vec<RayResult> = Vec::new();
457    let mut panicked = false;
458    let mut panic: Option<Box<dyn Any + Send + 'static>> = None;
459    let mut ctx = Ctx {
460        out: &mut out,
461        panicked: &mut panicked,
462        panic: &mut panic,
463    };
464    let t: ffi::b2Vec2 = translation.into().into();
465    unsafe {
466        let _ = ffi::b2World_CastShape(
467            world,
468            &proxy,
469            t,
470            filter.0,
471            Some(cb),
472            &mut ctx as *mut _ as *mut _,
473        );
474    }
475    if let Some(p) = panic.take() {
476        std::panic::resume_unwind(p);
477    }
478    out
479}
480
481/// Axis-aligned bounding box
482#[doc(alias = "aabb")]
483#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
484#[repr(C)]
485#[derive(Copy, Clone, Debug, PartialEq)]
486pub struct Aabb {
487    pub lower: Vec2,
488    pub upper: Vec2,
489}
490
491#[cfg(feature = "bytemuck")]
492unsafe impl bytemuck::Zeroable for Aabb {}
493#[cfg(feature = "bytemuck")]
494unsafe impl bytemuck::Pod for Aabb {}
495
496#[cfg(feature = "bytemuck")]
497const _: () = {
498    assert!(core::mem::size_of::<Aabb>() == 16);
499    assert!(core::mem::align_of::<Aabb>() == 4);
500};
501
502impl From<Aabb> for ffi::b2AABB {
503    fn from(a: Aabb) -> Self {
504        ffi::b2AABB {
505            lowerBound: a.lower.into(),
506            upperBound: a.upper.into(),
507        }
508    }
509}
510
511impl Aabb {
512    /// Create an AABB from lower and upper points.
513    #[inline]
514    pub fn new<L: Into<Vec2>, U: Into<Vec2>>(lower: L, upper: U) -> Self {
515        Self {
516            lower: lower.into(),
517            upper: upper.into(),
518        }
519    }
520    /// Create an AABB from center and half-extents (both in world units).
521    #[inline]
522    pub fn from_center_half_extents<C: Into<Vec2>, H: Into<Vec2>>(center: C, half: H) -> Self {
523        let c = center.into();
524        let h = half.into();
525        Self {
526            lower: Vec2::new(c.x - h.x, c.y - h.y),
527            upper: Vec2::new(c.x + h.x, c.y + h.y),
528        }
529    }
530}
531
532#[cfg(feature = "mint")]
533impl From<Aabb> for (mint::Point2<f32>, mint::Point2<f32>) {
534    #[inline]
535    fn from(a: Aabb) -> Self {
536        (a.lower.into(), a.upper.into())
537    }
538}
539
540#[cfg(feature = "mint")]
541impl From<(mint::Point2<f32>, mint::Point2<f32>)> for Aabb {
542    #[inline]
543    fn from((lower, upper): (mint::Point2<f32>, mint::Point2<f32>)) -> Self {
544        Self::new(lower, upper)
545    }
546}
547
548#[cfg(feature = "mint")]
549impl From<Aabb> for (mint::Vector2<f32>, mint::Vector2<f32>) {
550    #[inline]
551    fn from(a: Aabb) -> Self {
552        (a.lower.into(), a.upper.into())
553    }
554}
555
556#[cfg(feature = "mint")]
557impl From<(mint::Vector2<f32>, mint::Vector2<f32>)> for Aabb {
558    #[inline]
559    fn from((lower, upper): (mint::Vector2<f32>, mint::Vector2<f32>)) -> Self {
560        Self::new(lower, upper)
561    }
562}
563
564#[cfg(feature = "glam")]
565impl From<Aabb> for (glam::Vec2, glam::Vec2) {
566    #[inline]
567    fn from(a: Aabb) -> Self {
568        (a.lower.into(), a.upper.into())
569    }
570}
571
572#[cfg(feature = "glam")]
573impl From<(glam::Vec2, glam::Vec2)> for Aabb {
574    #[inline]
575    fn from((lower, upper): (glam::Vec2, glam::Vec2)) -> Self {
576        Self {
577            lower: lower.into(),
578            upper: upper.into(),
579        }
580    }
581}
582
583#[cfg(feature = "cgmath")]
584impl From<Aabb> for (cgmath::Point2<f32>, cgmath::Point2<f32>) {
585    #[inline]
586    fn from(a: Aabb) -> Self {
587        (a.lower.into(), a.upper.into())
588    }
589}
590
591#[cfg(feature = "cgmath")]
592impl From<(cgmath::Point2<f32>, cgmath::Point2<f32>)> for Aabb {
593    #[inline]
594    fn from((lower, upper): (cgmath::Point2<f32>, cgmath::Point2<f32>)) -> Self {
595        Self::new(lower, upper)
596    }
597}
598
599#[cfg(feature = "cgmath")]
600impl From<Aabb> for (cgmath::Vector2<f32>, cgmath::Vector2<f32>) {
601    #[inline]
602    fn from(a: Aabb) -> Self {
603        (a.lower.into(), a.upper.into())
604    }
605}
606
607#[cfg(feature = "cgmath")]
608impl From<(cgmath::Vector2<f32>, cgmath::Vector2<f32>)> for Aabb {
609    #[inline]
610    fn from((lower, upper): (cgmath::Vector2<f32>, cgmath::Vector2<f32>)) -> Self {
611        Self::new(lower, upper)
612    }
613}
614
615#[cfg(feature = "nalgebra")]
616impl From<Aabb> for (nalgebra::Point2<f32>, nalgebra::Point2<f32>) {
617    #[inline]
618    fn from(a: Aabb) -> Self {
619        (a.lower.into(), a.upper.into())
620    }
621}
622
623#[cfg(feature = "nalgebra")]
624impl From<(nalgebra::Point2<f32>, nalgebra::Point2<f32>)> for Aabb {
625    #[inline]
626    fn from((lower, upper): (nalgebra::Point2<f32>, nalgebra::Point2<f32>)) -> Self {
627        Self::new(lower, upper)
628    }
629}
630
631#[cfg(feature = "nalgebra")]
632impl From<Aabb> for (nalgebra::Vector2<f32>, nalgebra::Vector2<f32>) {
633    #[inline]
634    fn from(a: Aabb) -> Self {
635        (a.lower.into(), a.upper.into())
636    }
637}
638
639#[cfg(feature = "nalgebra")]
640impl From<(nalgebra::Vector2<f32>, nalgebra::Vector2<f32>)> for Aabb {
641    #[inline]
642    fn from((lower, upper): (nalgebra::Vector2<f32>, nalgebra::Vector2<f32>)) -> Self {
643        Self::new(lower, upper)
644    }
645}
646
647/// Filter for queries
648#[doc(alias = "query_filter")]
649#[derive(Copy, Clone, Debug)]
650pub struct QueryFilter(pub(crate) ffi::b2QueryFilter);
651
652impl Default for QueryFilter {
653    fn default() -> Self {
654        Self(unsafe { ffi::b2DefaultQueryFilter() })
655    }
656}
657
658#[cfg(feature = "serde")]
659impl serde::Serialize for QueryFilter {
660    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
661    where
662        S: serde::Serializer,
663    {
664        #[derive(serde::Serialize)]
665        struct Repr {
666            category_bits: u64,
667            mask_bits: u64,
668        }
669        Repr {
670            category_bits: self.0.categoryBits,
671            mask_bits: self.0.maskBits,
672        }
673        .serialize(serializer)
674    }
675}
676
677#[cfg(feature = "serde")]
678impl<'de> serde::Deserialize<'de> for QueryFilter {
679    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
680    where
681        D: serde::Deserializer<'de>,
682    {
683        #[derive(serde::Deserialize)]
684        struct Repr {
685            category_bits: u64,
686            mask_bits: u64,
687        }
688        let r = Repr::deserialize(deserializer)?;
689        Ok(Self(ffi::b2QueryFilter {
690            categoryBits: r.category_bits,
691            maskBits: r.mask_bits,
692        }))
693    }
694}
695
696impl QueryFilter {
697    pub fn category_bits(&self) -> u64 {
698        self.0.categoryBits
699    }
700
701    pub fn mask_bits(&self) -> u64 {
702        self.0.maskBits
703    }
704
705    pub fn mask(mut self, bits: u64) -> Self {
706        self.0.maskBits = bits;
707        self
708    }
709    pub fn category(mut self, bits: u64) -> Self {
710        self.0.categoryBits = bits;
711        self
712    }
713}
714
715/// Result of a closest ray cast
716#[doc(alias = "ray_result")]
717#[derive(Copy, Clone, Debug)]
718pub struct RayResult {
719    pub shape_id: ShapeId,
720    pub point: Vec2,
721    pub normal: Vec2,
722    pub fraction: f32,
723    pub hit: bool,
724}
725
726impl From<ffi::b2RayResult> for RayResult {
727    fn from(r: ffi::b2RayResult) -> Self {
728        Self {
729            shape_id: r.shapeId,
730            point: r.point.into(),
731            normal: r.normal.into(),
732            fraction: r.fraction,
733            hit: r.hit,
734        }
735    }
736}
737
738impl World {
739    /// Overlap test for all shapes in an AABB. Returns matching shape ids.
740    ///
741    /// Example
742    /// ```no_run
743    /// use boxdd::{World, WorldDef, BodyBuilder, ShapeDef, shapes, Vec2, Aabb, QueryFilter};
744    /// let mut world = World::new(WorldDef::builder().gravity([0.0,-9.8]).build()).unwrap();
745    /// let b = world.create_body_id(BodyBuilder::new().position([0.0, 2.0]).build());
746    /// let sdef = ShapeDef::builder().density(1.0).build();
747    /// world.create_polygon_shape_for(b, &sdef, &shapes::box_polygon(0.5, 0.5));
748    /// let hits = world.overlap_aabb(Aabb { lower: Vec2::new(-1.0, -1.0), upper: Vec2::new(1.0, 3.0) }, QueryFilter::default());
749    /// assert!(!hits.is_empty());
750    /// ```
751    pub fn overlap_aabb(&self, aabb: Aabb, filter: QueryFilter) -> Vec<ShapeId> {
752        crate::core::callback_state::assert_not_in_callback();
753        overlap_aabb_impl(self.raw(), aabb, filter)
754    }
755
756    pub fn try_overlap_aabb(
757        &self,
758        aabb: Aabb,
759        filter: QueryFilter,
760    ) -> crate::error::ApiResult<Vec<ShapeId>> {
761        crate::core::callback_state::check_not_in_callback()?;
762        Ok(overlap_aabb_impl(self.raw(), aabb, filter))
763    }
764
765    /// Cast a ray and return the closest hit.
766    ///
767    /// Example
768    /// ```no_run
769    /// use boxdd::{World, WorldDef, QueryFilter, Vec2};
770    /// let mut world = World::new(WorldDef::builder().gravity([0.0,-9.8]).build()).unwrap();
771    /// let hit = world.cast_ray_closest(Vec2::new(0.0, 5.0), Vec2::new(0.0, -10.0), QueryFilter::default());
772    /// if hit.hit { /* use hit.point / hit.normal */ }
773    /// ```
774    pub fn cast_ray_closest<VO: Into<Vec2>, VT: Into<Vec2>>(
775        &self,
776        origin: VO,
777        translation: VT,
778        filter: QueryFilter,
779    ) -> RayResult {
780        crate::core::callback_state::assert_not_in_callback();
781        cast_ray_closest_impl(self.raw(), origin, translation, filter)
782    }
783
784    pub fn try_cast_ray_closest<VO: Into<Vec2>, VT: Into<Vec2>>(
785        &self,
786        origin: VO,
787        translation: VT,
788        filter: QueryFilter,
789    ) -> crate::error::ApiResult<RayResult> {
790        crate::core::callback_state::check_not_in_callback()?;
791        Ok(cast_ray_closest_impl(
792            self.raw(),
793            origin,
794            translation,
795            filter,
796        ))
797    }
798
799    /// Cast a ray and collect all hits along the path.
800    ///
801    /// Example
802    /// ```no_run
803    /// use boxdd::{World, WorldDef, QueryFilter, Vec2};
804    /// let mut world = World::new(WorldDef::builder().gravity([0.0,-9.8]).build()).unwrap();
805    /// let hits = world.cast_ray_all(Vec2::new(0.0, 5.0), Vec2::new(0.0, -10.0), QueryFilter::default());
806    /// for h in hits { let _ = (h.point, h.normal, h.fraction); }
807    /// ```
808    pub fn cast_ray_all<VO: Into<Vec2>, VT: Into<Vec2>>(
809        &self,
810        origin: VO,
811        translation: VT,
812        filter: QueryFilter,
813    ) -> Vec<RayResult> {
814        crate::core::callback_state::assert_not_in_callback();
815        cast_ray_all_impl(self.raw(), origin, translation, filter)
816    }
817
818    pub fn try_cast_ray_all<VO: Into<Vec2>, VT: Into<Vec2>>(
819        &self,
820        origin: VO,
821        translation: VT,
822        filter: QueryFilter,
823    ) -> crate::error::ApiResult<Vec<RayResult>> {
824        crate::core::callback_state::check_not_in_callback()?;
825        Ok(cast_ray_all_impl(self.raw(), origin, translation, filter))
826    }
827
828    /// Overlap polygon points (creates a temporary shape proxy from given points + radius) and collect all shape ids.
829    ///
830    /// Example
831    /// ```no_run
832    /// use boxdd::{World, WorldDef, QueryFilter, Vec2};
833    /// let mut world = World::new(WorldDef::builder().gravity([0.0,-9.8]).build()).unwrap();
834    /// let square = [Vec2::new(-0.5, -0.5), Vec2::new(0.5, -0.5), Vec2::new(0.5, 0.5), Vec2::new(-0.5, 0.5)];
835    /// let hits = world.overlap_polygon_points(square, 0.0, QueryFilter::default());
836    /// let _ = hits;
837    /// ```
838    pub fn overlap_polygon_points<I, P>(
839        &self,
840        points: I,
841        radius: f32,
842        filter: QueryFilter,
843    ) -> Vec<ShapeId>
844    where
845        I: IntoIterator<Item = P>,
846        P: Into<Vec2>,
847    {
848        crate::core::callback_state::assert_not_in_callback();
849        overlap_polygon_points_impl(self.raw(), points, radius, filter)
850    }
851
852    pub fn try_overlap_polygon_points<I, P>(
853        &self,
854        points: I,
855        radius: f32,
856        filter: QueryFilter,
857    ) -> crate::error::ApiResult<Vec<ShapeId>>
858    where
859        I: IntoIterator<Item = P>,
860        P: Into<Vec2>,
861    {
862        crate::core::callback_state::check_not_in_callback()?;
863        Ok(overlap_polygon_points_impl(
864            self.raw(),
865            points,
866            radius,
867            filter,
868        ))
869    }
870
871    /// Cast a polygon proxy and collect hits. Returns all intersections with fraction and contact info.
872    ///
873    /// Example
874    /// ```no_run
875    /// use boxdd::{World, WorldDef, QueryFilter, Vec2};
876    /// let mut world = World::new(WorldDef::builder().gravity([0.0,-9.8]).build()).unwrap();
877    /// let tri = [Vec2::new(0.0, 0.0), Vec2::new(0.5, 0.0), Vec2::new(0.25, 0.5)];
878    /// let hits = world.cast_shape_points(tri, 0.0, Vec2::new(0.0, -1.0), QueryFilter::default());
879    /// for h in hits { let _ = (h.point, h.normal, h.fraction); }
880    /// ```
881    pub fn cast_shape_points<I, P, VT>(
882        &self,
883        points: I,
884        radius: f32,
885        translation: VT,
886        filter: QueryFilter,
887    ) -> Vec<RayResult>
888    where
889        I: IntoIterator<Item = P>,
890        P: Into<Vec2>,
891        VT: Into<Vec2>,
892    {
893        crate::core::callback_state::assert_not_in_callback();
894        cast_shape_points_impl(self.raw(), points, radius, translation, filter)
895    }
896
897    pub fn try_cast_shape_points<I, P, VT>(
898        &self,
899        points: I,
900        radius: f32,
901        translation: VT,
902        filter: QueryFilter,
903    ) -> crate::error::ApiResult<Vec<RayResult>>
904    where
905        I: IntoIterator<Item = P>,
906        P: Into<Vec2>,
907        VT: Into<Vec2>,
908    {
909        crate::core::callback_state::check_not_in_callback()?;
910        Ok(cast_shape_points_impl(
911            self.raw(),
912            points,
913            radius,
914            translation,
915            filter,
916        ))
917    }
918
919    /// Cast a capsule mover and return remaining fraction (1.0 = free, < 1.0 = hit earlier).
920    pub fn cast_mover<V1: Into<Vec2>, V2: Into<Vec2>, VT: Into<Vec2>>(
921        &self,
922        c1: V1,
923        c2: V2,
924        radius: f32,
925        translation: VT,
926        filter: QueryFilter,
927    ) -> f32 {
928        crate::core::callback_state::assert_not_in_callback();
929        cast_mover_impl(self.raw(), c1, c2, radius, translation, filter)
930    }
931
932    pub fn try_cast_mover<V1: Into<Vec2>, V2: Into<Vec2>, VT: Into<Vec2>>(
933        &self,
934        c1: V1,
935        c2: V2,
936        radius: f32,
937        translation: VT,
938        filter: QueryFilter,
939    ) -> crate::error::ApiResult<f32> {
940        crate::core::callback_state::check_not_in_callback()?;
941        Ok(cast_mover_impl(
942            self.raw(),
943            c1,
944            c2,
945            radius,
946            translation,
947            filter,
948        ))
949    }
950
951    /// Overlap polygon points with an offset transform.
952    ///
953    /// Example
954    /// ```no_run
955    /// use boxdd::{World, WorldDef, QueryFilter, Vec2};
956    /// let mut world = World::new(WorldDef::builder().gravity([0.0,-9.8]).build()).unwrap();
957    /// let rect = [Vec2::new(-0.5, -0.25), Vec2::new(0.5, -0.25), Vec2::new(0.5, 0.25), Vec2::new(-0.5, 0.25)];
958    /// let hits = world.overlap_polygon_points_with_offset(rect, 0.0, Vec2::new(0.0, 2.0), 0.0_f32, QueryFilter::default());
959    /// let _ = hits;
960    /// ```
961    pub fn overlap_polygon_points_with_offset<I, P, V, A>(
962        &self,
963        points: I,
964        radius: f32,
965        position: V,
966        angle_radians: A,
967        filter: QueryFilter,
968    ) -> Vec<ShapeId>
969    where
970        I: IntoIterator<Item = P>,
971        P: Into<Vec2>,
972        V: Into<Vec2>,
973        A: Into<f32>,
974    {
975        crate::core::callback_state::assert_not_in_callback();
976        overlap_polygon_points_with_offset_impl(
977            self.raw(),
978            points,
979            radius,
980            position,
981            angle_radians,
982            filter,
983        )
984    }
985
986    pub fn try_overlap_polygon_points_with_offset<I, P, V, A>(
987        &self,
988        points: I,
989        radius: f32,
990        position: V,
991        angle_radians: A,
992        filter: QueryFilter,
993    ) -> crate::error::ApiResult<Vec<ShapeId>>
994    where
995        I: IntoIterator<Item = P>,
996        P: Into<Vec2>,
997        V: Into<Vec2>,
998        A: Into<f32>,
999    {
1000        crate::core::callback_state::check_not_in_callback()?;
1001        Ok(overlap_polygon_points_with_offset_impl(
1002            self.raw(),
1003            points,
1004            radius,
1005            position,
1006            angle_radians,
1007            filter,
1008        ))
1009    }
1010
1011    /// Cast polygon points with an offset transform (position + angle).
1012    ///
1013    /// Example
1014    /// ```no_run
1015    /// use boxdd::{World, WorldDef, QueryFilter, Vec2};
1016    /// let mut world = World::new(WorldDef::builder().gravity([0.0,-9.8]).build()).unwrap();
1017    /// let rect = [Vec2::new(-0.5, -0.25), Vec2::new(0.5, -0.25), Vec2::new(0.5, 0.25), Vec2::new(-0.5, 0.25)];
1018    /// let hits = world.cast_shape_points_with_offset(rect, 0.0, Vec2::new(0.0, 2.0), 0.0_f32, Vec2::new(0.0, -1.0), QueryFilter::default());
1019    /// for h in hits { let _ = (h.point, h.normal, h.fraction); }
1020    /// ```
1021    pub fn cast_shape_points_with_offset<I, P, V, A, VT>(
1022        &self,
1023        points: I,
1024        radius: f32,
1025        position: V,
1026        angle_radians: A,
1027        translation: VT,
1028        filter: QueryFilter,
1029    ) -> Vec<RayResult>
1030    where
1031        I: IntoIterator<Item = P>,
1032        P: Into<Vec2>,
1033        V: Into<Vec2>,
1034        A: Into<f32>,
1035        VT: Into<Vec2>,
1036    {
1037        crate::core::callback_state::assert_not_in_callback();
1038        cast_shape_points_with_offset_impl(
1039            self.raw(),
1040            points,
1041            radius,
1042            position,
1043            angle_radians,
1044            translation,
1045            filter,
1046        )
1047    }
1048
1049    pub fn try_cast_shape_points_with_offset<I, P, V, A, VT>(
1050        &self,
1051        points: I,
1052        radius: f32,
1053        position: V,
1054        angle_radians: A,
1055        translation: VT,
1056        filter: QueryFilter,
1057    ) -> crate::error::ApiResult<Vec<RayResult>>
1058    where
1059        I: IntoIterator<Item = P>,
1060        P: Into<Vec2>,
1061        V: Into<Vec2>,
1062        A: Into<f32>,
1063        VT: Into<Vec2>,
1064    {
1065        crate::core::callback_state::check_not_in_callback()?;
1066        Ok(cast_shape_points_with_offset_impl(
1067            self.raw(),
1068            points,
1069            radius,
1070            position,
1071            angle_radians,
1072            translation,
1073            filter,
1074        ))
1075    }
1076}
1077
1078impl WorldHandle {
1079    pub fn overlap_aabb(&self, aabb: Aabb, filter: QueryFilter) -> Vec<ShapeId> {
1080        crate::core::callback_state::assert_not_in_callback();
1081        overlap_aabb_impl(self.raw(), aabb, filter)
1082    }
1083
1084    pub fn try_overlap_aabb(
1085        &self,
1086        aabb: Aabb,
1087        filter: QueryFilter,
1088    ) -> crate::error::ApiResult<Vec<ShapeId>> {
1089        crate::core::callback_state::check_not_in_callback()?;
1090        Ok(overlap_aabb_impl(self.raw(), aabb, filter))
1091    }
1092
1093    pub fn cast_ray_closest<VO: Into<Vec2>, VT: Into<Vec2>>(
1094        &self,
1095        origin: VO,
1096        translation: VT,
1097        filter: QueryFilter,
1098    ) -> RayResult {
1099        crate::core::callback_state::assert_not_in_callback();
1100        cast_ray_closest_impl(self.raw(), origin, translation, filter)
1101    }
1102
1103    pub fn try_cast_ray_closest<VO: Into<Vec2>, VT: Into<Vec2>>(
1104        &self,
1105        origin: VO,
1106        translation: VT,
1107        filter: QueryFilter,
1108    ) -> crate::error::ApiResult<RayResult> {
1109        crate::core::callback_state::check_not_in_callback()?;
1110        Ok(cast_ray_closest_impl(
1111            self.raw(),
1112            origin,
1113            translation,
1114            filter,
1115        ))
1116    }
1117
1118    pub fn cast_ray_all<VO: Into<Vec2>, VT: Into<Vec2>>(
1119        &self,
1120        origin: VO,
1121        translation: VT,
1122        filter: QueryFilter,
1123    ) -> Vec<RayResult> {
1124        crate::core::callback_state::assert_not_in_callback();
1125        cast_ray_all_impl(self.raw(), origin, translation, filter)
1126    }
1127
1128    pub fn try_cast_ray_all<VO: Into<Vec2>, VT: Into<Vec2>>(
1129        &self,
1130        origin: VO,
1131        translation: VT,
1132        filter: QueryFilter,
1133    ) -> crate::error::ApiResult<Vec<RayResult>> {
1134        crate::core::callback_state::check_not_in_callback()?;
1135        Ok(cast_ray_all_impl(self.raw(), origin, translation, filter))
1136    }
1137
1138    pub fn overlap_polygon_points<I, P>(
1139        &self,
1140        points: I,
1141        radius: f32,
1142        filter: QueryFilter,
1143    ) -> Vec<ShapeId>
1144    where
1145        I: IntoIterator<Item = P>,
1146        P: Into<Vec2>,
1147    {
1148        crate::core::callback_state::assert_not_in_callback();
1149        overlap_polygon_points_impl(self.raw(), points, radius, filter)
1150    }
1151
1152    pub fn try_overlap_polygon_points<I, P>(
1153        &self,
1154        points: I,
1155        radius: f32,
1156        filter: QueryFilter,
1157    ) -> crate::error::ApiResult<Vec<ShapeId>>
1158    where
1159        I: IntoIterator<Item = P>,
1160        P: Into<Vec2>,
1161    {
1162        crate::core::callback_state::check_not_in_callback()?;
1163        Ok(overlap_polygon_points_impl(
1164            self.raw(),
1165            points,
1166            radius,
1167            filter,
1168        ))
1169    }
1170
1171    pub fn cast_shape_points<I, P, VT>(
1172        &self,
1173        points: I,
1174        radius: f32,
1175        translation: VT,
1176        filter: QueryFilter,
1177    ) -> Vec<RayResult>
1178    where
1179        I: IntoIterator<Item = P>,
1180        P: Into<Vec2>,
1181        VT: Into<Vec2>,
1182    {
1183        crate::core::callback_state::assert_not_in_callback();
1184        cast_shape_points_impl(self.raw(), points, radius, translation, filter)
1185    }
1186
1187    pub fn try_cast_shape_points<I, P, VT>(
1188        &self,
1189        points: I,
1190        radius: f32,
1191        translation: VT,
1192        filter: QueryFilter,
1193    ) -> crate::error::ApiResult<Vec<RayResult>>
1194    where
1195        I: IntoIterator<Item = P>,
1196        P: Into<Vec2>,
1197        VT: Into<Vec2>,
1198    {
1199        crate::core::callback_state::check_not_in_callback()?;
1200        Ok(cast_shape_points_impl(
1201            self.raw(),
1202            points,
1203            radius,
1204            translation,
1205            filter,
1206        ))
1207    }
1208
1209    pub fn cast_mover<V1: Into<Vec2>, V2: Into<Vec2>, VT: Into<Vec2>>(
1210        &self,
1211        c1: V1,
1212        c2: V2,
1213        radius: f32,
1214        translation: VT,
1215        filter: QueryFilter,
1216    ) -> f32 {
1217        crate::core::callback_state::assert_not_in_callback();
1218        cast_mover_impl(self.raw(), c1, c2, radius, translation, filter)
1219    }
1220
1221    pub fn try_cast_mover<V1: Into<Vec2>, V2: Into<Vec2>, VT: Into<Vec2>>(
1222        &self,
1223        c1: V1,
1224        c2: V2,
1225        radius: f32,
1226        translation: VT,
1227        filter: QueryFilter,
1228    ) -> crate::error::ApiResult<f32> {
1229        crate::core::callback_state::check_not_in_callback()?;
1230        Ok(cast_mover_impl(
1231            self.raw(),
1232            c1,
1233            c2,
1234            radius,
1235            translation,
1236            filter,
1237        ))
1238    }
1239
1240    pub fn overlap_polygon_points_with_offset<I, P, V, A>(
1241        &self,
1242        points: I,
1243        radius: f32,
1244        position: V,
1245        angle_radians: A,
1246        filter: QueryFilter,
1247    ) -> Vec<ShapeId>
1248    where
1249        I: IntoIterator<Item = P>,
1250        P: Into<Vec2>,
1251        V: Into<Vec2>,
1252        A: Into<f32>,
1253    {
1254        crate::core::callback_state::assert_not_in_callback();
1255        overlap_polygon_points_with_offset_impl(
1256            self.raw(),
1257            points,
1258            radius,
1259            position,
1260            angle_radians,
1261            filter,
1262        )
1263    }
1264
1265    pub fn try_overlap_polygon_points_with_offset<I, P, V, A>(
1266        &self,
1267        points: I,
1268        radius: f32,
1269        position: V,
1270        angle_radians: A,
1271        filter: QueryFilter,
1272    ) -> crate::error::ApiResult<Vec<ShapeId>>
1273    where
1274        I: IntoIterator<Item = P>,
1275        P: Into<Vec2>,
1276        V: Into<Vec2>,
1277        A: Into<f32>,
1278    {
1279        crate::core::callback_state::check_not_in_callback()?;
1280        Ok(overlap_polygon_points_with_offset_impl(
1281            self.raw(),
1282            points,
1283            radius,
1284            position,
1285            angle_radians,
1286            filter,
1287        ))
1288    }
1289
1290    pub fn cast_shape_points_with_offset<I, P, V, A, VT>(
1291        &self,
1292        points: I,
1293        radius: f32,
1294        position: V,
1295        angle_radians: A,
1296        translation: VT,
1297        filter: QueryFilter,
1298    ) -> Vec<RayResult>
1299    where
1300        I: IntoIterator<Item = P>,
1301        P: Into<Vec2>,
1302        V: Into<Vec2>,
1303        A: Into<f32>,
1304        VT: Into<Vec2>,
1305    {
1306        crate::core::callback_state::assert_not_in_callback();
1307        cast_shape_points_with_offset_impl(
1308            self.raw(),
1309            points,
1310            radius,
1311            position,
1312            angle_radians,
1313            translation,
1314            filter,
1315        )
1316    }
1317
1318    pub fn try_cast_shape_points_with_offset<I, P, V, A, VT>(
1319        &self,
1320        points: I,
1321        radius: f32,
1322        position: V,
1323        angle_radians: A,
1324        translation: VT,
1325        filter: QueryFilter,
1326    ) -> crate::error::ApiResult<Vec<RayResult>>
1327    where
1328        I: IntoIterator<Item = P>,
1329        P: Into<Vec2>,
1330        V: Into<Vec2>,
1331        A: Into<f32>,
1332        VT: Into<Vec2>,
1333    {
1334        crate::core::callback_state::check_not_in_callback()?;
1335        Ok(cast_shape_points_with_offset_impl(
1336            self.raw(),
1337            points,
1338            radius,
1339            position,
1340            angle_radians,
1341            translation,
1342            filter,
1343        ))
1344    }
1345}