Skip to main content

box2d_rust/geometry/
bounds.rs

1// Shape AABB computation from geometry.c.
2//
3// AABBs are built in double and narrowed to float once. In large world mode
4// the narrowing rounds outward so the box always contains the shape, and the
5// inflation (speculative margin) folds into the double step, otherwise it
6// vanishes into a float ULP far from the origin. In float mode the rounding
7// helpers are plain casts.
8//
9// SPDX-FileCopyrightText: 2023 Erin Catto
10// SPDX-License-Identifier: MIT
11
12// In single precision the Pos coordinates are f32, so the widening `as f64`
13// promotions below mirror the C double math in both modes.
14#![allow(clippy::unnecessary_cast)]
15
16use crate::collision::{Capsule, Circle, Polygon, Segment, ShapeGeometry};
17use crate::math_functions::{
18    round_down_float, round_up_float, transform_world_point, Aabb, Vec2, WorldTransform,
19};
20
21fn compute_circle_fat_aabb(shape: &Circle, xf: WorldTransform, extra: f32) -> Aabb {
22    let c = transform_world_point(xf, shape.center);
23    let r = shape.radius as f64 + extra as f64;
24    Aabb {
25        lower_bound: Vec2 {
26            x: round_down_float(c.x as f64 - r),
27            y: round_down_float(c.y as f64 - r),
28        },
29        upper_bound: Vec2 {
30            x: round_up_float(c.x as f64 + r),
31            y: round_up_float(c.y as f64 + r),
32        },
33    }
34}
35
36fn compute_capsule_fat_aabb(shape: &Capsule, xf: WorldTransform, extra: f32) -> Aabb {
37    let v1 = transform_world_point(xf, shape.center1);
38    let v2 = transform_world_point(xf, shape.center2);
39    let r = shape.radius as f64 + extra as f64;
40    let (v1x, v1y) = (v1.x as f64, v1.y as f64);
41    let (v2x, v2y) = (v2.x as f64, v2.y as f64);
42    Aabb {
43        lower_bound: Vec2 {
44            x: round_down_float(if v1x < v2x { v1x } else { v2x } - r),
45            y: round_down_float(if v1y < v2y { v1y } else { v2y } - r),
46        },
47        upper_bound: Vec2 {
48            x: round_up_float(if v1x > v2x { v1x } else { v2x } + r),
49            y: round_up_float(if v1y > v2y { v1y } else { v2y } + r),
50        },
51    }
52}
53
54fn compute_polygon_fat_aabb(shape: &Polygon, xf: WorldTransform, extra: f32) -> Aabb {
55    debug_assert!(shape.count > 0);
56    let v = transform_world_point(xf, shape.vertices[0]);
57    let (mut lx, mut ly, mut ux, mut uy) = (v.x as f64, v.y as f64, v.x as f64, v.y as f64);
58
59    for i in 1..shape.count as usize {
60        let v = transform_world_point(xf, shape.vertices[i]);
61        let (vx, vy) = (v.x as f64, v.y as f64);
62        lx = if vx < lx { vx } else { lx };
63        ly = if vy < ly { vy } else { ly };
64        ux = if vx > ux { vx } else { ux };
65        uy = if vy > uy { vy } else { uy };
66    }
67
68    let r = shape.radius as f64 + extra as f64;
69    Aabb {
70        lower_bound: Vec2 {
71            x: round_down_float(lx - r),
72            y: round_down_float(ly - r),
73        },
74        upper_bound: Vec2 {
75            x: round_up_float(ux + r),
76            y: round_up_float(uy + r),
77        },
78    }
79}
80
81fn compute_segment_fat_aabb(shape: &Segment, xf: WorldTransform, extra: f32) -> Aabb {
82    let v1 = transform_world_point(xf, shape.point1);
83    let v2 = transform_world_point(xf, shape.point2);
84    let e = extra as f64;
85    let (v1x, v1y) = (v1.x as f64, v1.y as f64);
86    let (v2x, v2y) = (v2.x as f64, v2.y as f64);
87    Aabb {
88        lower_bound: Vec2 {
89            x: round_down_float(if v1x < v2x { v1x } else { v2x } - e),
90            y: round_down_float(if v1y < v2y { v1y } else { v2y } - e),
91        },
92        upper_bound: Vec2 {
93            x: round_up_float(if v1x > v2x { v1x } else { v2x } + e),
94            y: round_up_float(if v1y > v2y { v1y } else { v2y } + e),
95        },
96    }
97}
98
99/// Compute the bounding box of a transformed circle. (b2ComputeCircleAABB)
100pub fn compute_circle_aabb(shape: &Circle, xf: WorldTransform) -> Aabb {
101    compute_circle_fat_aabb(shape, xf, 0.0)
102}
103
104/// Compute the bounding box of a transformed capsule. (b2ComputeCapsuleAABB)
105pub fn compute_capsule_aabb(shape: &Capsule, xf: WorldTransform) -> Aabb {
106    compute_capsule_fat_aabb(shape, xf, 0.0)
107}
108
109/// Compute the bounding box of a transformed polygon. (b2ComputePolygonAABB)
110pub fn compute_polygon_aabb(shape: &Polygon, xf: WorldTransform) -> Aabb {
111    compute_polygon_fat_aabb(shape, xf, 0.0)
112}
113
114/// Compute the bounding box of a transformed line segment. (b2ComputeSegmentAABB)
115pub fn compute_segment_aabb(shape: &Segment, xf: WorldTransform) -> Aabb {
116    compute_segment_fat_aabb(shape, xf, 0.0)
117}
118
119/// Compute a fattened bounding box of a transformed shape. (b2ComputeFatShapeAABB)
120pub fn compute_fat_shape_aabb(geometry: &ShapeGeometry, xf: WorldTransform, extra: f32) -> Aabb {
121    match geometry {
122        ShapeGeometry::Capsule(capsule) => compute_capsule_fat_aabb(capsule, xf, extra),
123        ShapeGeometry::Circle(circle) => compute_circle_fat_aabb(circle, xf, extra),
124        ShapeGeometry::Polygon(polygon) => compute_polygon_fat_aabb(polygon, xf, extra),
125        ShapeGeometry::Segment(segment) => compute_segment_fat_aabb(segment, xf, extra),
126        ShapeGeometry::ChainSegment(chain_segment) => {
127            compute_segment_fat_aabb(&chain_segment.segment, xf, extra)
128        }
129    }
130}