Skip to main content

box2d_rust/shape/
dispatch.rs

1// Per-shape-type dispatch helpers from shape.c.
2// SPDX-FileCopyrightText: 2023 Erin Catto
3// SPDX-License-Identifier: MIT
4
5use super::{Shape, ShapeExtent};
6use crate::broad_phase::{proxy_type, BroadPhase};
7use crate::collision::ShapeGeometry;
8use crate::collision::{CastOutput, MassData, PlaneResult, RayCastInput, ShapeCastInput};
9use crate::constants::speculative_distance;
10use crate::core::NULL_INDEX;
11use crate::distance::{make_proxy, ShapeProxy};
12use crate::geometry::{
13    collide_mover_and_capsule, collide_mover_and_circle, collide_mover_and_polygon,
14    collide_mover_and_segment, compute_capsule_aabb, compute_capsule_mass, compute_circle_aabb,
15    compute_circle_mass, compute_fat_shape_aabb, compute_polygon_aabb, compute_polygon_mass,
16    compute_segment_aabb, ray_cast_capsule, ray_cast_circle, ray_cast_polygon, ray_cast_segment,
17    shape_cast_capsule, shape_cast_circle, shape_cast_polygon, shape_cast_segment,
18};
19use crate::math_functions::{
20    abs_float, add, cross, dot, inv_rotate_vector, inv_transform_point, length, length_squared,
21    lerp, max_float, min_float, mul_sv, rotate_vector, sub, transform_point, Aabb, Transform, Vec2,
22    WorldTransform, PI,
23};
24use crate::types::BodyType;
25
26/// Compute the shape AABB and fat AABB with speculative and movement margins.
27/// (static b2UpdateShapeAABBs)
28pub(crate) fn update_shape_aabbs(
29    shape: &mut Shape,
30    transform: WorldTransform,
31    proxy_type_: BodyType,
32) {
33    // Compute a bounding box with a speculative margin
34    let speculative = speculative_distance();
35    let aabb_margin = shape.aabb_margin;
36
37    let aabb = compute_fat_shape_aabb(&shape.geometry, transform, speculative);
38    shape.aabb = aabb;
39
40    // Smaller margin for static bodies. Cannot be zero due to TOI tolerance.
41    let margin = if proxy_type_ == BodyType::Static {
42        speculative
43    } else {
44        aabb_margin
45    };
46    shape.fat_aabb = Aabb {
47        lower_bound: Vec2 {
48            x: aabb.lower_bound.x - margin,
49            y: aabb.lower_bound.y - margin,
50        },
51        upper_bound: Vec2 {
52            x: aabb.upper_bound.x + margin,
53            y: aabb.upper_bound.y + margin,
54        },
55    };
56}
57
58/// (b2CreateShapeProxy)
59pub fn create_shape_proxy(
60    shape: &mut Shape,
61    bp: &mut BroadPhase,
62    type_: BodyType,
63    transform: WorldTransform,
64    force_pair_creation: bool,
65) {
66    debug_assert!(shape.proxy_key == NULL_INDEX);
67
68    update_shape_aabbs(shape, transform, type_);
69
70    // Create proxies in the broad-phase.
71    shape.proxy_key = bp.create_proxy(
72        type_,
73        shape.fat_aabb,
74        shape.filter.category_bits,
75        shape.id,
76        force_pair_creation,
77    );
78    debug_assert!((proxy_type(shape.proxy_key) as usize) < crate::types::BODY_TYPE_COUNT);
79}
80
81/// (b2DestroyShapeProxy)
82pub fn destroy_shape_proxy(shape: &mut Shape, bp: &mut BroadPhase) {
83    if shape.proxy_key != NULL_INDEX {
84        bp.destroy_proxy(shape.proxy_key);
85        shape.proxy_key = NULL_INDEX;
86    }
87}
88
89/// (b2ComputeShapeMass)
90pub fn compute_shape_mass(shape: &Shape) -> MassData {
91    match &shape.geometry {
92        ShapeGeometry::Capsule(capsule) => compute_capsule_mass(capsule, shape.density),
93        ShapeGeometry::Circle(circle) => compute_circle_mass(circle, shape.density),
94        ShapeGeometry::Polygon(polygon) => compute_polygon_mass(polygon, shape.density),
95        _ => MassData::default(),
96    }
97}
98
99/// (b2ComputeShapeExtent)
100pub fn compute_shape_extent(shape: &Shape, local_center: Vec2) -> ShapeExtent {
101    let mut extent = ShapeExtent::default();
102
103    match &shape.geometry {
104        ShapeGeometry::Capsule(capsule) => {
105            let radius = capsule.radius;
106            extent.min_extent = radius;
107            let c1 = sub(capsule.center1, local_center);
108            let c2 = sub(capsule.center2, local_center);
109            extent.max_extent = max_float(length_squared(c1), length_squared(c2)).sqrt() + radius;
110        }
111
112        ShapeGeometry::Circle(circle) => {
113            let radius = circle.radius;
114            extent.min_extent = radius;
115            extent.max_extent = length(sub(circle.center, local_center)) + radius;
116        }
117
118        ShapeGeometry::Polygon(poly) => {
119            let mut min_extent = crate::constants::huge();
120            let mut max_extent_sqr = 0.0f32;
121            let count = poly.count as usize;
122            for i in 0..count {
123                let v = poly.vertices[i];
124                let plane_offset = dot(poly.normals[i], sub(v, poly.centroid));
125                min_extent = min_float(min_extent, plane_offset);
126
127                let distance_sqr = length_squared(sub(v, local_center));
128                max_extent_sqr = max_float(max_extent_sqr, distance_sqr);
129            }
130
131            extent.min_extent = min_extent + poly.radius;
132            extent.max_extent = max_extent_sqr.sqrt() + poly.radius;
133        }
134
135        ShapeGeometry::Segment(segment) => {
136            extent.min_extent = 0.0;
137            let c1 = sub(segment.point1, local_center);
138            let c2 = sub(segment.point2, local_center);
139            extent.max_extent = max_float(length_squared(c1), length_squared(c2)).sqrt();
140        }
141
142        ShapeGeometry::ChainSegment(chain_segment) => {
143            extent.min_extent = 0.0;
144            let c1 = sub(chain_segment.segment.point1, local_center);
145            let c2 = sub(chain_segment.segment.point2, local_center);
146            extent.max_extent = max_float(length_squared(c1), length_squared(c2)).sqrt();
147        }
148    }
149
150    extent
151}
152
153/// (b2ComputeShapeAABB)
154pub fn compute_shape_aabb(shape: &Shape, xf: WorldTransform) -> Aabb {
155    match &shape.geometry {
156        ShapeGeometry::Capsule(capsule) => compute_capsule_aabb(capsule, xf),
157        ShapeGeometry::Circle(circle) => compute_circle_aabb(circle, xf),
158        ShapeGeometry::Polygon(polygon) => compute_polygon_aabb(polygon, xf),
159        ShapeGeometry::Segment(segment) => compute_segment_aabb(segment, xf),
160        ShapeGeometry::ChainSegment(chain_segment) => {
161            compute_segment_aabb(&chain_segment.segment, xf)
162        }
163    }
164}
165
166/// (b2GetShapeCentroid)
167pub fn get_shape_centroid(shape: &Shape) -> Vec2 {
168    match &shape.geometry {
169        ShapeGeometry::Capsule(capsule) => lerp(capsule.center1, capsule.center2, 0.5),
170        ShapeGeometry::Circle(circle) => circle.center,
171        ShapeGeometry::Polygon(polygon) => polygon.centroid,
172        ShapeGeometry::Segment(segment) => lerp(segment.point1, segment.point2, 0.5),
173        ShapeGeometry::ChainSegment(chain_segment) => lerp(
174            chain_segment.segment.point1,
175            chain_segment.segment.point2,
176            0.5,
177        ),
178    }
179}
180
181/// (b2GetShapePerimeter)
182pub fn get_shape_perimeter(shape: &Shape) -> f32 {
183    match &shape.geometry {
184        ShapeGeometry::Capsule(capsule) => {
185            2.0 * length(sub(capsule.center1, capsule.center2)) + 2.0 * PI * capsule.radius
186        }
187        ShapeGeometry::Circle(circle) => 2.0 * PI * circle.radius,
188        ShapeGeometry::Polygon(polygon) => {
189            let points = &polygon.vertices;
190            let count = polygon.count as usize;
191            let mut perimeter = 2.0 * PI * polygon.radius;
192            debug_assert!(count > 0);
193            let mut prev = points[count - 1];
194            for &next in points.iter().take(count) {
195                perimeter += length(sub(next, prev));
196                prev = next;
197            }
198
199            perimeter
200        }
201        ShapeGeometry::Segment(segment) => 2.0 * length(sub(segment.point1, segment.point2)),
202        ShapeGeometry::ChainSegment(chain_segment) => {
203            2.0 * length(sub(
204                chain_segment.segment.point1,
205                chain_segment.segment.point2,
206            ))
207        }
208    }
209}
210
211/// This projects the shape perimeter onto an infinite line.
212/// (b2GetShapeProjectedPerimeter)
213pub fn get_shape_projected_perimeter(shape: &Shape, line: Vec2) -> f32 {
214    match &shape.geometry {
215        ShapeGeometry::Capsule(capsule) => {
216            let axis = sub(capsule.center2, capsule.center1);
217            let projected_length = abs_float(dot(axis, line));
218            projected_length + 2.0 * capsule.radius
219        }
220
221        ShapeGeometry::Circle(circle) => 2.0 * circle.radius,
222
223        ShapeGeometry::Polygon(polygon) => {
224            let points = &polygon.vertices;
225            let count = polygon.count as usize;
226            debug_assert!(count > 0);
227            let value = dot(points[0], line);
228            let mut lower = value;
229            let mut upper = value;
230            for point in points.iter().take(count).skip(1) {
231                let value = dot(*point, line);
232                lower = min_float(lower, value);
233                upper = max_float(upper, value);
234            }
235
236            (upper - lower) + 2.0 * polygon.radius
237        }
238
239        ShapeGeometry::Segment(segment) => {
240            let value1 = dot(segment.point1, line);
241            let value2 = dot(segment.point2, line);
242            abs_float(value2 - value1)
243        }
244
245        ShapeGeometry::ChainSegment(chain_segment) => {
246            let value1 = dot(chain_segment.segment.point1, line);
247            let value2 = dot(chain_segment.segment.point2, line);
248            abs_float(value2 - value1)
249        }
250    }
251}
252
253/// (b2MakeShapeDistanceProxy)
254pub fn make_shape_distance_proxy(shape: &Shape) -> ShapeProxy {
255    match &shape.geometry {
256        ShapeGeometry::Capsule(capsule) => {
257            make_proxy(&[capsule.center1, capsule.center2], capsule.radius)
258        }
259        ShapeGeometry::Circle(circle) => make_proxy(&[circle.center], circle.radius),
260        ShapeGeometry::Polygon(polygon) => {
261            make_proxy(&polygon.vertices[..polygon.count as usize], polygon.radius)
262        }
263        ShapeGeometry::Segment(segment) => make_proxy(&[segment.point1, segment.point2], 0.0),
264        ShapeGeometry::ChainSegment(chain_segment) => make_proxy(
265            &[chain_segment.segment.point1, chain_segment.segment.point2],
266            0.0,
267        ),
268    }
269}
270
271/// (b2RayCastShape)
272pub fn ray_cast_shape(input: &RayCastInput, shape: &Shape, transform: Transform) -> CastOutput {
273    let local_input = RayCastInput {
274        origin: inv_transform_point(transform, input.origin),
275        translation: inv_rotate_vector(transform.q, input.translation),
276        max_fraction: input.max_fraction,
277    };
278
279    let mut output = match &shape.geometry {
280        ShapeGeometry::Capsule(capsule) => ray_cast_capsule(capsule, &local_input),
281        ShapeGeometry::Circle(circle) => ray_cast_circle(circle, &local_input),
282        ShapeGeometry::Polygon(polygon) => ray_cast_polygon(polygon, &local_input),
283        ShapeGeometry::Segment(segment) => ray_cast_segment(segment, &local_input, false),
284        ShapeGeometry::ChainSegment(chain_segment) => {
285            ray_cast_segment(&chain_segment.segment, &local_input, true)
286        }
287    };
288
289    // The output point stays in the frame of the input transform, a caller
290    // chosen frame that is typically re-centered near the origin.
291    output.point = transform_point(transform, output.point);
292    output.normal = rotate_vector(transform.q, output.normal);
293    output
294}
295
296/// (b2ShapeCastShape)
297pub fn shape_cast_shape(input: &ShapeCastInput, shape: &Shape, transform: Transform) -> CastOutput {
298    let mut output = CastOutput::default();
299
300    if input.proxy.count == 0 {
301        return output;
302    }
303
304    let mut local_input = *input;
305
306    for i in 0..local_input.proxy.count as usize {
307        local_input.proxy.points[i] = inv_transform_point(transform, input.proxy.points[i]);
308    }
309
310    local_input.translation = inv_rotate_vector(transform.q, input.translation);
311
312    match &shape.geometry {
313        ShapeGeometry::Capsule(capsule) => {
314            output = shape_cast_capsule(capsule, &local_input);
315        }
316        ShapeGeometry::Circle(circle) => {
317            output = shape_cast_circle(circle, &local_input);
318        }
319        ShapeGeometry::Polygon(polygon) => {
320            output = shape_cast_polygon(polygon, &local_input);
321        }
322        ShapeGeometry::Segment(segment) => {
323            output = shape_cast_segment(segment, &local_input);
324        }
325        ShapeGeometry::ChainSegment(chain_segment) => {
326            // Check for back side collision
327            let mut approximate_centroid = local_input.proxy.points[0];
328            for i in 1..local_input.proxy.count as usize {
329                approximate_centroid = add(approximate_centroid, local_input.proxy.points[i]);
330            }
331
332            approximate_centroid =
333                mul_sv(1.0 / local_input.proxy.count as f32, approximate_centroid);
334
335            let edge = sub(chain_segment.segment.point2, chain_segment.segment.point1);
336            let r = sub(approximate_centroid, chain_segment.segment.point1);
337
338            if cross(r, edge) < 0.0 {
339                // Shape cast starts behind
340                return output;
341            }
342
343            output = shape_cast_segment(&chain_segment.segment, &local_input);
344        }
345    }
346
347    // Same frame contract as ray_cast_shape, the point stays in the input
348    // transform frame
349    output.point = transform_point(transform, output.point);
350    output.normal = rotate_vector(transform.q, output.normal);
351    output
352}
353
354/// (b2CollideMover)
355pub fn collide_mover(
356    mover: &crate::collision::Capsule,
357    shape: &Shape,
358    transform: Transform,
359) -> PlaneResult {
360    let local_mover = crate::collision::Capsule {
361        center1: inv_transform_point(transform, mover.center1),
362        center2: inv_transform_point(transform, mover.center2),
363        radius: mover.radius,
364    };
365
366    let mut result = match &shape.geometry {
367        ShapeGeometry::Capsule(capsule) => collide_mover_and_capsule(&local_mover, capsule),
368        ShapeGeometry::Circle(circle) => collide_mover_and_circle(&local_mover, circle),
369        ShapeGeometry::Polygon(polygon) => collide_mover_and_polygon(&local_mover, polygon),
370        ShapeGeometry::Segment(segment) => collide_mover_and_segment(&local_mover, segment),
371        ShapeGeometry::ChainSegment(chain_segment) => {
372            collide_mover_and_segment(&local_mover, &chain_segment.segment)
373        }
374    };
375
376    if !result.hit {
377        return result;
378    }
379
380    result.plane.normal = rotate_vector(transform.q, result.plane.normal);
381    result
382}