1use crate::collision::{Circle, ShapeGeometry, ShapeType};
14use crate::core::NULL_INDEX;
15use crate::math_functions::{Aabb, Vec2, VEC2_ZERO};
16use crate::types::{Filter, QueryFilter, SurfaceMaterial};
17
18#[derive(Debug, Clone)]
24pub struct Shape {
25 pub id: i32,
26 pub body_id: i32,
27 pub prev_shape_id: i32,
28 pub next_shape_id: i32,
29 pub sensor_index: i32,
30 pub material: SurfaceMaterial,
31 pub density: f32,
32 pub aabb_margin: f32,
33 pub aabb: Aabb,
34 pub fat_aabb: Aabb,
35 pub local_centroid: Vec2,
36 pub proxy_key: i32,
37
38 pub filter: Filter,
39 pub user_data: u64,
40
41 pub geometry: ShapeGeometry,
43
44 pub generation: u16,
45 pub enable_sensor_events: bool,
46 pub enable_contact_events: bool,
47 pub enable_custom_filtering: bool,
48 pub enable_hit_events: bool,
49 pub enable_pre_solve_events: bool,
50 pub enlarged_aabb: bool,
51}
52
53impl Shape {
54 pub fn shape_type(&self) -> ShapeType {
56 self.geometry.shape_type()
57 }
58
59 pub fn radius(&self) -> f32 {
61 match &self.geometry {
62 ShapeGeometry::Capsule(capsule) => capsule.radius,
63 ShapeGeometry::Circle(circle) => circle.radius,
64 ShapeGeometry::Polygon(polygon) => polygon.radius,
65 _ => 0.0,
66 }
67 }
68}
69
70impl Default for Shape {
71 fn default() -> Self {
72 Shape {
73 id: NULL_INDEX,
74 body_id: NULL_INDEX,
75 prev_shape_id: NULL_INDEX,
76 next_shape_id: NULL_INDEX,
77 sensor_index: NULL_INDEX,
78 material: SurfaceMaterial::default(),
79 density: 0.0,
80 aabb_margin: 0.0,
81 aabb: Aabb::default(),
82 fat_aabb: Aabb::default(),
83 local_centroid: VEC2_ZERO,
84 proxy_key: NULL_INDEX,
85 filter: Filter::default(),
86 user_data: 0,
87 geometry: ShapeGeometry::Circle(Circle::default()),
88 generation: 0,
89 enable_sensor_events: false,
90 enable_contact_events: false,
91 enable_custom_filtering: false,
92 enable_hit_events: false,
93 enable_pre_solve_events: false,
94 enlarged_aabb: false,
95 }
96 }
97}
98
99#[derive(Debug, Clone, Default)]
104pub struct ChainShape {
105 pub id: i32,
106 pub body_id: i32,
107 pub next_chain_id: i32,
108 pub shape_indices: Vec<i32>,
109 pub materials: Vec<SurfaceMaterial>,
110 pub generation: u16,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Default)]
115pub struct ShapeExtent {
116 pub min_extent: f32,
117 pub max_extent: f32,
118}
119
120pub fn should_shapes_collide(filter_a: Filter, filter_b: Filter) -> bool {
122 if filter_a.group_index == filter_b.group_index && filter_a.group_index != 0 {
123 return filter_a.group_index > 0;
124 }
125
126 (filter_a.mask_bits & filter_b.category_bits) != 0
127 && (filter_a.category_bits & filter_b.mask_bits) != 0
128}
129
130pub fn should_query_collide(shape_filter: Filter, query_filter: QueryFilter) -> bool {
132 (shape_filter.category_bits & query_filter.mask_bits) != 0
133 && (shape_filter.mask_bits & query_filter.category_bits) != 0
134}
135
136mod dispatch;
137mod lifecycle;
138
139pub use dispatch::*;
140pub use lifecycle::*;
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::types::default_filter;
146
147 #[test]
148 fn filter_logic() {
149 let mut a = default_filter();
151 let mut b = default_filter();
152 a.group_index = 3;
153 b.group_index = 3;
154 assert!(should_shapes_collide(a, b));
155 a.group_index = -3;
156 b.group_index = -3;
157 assert!(!should_shapes_collide(a, b));
158
159 a.group_index = 0;
161 b.group_index = 0;
162 a.category_bits = 0x2;
163 a.mask_bits = 0x4;
164 b.category_bits = 0x4;
165 b.mask_bits = 0x2;
166 assert!(should_shapes_collide(a, b));
167 b.mask_bits = 0x8;
168 assert!(!should_shapes_collide(a, b));
169
170 let shape_filter = default_filter();
172 let query = crate::types::default_query_filter();
173 assert!(should_query_collide(shape_filter, query));
174 }
175
176 #[test]
177 fn shape_dispatch_matches_geometry() {
178 use crate::collision::Circle;
179 use crate::geometry::{compute_circle_mass, make_box};
180 use crate::math_functions::{make_world_transform, Vec2, PI, TRANSFORM_IDENTITY};
181
182 let circle = Circle {
183 center: Vec2 { x: 1.0, y: 0.0 },
184 radius: 1.0,
185 };
186 let shape = Shape {
187 density: 2.0,
188 geometry: ShapeGeometry::Circle(circle),
189 ..Default::default()
190 };
191
192 let md = compute_shape_mass(&shape);
194 assert_eq!(md, compute_circle_mass(&circle, 2.0));
195
196 assert_eq!(get_shape_centroid(&shape), circle.center);
198 assert_eq!(get_shape_perimeter(&shape), 2.0 * PI);
199 let extent = compute_shape_extent(&shape, Vec2 { x: 0.0, y: 0.0 });
200 assert_eq!(extent.min_extent, 1.0);
201 assert_eq!(extent.max_extent, 2.0);
202
203 let aabb = compute_shape_aabb(&shape, make_world_transform(TRANSFORM_IDENTITY));
205 assert!((aabb.lower_bound.x - 0.0).abs() < 1e-6);
206 assert!((aabb.upper_bound.x - 2.0).abs() < 1e-6);
207
208 let proxy = make_shape_distance_proxy(&shape);
210 assert_eq!(proxy.count, 1);
211 assert_eq!(proxy.radius, 1.0);
212
213 let box_shape = Shape {
215 geometry: ShapeGeometry::Polygon(make_box(1.0, 1.0)),
216 ..Default::default()
217 };
218 let proj = get_shape_projected_perimeter(&box_shape, Vec2 { x: 1.0, y: 0.0 });
219 assert_eq!(proj, 2.0);
220 }
221
222 #[test]
223 fn ray_cast_shape_respects_transform() {
224 use crate::collision::{Circle, RayCastInput};
225 use crate::math_functions::{Transform, Vec2, ROT_IDENTITY};
226
227 let shape = Shape {
229 geometry: ShapeGeometry::Circle(Circle {
230 center: Vec2 { x: 0.0, y: 0.0 },
231 radius: 1.0,
232 }),
233 ..Default::default()
234 };
235 let transform = Transform {
236 p: Vec2 { x: 3.0, y: 0.0 },
237 q: ROT_IDENTITY,
238 };
239
240 let input = RayCastInput {
241 origin: Vec2 { x: 0.0, y: 0.0 },
242 translation: Vec2 { x: 8.0, y: 0.0 },
243 max_fraction: 1.0,
244 };
245 let output = ray_cast_shape(&input, &shape, transform);
246 assert!(output.hit);
247 assert!((output.point.x - 2.0).abs() < 1e-5);
249 assert!((output.fraction - 0.25).abs() < 1e-6);
250 assert!((output.normal.x + 1.0).abs() < 1e-6);
251 }
252
253 #[test]
254 fn create_shapes_on_body_updates_mass() {
255 use crate::body::{create_body, get_body_full_id};
256 use crate::collision::Circle;
257 use crate::geometry::{compute_polygon_mass, make_box};
258 use crate::math_functions::Vec2;
259 use crate::solver_set::AWAKE_SET;
260 use crate::types::{default_body_def, default_shape_def, default_world_def, BodyType};
261 use crate::world::World;
262
263 let mut world = World::new(&default_world_def());
264
265 let mut body_def = default_body_def();
266 body_def.type_ = BodyType::Dynamic;
267 let body = create_body(&mut world, &body_def);
268 let body_index = get_body_full_id(&world, body);
269
270 let box_poly = make_box(1.0, 1.0);
272 let shape_def = default_shape_def();
273 let shape_id = create_polygon_shape(&mut world, body, &shape_def, &box_poly);
274 assert!(shape_id.index1 >= 1);
275
276 let expected = compute_polygon_mass(&box_poly, 1.0);
277 {
278 let b = &world.bodies[body_index as usize];
279 assert_eq!(b.shape_count, 1);
280 assert_eq!(b.mass, expected.mass);
281 assert_eq!(b.inertia, expected.rotational_inertia);
282 }
283 {
284 let sim = &world.solver_sets[AWAKE_SET as usize].body_sims[0];
285 assert_eq!(sim.inv_mass, 1.0 / expected.mass);
286 assert!(sim.max_extent > 1.4 && sim.max_extent < 1.5);
287 }
288
289 let raw_shape = (shape_id.index1 - 1) as usize;
291 let proxy_key = world.shapes[raw_shape].proxy_key;
292 assert!(proxy_key != NULL_INDEX);
293 assert_eq!(world.broad_phase.shape_index(proxy_key), raw_shape as i32);
294
295 let circle = Circle {
297 center: Vec2 { x: 2.0, y: 0.0 },
298 radius: 0.5,
299 };
300 let mut lazy_def = default_shape_def();
301 lazy_def.update_body_mass = false;
302 let _s2 = create_circle_shape(&mut world, body, &lazy_def, &circle);
303 {
304 let b = &world.bodies[body_index as usize];
305 assert_eq!(b.shape_count, 2);
306 assert_eq!(b.mass, expected.mass);
308 assert!(b.flags & crate::body::body_flags::DIRTY_MASS != 0);
309 }
310 crate::body::update_body_mass_data(&mut world, body_index);
311 assert!(world.bodies[body_index as usize].mass > expected.mass);
312
313 let mut sensor_def = default_shape_def();
315 sensor_def.is_sensor = true;
316 let s3 = create_circle_shape(&mut world, body, &sensor_def, &circle);
317 let raw_s3 = (s3.index1 - 1) as usize;
318 assert_eq!(world.shapes[raw_s3].sensor_index, 0);
319 assert_eq!(world.sensors.len(), 1);
320 assert_eq!(world.sensors[0].shape_id, raw_s3 as i32);
321
322 world.validate_solver_sets();
323 }
324
325 #[test]
326 fn proxy_lifecycle_updates_broad_phase() {
327 use crate::broad_phase::BroadPhase;
328 use crate::math_functions::{make_world_transform, TRANSFORM_IDENTITY};
329 use crate::types::{BodyType, Capacity};
330
331 let mut bp = BroadPhase::new(&Capacity::default());
332 let mut shape = Shape {
333 id: 5,
334 geometry: ShapeGeometry::Circle(crate::collision::Circle {
335 center: crate::math_functions::Vec2 { x: 0.0, y: 0.0 },
336 radius: 0.5,
337 }),
338 aabb_margin: crate::constants::max_aabb_margin(),
339 ..Default::default()
340 };
341
342 create_shape_proxy(
343 &mut shape,
344 &mut bp,
345 BodyType::Dynamic,
346 make_world_transform(TRANSFORM_IDENTITY),
347 false,
348 );
349 assert!(shape.proxy_key != NULL_INDEX);
350 assert!(shape.fat_aabb.upper_bound.x > shape.aabb.upper_bound.x);
352 assert!(shape.aabb.upper_bound.x > 0.5);
353 assert_eq!(bp.shape_index(shape.proxy_key), 5);
354
355 destroy_shape_proxy(&mut shape, &mut bp);
356 assert_eq!(shape.proxy_key, NULL_INDEX);
357 }
358}