Skip to main content

box3d_rust/shape/
query.rs

1//! Shape query dispatch: projected area, ray/shape cast, overlap, and mover collide.
2//! Port of the corresponding functions from box3d-cpp-reference/src/shape.c.
3//!
4//! SPDX-FileCopyrightText: 2025 Erin Catto
5//! SPDX-License-Identifier: MIT
6
7use super::{Shape, ShapeGeometry};
8use crate::compound::{
9    collide_mover_and_compound, overlap_compound, ray_cast_compound, shape_cast_compound,
10};
11use crate::constants::MAX_SHAPE_CAST_POINTS;
12use crate::distance::{CastOutput, ShapeProxy};
13use crate::geometry::{
14    collide_mover_and_capsule, collide_mover_and_sphere, overlap_capsule, overlap_sphere,
15    ray_cast_capsule, ray_cast_sphere, shape_cast_capsule, shape_cast_sphere, Capsule, PlaneResult,
16    RayCastInput, ShapeCastInput,
17};
18use crate::height_field::{
19    collide_mover_and_height_field, overlap_height_field, ray_cast_height_field,
20    shape_cast_height_field,
21};
22use crate::hull::{
23    collide_mover_and_hull, compute_hull_projected_area, overlap_hull, ray_cast_hull,
24    shape_cast_hull,
25};
26use crate::math_functions::{
27    cross, inv_rotate_vector, inv_transform_point, length, min_int, rotate_vector, sub,
28    transform_point, Transform, Vec3, PI,
29};
30use crate::mesh::{collide_mover_and_mesh, overlap_mesh, ray_cast_mesh, shape_cast_mesh, Mesh};
31
32/// Projected area of a shape onto a plane with the given normal.
33/// Used by explosions. (b3GetShapeProjectedArea)
34pub fn get_shape_projected_area(shape: &Shape, plane_normal: Vec3) -> f32 {
35    match &shape.geometry {
36        ShapeGeometry::Capsule(capsule) => {
37            let radius = capsule.radius;
38            let axis = sub(capsule.center2, capsule.center1);
39            let projected_length = length(cross(axis, plane_normal));
40            let cylinder_area = 2.0 * radius * projected_length;
41            let sphere_area = PI * radius * radius;
42            sphere_area + cylinder_area
43        }
44        ShapeGeometry::Hull(hull) => compute_hull_projected_area(hull, plane_normal),
45        ShapeGeometry::Sphere(sphere) => PI * sphere.radius * sphere.radius,
46        _ => 0.0,
47    }
48}
49
50/// Ray cast a shape in world (or relative) space. Transforms the ray into
51/// local space, dispatches, then transforms the hit back. (b3RayCastShape)
52pub fn ray_cast_shape(shape: &Shape, transform: Transform, input: &RayCastInput) -> CastOutput {
53    let local_input = RayCastInput {
54        origin: inv_transform_point(transform, input.origin),
55        translation: inv_rotate_vector(transform.q, input.translation),
56        max_fraction: input.max_fraction,
57    };
58
59    let mut output = match &shape.geometry {
60        ShapeGeometry::Capsule(capsule) => ray_cast_capsule(capsule, &local_input),
61        ShapeGeometry::Compound(compound) => ray_cast_compound(compound, &local_input),
62        ShapeGeometry::Sphere(sphere) => ray_cast_sphere(sphere, &local_input),
63        ShapeGeometry::Hull(hull) => ray_cast_hull(hull, &local_input),
64        ShapeGeometry::Mesh { data, scale } => {
65            let mesh = Mesh::new(data, *scale);
66            ray_cast_mesh(&mesh, &local_input)
67        }
68        ShapeGeometry::HeightField(height_field) => {
69            ray_cast_height_field(height_field, &local_input)
70        }
71    };
72
73    output.point = transform_point(transform, output.point);
74    output.normal = rotate_vector(transform.q, output.normal);
75    output
76}
77
78/// Shape cast a shape in world (or relative) space. (b3ShapeCastShape)
79pub fn shape_cast_shape(shape: &Shape, transform: Transform, input: &ShapeCastInput) -> CastOutput {
80    let mut local_points = [Vec3::default(); MAX_SHAPE_CAST_POINTS];
81    let count = min_int(input.proxy.count, MAX_SHAPE_CAST_POINTS as i32);
82    for i in 0..count {
83        local_points[i as usize] = inv_transform_point(transform, input.proxy.points[i as usize]);
84    }
85
86    let mut local_input = *input;
87    local_input.proxy.count = count;
88    local_input.proxy.points = local_points;
89    local_input.translation = inv_rotate_vector(transform.q, input.translation);
90
91    let mut output = match &shape.geometry {
92        ShapeGeometry::Capsule(capsule) => shape_cast_capsule(capsule, &local_input),
93        ShapeGeometry::Compound(compound) => shape_cast_compound(compound, &local_input),
94        ShapeGeometry::HeightField(height_field) => {
95            shape_cast_height_field(height_field, &local_input)
96        }
97        ShapeGeometry::Hull(hull) => shape_cast_hull(hull, &local_input),
98        ShapeGeometry::Mesh { data, scale } => {
99            let mesh = Mesh::new(data, *scale);
100            shape_cast_mesh(&mesh, &local_input)
101        }
102        ShapeGeometry::Sphere(sphere) => shape_cast_sphere(sphere, &local_input),
103    };
104
105    output.point = transform_point(transform, output.point);
106    output.normal = rotate_vector(transform.q, output.normal);
107    output
108}
109
110/// Test overlap between a shape and a shape proxy. (b3OverlapShape)
111pub fn overlap_shape(shape: &Shape, transform: Transform, proxy: &ShapeProxy) -> bool {
112    match &shape.geometry {
113        ShapeGeometry::Capsule(capsule) => overlap_capsule(capsule, transform, proxy),
114        ShapeGeometry::Compound(compound) => overlap_compound(compound, transform, proxy),
115        ShapeGeometry::HeightField(height_field) => {
116            overlap_height_field(height_field, transform, proxy)
117        }
118        ShapeGeometry::Hull(hull) => overlap_hull(hull, transform, proxy),
119        ShapeGeometry::Mesh { data, scale } => {
120            let mesh = Mesh::new(data, *scale);
121            overlap_mesh(&mesh, transform, proxy)
122        }
123        ShapeGeometry::Sphere(sphere) => overlap_sphere(sphere, transform, proxy),
124    }
125}
126
127/// Collide a capsule mover with a shape, writing contact planes into `planes`.
128/// Transforms the mover into local space, dispatches, then rotates results back.
129/// (b3CollideMover)
130pub fn collide_mover(
131    planes: &mut [PlaneResult],
132    shape: &Shape,
133    transform: Transform,
134    mover: &Capsule,
135) -> i32 {
136    let plane_capacity = planes.len() as i32;
137    if plane_capacity == 0 {
138        return 0;
139    }
140
141    let local_mover = Capsule {
142        center1: inv_transform_point(transform, mover.center1),
143        center2: inv_transform_point(transform, mover.center2),
144        radius: mover.radius,
145    };
146
147    let plane_count = match &shape.geometry {
148        ShapeGeometry::Capsule(capsule) => {
149            collide_mover_and_capsule(&mut planes[0], capsule, &local_mover)
150        }
151        ShapeGeometry::Compound(compound) => {
152            collide_mover_and_compound(planes, compound, &local_mover)
153        }
154        ShapeGeometry::Sphere(sphere) => {
155            collide_mover_and_sphere(&mut planes[0], sphere, &local_mover)
156        }
157        ShapeGeometry::Hull(hull) => collide_mover_and_hull(&mut planes[0], hull, &local_mover),
158        ShapeGeometry::Mesh { data, scale } => {
159            let mesh = Mesh::new(data, *scale);
160            collide_mover_and_mesh(planes, &mesh, &local_mover)
161        }
162        ShapeGeometry::HeightField(height_field) => {
163            collide_mover_and_height_field(planes, height_field, &local_mover)
164        }
165    };
166
167    for i in 0..plane_count {
168        planes[i as usize].plane.normal =
169            rotate_vector(transform.q, planes[i as usize].plane.normal);
170        planes[i as usize].point = transform_point(transform, planes[i as usize].point);
171    }
172
173    plane_count
174}