Skip to main content

box2d_rust/geometry/
shapes.rs

1// Polygon constructors and transforms from geometry.c.
2// SPDX-FileCopyrightText: 2023 Erin Catto
3// SPDX-License-Identifier: MIT
4
5use crate::collision::Polygon;
6use crate::hull::{validate_hull, Hull};
7use crate::math_functions::{
8    add, cross, cross_vs, dot, is_valid_float, mul_add, normalize, rotate_vector, sub,
9    transform_point, Rot, Transform, Vec2, VEC2_ZERO,
10};
11
12pub(crate) fn compute_polygon_centroid(vertices: &[Vec2]) -> Vec2 {
13    let mut center = VEC2_ZERO;
14    let mut area = 0.0f32;
15
16    // Get a reference point for forming triangles.
17    // Use the first vertex to reduce round-off errors.
18    let origin = vertices[0];
19
20    let inv3 = 1.0 / 3.0;
21
22    let count = vertices.len();
23    for i in 1..count - 1 {
24        // Triangle edges
25        let e1 = sub(vertices[i], origin);
26        let e2 = sub(vertices[i + 1], origin);
27        let a = 0.5 * cross(e1, e2);
28
29        // Area weighted centroid
30        center = mul_add(center, a * inv3, add(e1, e2));
31        area += a;
32    }
33
34    debug_assert!(area > f32::EPSILON);
35    let inv_area = 1.0 / area;
36    center.x *= inv_area;
37    center.y *= inv_area;
38
39    // Restore offset
40    add(origin, center)
41}
42
43/// Make a convex polygon from a convex hull. This will assert if the hull is
44/// not valid. (b2MakePolygon)
45///
46/// @warning Do not manually fill in the hull data, it must come directly from
47/// `compute_hull`.
48pub fn make_polygon(hull: &Hull, radius: f32) -> Polygon {
49    debug_assert!(validate_hull(hull));
50
51    if hull.count < 3 {
52        // Handle a bad hull when assertions are disabled
53        return make_square(0.5);
54    }
55
56    let mut shape = Polygon {
57        count: hull.count,
58        radius,
59        ..Default::default()
60    };
61
62    // Copy vertices
63    for i in 0..shape.count as usize {
64        shape.vertices[i] = hull.points[i];
65    }
66
67    // Compute normals. Ensure the edges have non-zero length.
68    for i in 0..shape.count as usize {
69        let i1 = i;
70        let i2 = if i + 1 < shape.count as usize {
71            i + 1
72        } else {
73            0
74        };
75        let edge = sub(shape.vertices[i2], shape.vertices[i1]);
76        debug_assert!(dot(edge, edge) > f32::EPSILON * f32::EPSILON);
77        shape.normals[i] = normalize(cross_vs(edge, 1.0));
78    }
79
80    shape.centroid = compute_polygon_centroid(&shape.vertices[..shape.count as usize]);
81
82    shape
83}
84
85/// Make an offset convex polygon from a convex hull. This will assert if the
86/// hull is not valid. (b2MakeOffsetPolygon)
87pub fn make_offset_polygon(hull: &Hull, position: Vec2, rotation: Rot) -> Polygon {
88    make_offset_rounded_polygon(hull, position, rotation, 0.0)
89}
90
91/// Make an offset rounded convex polygon from a convex hull. This will assert
92/// if the hull is not valid. (b2MakeOffsetRoundedPolygon)
93pub fn make_offset_rounded_polygon(
94    hull: &Hull,
95    position: Vec2,
96    rotation: Rot,
97    radius: f32,
98) -> Polygon {
99    debug_assert!(validate_hull(hull));
100
101    if hull.count < 3 {
102        // Handle a bad hull when assertions are disabled
103        return make_square(0.5);
104    }
105
106    let transform = Transform {
107        p: position,
108        q: rotation,
109    };
110
111    let mut shape = Polygon {
112        count: hull.count,
113        radius,
114        ..Default::default()
115    };
116
117    // Copy vertices
118    for i in 0..shape.count as usize {
119        shape.vertices[i] = transform_point(transform, hull.points[i]);
120    }
121
122    // Compute normals. Ensure the edges have non-zero length.
123    for i in 0..shape.count as usize {
124        let i1 = i;
125        let i2 = if i + 1 < shape.count as usize {
126            i + 1
127        } else {
128            0
129        };
130        let edge = sub(shape.vertices[i2], shape.vertices[i1]);
131        debug_assert!(dot(edge, edge) > f32::EPSILON * f32::EPSILON);
132        shape.normals[i] = normalize(cross_vs(edge, 1.0));
133    }
134
135    shape.centroid = compute_polygon_centroid(&shape.vertices[..shape.count as usize]);
136
137    shape
138}
139
140/// Make a square polygon, bypassing the need for a convex hull. (b2MakeSquare)
141pub fn make_square(half_width: f32) -> Polygon {
142    make_box(half_width, half_width)
143}
144
145/// Make a box (rectangle) polygon, bypassing the need for a convex hull.
146/// (b2MakeBox)
147pub fn make_box(half_width: f32, half_height: f32) -> Polygon {
148    debug_assert!(is_valid_float(half_width) && half_width > 0.0);
149    debug_assert!(is_valid_float(half_height) && half_height > 0.0);
150
151    let mut shape = Polygon {
152        count: 4,
153        ..Default::default()
154    };
155    shape.vertices[0] = Vec2 {
156        x: -half_width,
157        y: -half_height,
158    };
159    shape.vertices[1] = Vec2 {
160        x: half_width,
161        y: -half_height,
162    };
163    shape.vertices[2] = Vec2 {
164        x: half_width,
165        y: half_height,
166    };
167    shape.vertices[3] = Vec2 {
168        x: -half_width,
169        y: half_height,
170    };
171    shape.normals[0] = Vec2 { x: 0.0, y: -1.0 };
172    shape.normals[1] = Vec2 { x: 1.0, y: 0.0 };
173    shape.normals[2] = Vec2 { x: 0.0, y: 1.0 };
174    shape.normals[3] = Vec2 { x: -1.0, y: 0.0 };
175    shape.radius = 0.0;
176    shape.centroid = VEC2_ZERO;
177    shape
178}
179
180/// Make a rounded box, bypassing the need for a convex hull. (b2MakeRoundedBox)
181pub fn make_rounded_box(half_width: f32, half_height: f32, radius: f32) -> Polygon {
182    debug_assert!(is_valid_float(radius) && radius >= 0.0);
183    let mut shape = make_box(half_width, half_height);
184    shape.radius = radius;
185    shape
186}
187
188/// Make an offset box, bypassing the need for a convex hull. (b2MakeOffsetBox)
189pub fn make_offset_box(half_width: f32, half_height: f32, center: Vec2, rotation: Rot) -> Polygon {
190    let xf = Transform {
191        p: center,
192        q: rotation,
193    };
194
195    let mut shape = Polygon {
196        count: 4,
197        ..Default::default()
198    };
199    shape.vertices[0] = transform_point(
200        xf,
201        Vec2 {
202            x: -half_width,
203            y: -half_height,
204        },
205    );
206    shape.vertices[1] = transform_point(
207        xf,
208        Vec2 {
209            x: half_width,
210            y: -half_height,
211        },
212    );
213    shape.vertices[2] = transform_point(
214        xf,
215        Vec2 {
216            x: half_width,
217            y: half_height,
218        },
219    );
220    shape.vertices[3] = transform_point(
221        xf,
222        Vec2 {
223            x: -half_width,
224            y: half_height,
225        },
226    );
227    shape.normals[0] = rotate_vector(xf.q, Vec2 { x: 0.0, y: -1.0 });
228    shape.normals[1] = rotate_vector(xf.q, Vec2 { x: 1.0, y: 0.0 });
229    shape.normals[2] = rotate_vector(xf.q, Vec2 { x: 0.0, y: 1.0 });
230    shape.normals[3] = rotate_vector(xf.q, Vec2 { x: -1.0, y: 0.0 });
231    shape.radius = 0.0;
232    shape.centroid = xf.p;
233    shape
234}
235
236/// Make an offset rounded box, bypassing the need for a convex hull.
237/// (b2MakeOffsetRoundedBox)
238pub fn make_offset_rounded_box(
239    half_width: f32,
240    half_height: f32,
241    center: Vec2,
242    rotation: Rot,
243    radius: f32,
244) -> Polygon {
245    debug_assert!(is_valid_float(radius) && radius >= 0.0);
246    let mut shape = make_offset_box(half_width, half_height, center, rotation);
247    shape.radius = radius;
248    shape
249}
250
251/// Transform a polygon. This is useful for transferring a shape from one body
252/// to another. (b2TransformPolygon)
253pub fn transform_polygon(transform: Transform, polygon: &Polygon) -> Polygon {
254    let mut p = *polygon;
255
256    for i in 0..p.count as usize {
257        p.vertices[i] = transform_point(transform, p.vertices[i]);
258        p.normals[i] = rotate_vector(transform.q, p.normals[i]);
259    }
260
261    p.centroid = transform_point(transform, p.centroid);
262
263    p
264}