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 api;
137mod chain;
138mod dispatch;
139mod geometry_api;
140mod lifecycle;
141
142pub use api::*;
143pub use chain::*;
144pub use dispatch::*;
145pub use geometry_api::*;
146pub use lifecycle::*;
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use crate::types::default_filter;
152
153 #[test]
154 fn filter_logic() {
155 let mut a = default_filter();
157 let mut b = default_filter();
158 a.group_index = 3;
159 b.group_index = 3;
160 assert!(should_shapes_collide(a, b));
161 a.group_index = -3;
162 b.group_index = -3;
163 assert!(!should_shapes_collide(a, b));
164
165 a.group_index = 0;
167 b.group_index = 0;
168 a.category_bits = 0x2;
169 a.mask_bits = 0x4;
170 b.category_bits = 0x4;
171 b.mask_bits = 0x2;
172 assert!(should_shapes_collide(a, b));
173 b.mask_bits = 0x8;
174 assert!(!should_shapes_collide(a, b));
175
176 let shape_filter = default_filter();
178 let query = crate::types::default_query_filter();
179 assert!(should_query_collide(shape_filter, query));
180 }
181
182 #[test]
183 fn shape_dispatch_matches_geometry() {
184 use crate::collision::Circle;
185 use crate::geometry::{compute_circle_mass, make_box};
186 use crate::math_functions::{make_world_transform, Vec2, PI, TRANSFORM_IDENTITY};
187
188 let circle = Circle {
189 center: Vec2 { x: 1.0, y: 0.0 },
190 radius: 1.0,
191 };
192 let shape = Shape {
193 density: 2.0,
194 geometry: ShapeGeometry::Circle(circle),
195 ..Default::default()
196 };
197
198 let md = compute_shape_mass(&shape);
200 assert_eq!(md, compute_circle_mass(&circle, 2.0));
201
202 assert_eq!(get_shape_centroid(&shape), circle.center);
204 assert_eq!(get_shape_perimeter(&shape), 2.0 * PI);
205 let extent = compute_shape_extent(&shape, Vec2 { x: 0.0, y: 0.0 });
206 assert_eq!(extent.min_extent, 1.0);
207 assert_eq!(extent.max_extent, 2.0);
208
209 let aabb = compute_shape_aabb(&shape, make_world_transform(TRANSFORM_IDENTITY));
211 assert!((aabb.lower_bound.x - 0.0).abs() < 1e-6);
212 assert!((aabb.upper_bound.x - 2.0).abs() < 1e-6);
213
214 let proxy = make_shape_distance_proxy(&shape);
216 assert_eq!(proxy.count, 1);
217 assert_eq!(proxy.radius, 1.0);
218
219 let box_shape = Shape {
221 geometry: ShapeGeometry::Polygon(make_box(1.0, 1.0)),
222 ..Default::default()
223 };
224 let proj = get_shape_projected_perimeter(&box_shape, Vec2 { x: 1.0, y: 0.0 });
225 assert_eq!(proj, 2.0);
226 }
227
228 #[test]
229 fn ray_cast_shape_respects_transform() {
230 use crate::collision::{Circle, RayCastInput};
231 use crate::math_functions::{Transform, Vec2, ROT_IDENTITY};
232
233 let shape = Shape {
235 geometry: ShapeGeometry::Circle(Circle {
236 center: Vec2 { x: 0.0, y: 0.0 },
237 radius: 1.0,
238 }),
239 ..Default::default()
240 };
241 let transform = Transform {
242 p: Vec2 { x: 3.0, y: 0.0 },
243 q: ROT_IDENTITY,
244 };
245
246 let input = RayCastInput {
247 origin: Vec2 { x: 0.0, y: 0.0 },
248 translation: Vec2 { x: 8.0, y: 0.0 },
249 max_fraction: 1.0,
250 };
251 let output = ray_cast_shape(&input, &shape, transform);
252 assert!(output.hit);
253 assert!((output.point.x - 2.0).abs() < 1e-5);
255 assert!((output.fraction - 0.25).abs() < 1e-6);
256 assert!((output.normal.x + 1.0).abs() < 1e-6);
257 }
258
259 #[test]
260 fn create_shapes_on_body_updates_mass() {
261 use crate::body::{create_body, get_body_full_id};
262 use crate::collision::Circle;
263 use crate::geometry::{compute_polygon_mass, make_box};
264 use crate::math_functions::Vec2;
265 use crate::solver_set::AWAKE_SET;
266 use crate::types::{default_body_def, default_shape_def, default_world_def, BodyType};
267 use crate::world::World;
268
269 let mut world = World::new(&default_world_def());
270
271 let mut body_def = default_body_def();
272 body_def.type_ = BodyType::Dynamic;
273 let body = create_body(&mut world, &body_def);
274 let body_index = get_body_full_id(&world, body);
275
276 let box_poly = make_box(1.0, 1.0);
278 let shape_def = default_shape_def();
279 let shape_id = create_polygon_shape(&mut world, body, &shape_def, &box_poly);
280 assert!(shape_id.index1 >= 1);
281
282 let expected = compute_polygon_mass(&box_poly, 1.0);
283 {
284 let b = &world.bodies[body_index as usize];
285 assert_eq!(b.shape_count, 1);
286 assert_eq!(b.mass, expected.mass);
287 assert_eq!(b.inertia, expected.rotational_inertia);
288 }
289 {
290 let sim = &world.solver_sets[AWAKE_SET as usize].body_sims[0];
291 assert_eq!(sim.inv_mass, 1.0 / expected.mass);
292 assert!(sim.max_extent > 1.4 && sim.max_extent < 1.5);
293 }
294
295 let raw_shape = (shape_id.index1 - 1) as usize;
297 let proxy_key = world.shapes[raw_shape].proxy_key;
298 assert!(proxy_key != NULL_INDEX);
299 assert_eq!(world.broad_phase.shape_index(proxy_key), raw_shape as i32);
300
301 let circle = Circle {
303 center: Vec2 { x: 2.0, y: 0.0 },
304 radius: 0.5,
305 };
306 let mut lazy_def = default_shape_def();
307 lazy_def.update_body_mass = false;
308 let _s2 = create_circle_shape(&mut world, body, &lazy_def, &circle);
309 {
310 let b = &world.bodies[body_index as usize];
311 assert_eq!(b.shape_count, 2);
312 assert_eq!(b.mass, expected.mass);
314 assert!(b.flags & crate::body::body_flags::DIRTY_MASS != 0);
315 }
316 crate::body::update_body_mass_data(&mut world, body_index);
317 assert!(world.bodies[body_index as usize].mass > expected.mass);
318
319 let mut sensor_def = default_shape_def();
321 sensor_def.is_sensor = true;
322 let s3 = create_circle_shape(&mut world, body, &sensor_def, &circle);
323 let raw_s3 = (s3.index1 - 1) as usize;
324 assert_eq!(world.shapes[raw_s3].sensor_index, 0);
325 assert_eq!(world.sensors.len(), 1);
326 assert_eq!(world.sensors[0].shape_id, raw_s3 as i32);
327
328 world.validate_solver_sets();
329 }
330
331 #[test]
332 fn proxy_lifecycle_updates_broad_phase() {
333 use crate::broad_phase::BroadPhase;
334 use crate::math_functions::{make_world_transform, TRANSFORM_IDENTITY};
335 use crate::types::{BodyType, Capacity};
336
337 let mut bp = BroadPhase::new(&Capacity::default());
338 let mut shape = Shape {
339 id: 5,
340 geometry: ShapeGeometry::Circle(crate::collision::Circle {
341 center: crate::math_functions::Vec2 { x: 0.0, y: 0.0 },
342 radius: 0.5,
343 }),
344 aabb_margin: crate::constants::max_aabb_margin(),
345 ..Default::default()
346 };
347
348 create_shape_proxy(
349 &mut shape,
350 &mut bp,
351 BodyType::Dynamic,
352 make_world_transform(TRANSFORM_IDENTITY),
353 false,
354 );
355 assert!(shape.proxy_key != NULL_INDEX);
356 assert!(shape.fat_aabb.upper_bound.x > shape.aabb.upper_bound.x);
358 assert!(shape.aabb.upper_bound.x > 0.5);
359 assert_eq!(bp.shape_index(shape.proxy_key), 5);
360
361 destroy_shape_proxy(&mut shape, &mut bp);
362 assert_eq!(shape.proxy_key, NULL_INDEX);
363 }
364}