Skip to main content

box2d_rust/shape/
lifecycle.rs

1// Shape creation from shape.c.
2// SPDX-FileCopyrightText: 2023 Erin Catto
3// SPDX-License-Identifier: MIT
4
5use super::{create_shape_proxy, get_shape_centroid, Shape};
6use crate::collision::ShapeGeometry;
7use crate::core::NULL_INDEX;
8use crate::math_functions::{max_float, min_float, Aabb, WorldTransform};
9
10/// AABB margin for the broad phase fat AABB, limited by shape size.
11/// (static b2ComputeShapeMargin)
12pub(crate) fn compute_shape_margin(shape: &Shape) -> f32 {
13    use crate::constants::{max_aabb_margin, AABB_MARGIN_FRACTION};
14    use crate::math_functions::distance;
15
16    let margin = match &shape.geometry {
17        ShapeGeometry::Capsule(capsule) => {
18            0.5 * distance(capsule.center2, capsule.center1) + capsule.radius
19        }
20        ShapeGeometry::Circle(circle) => circle.radius,
21        ShapeGeometry::Polygon(poly) => {
22            let mut max_extent_sqr = 0.0f32;
23            for i in 0..poly.count as usize {
24                let distance_sqr =
25                    crate::math_functions::distance_squared(poly.vertices[i], poly.centroid);
26                max_extent_sqr = max_float(max_extent_sqr, distance_sqr);
27            }
28            max_extent_sqr.sqrt()
29        }
30        ShapeGeometry::Segment(segment) => 0.5 * distance(segment.point1, segment.point2),
31        ShapeGeometry::ChainSegment(chain_segment) => {
32            0.5 * distance(chain_segment.segment.point1, chain_segment.segment.point2)
33        }
34    };
35
36    min_float(max_aabb_margin(), AABB_MARGIN_FRACTION * margin)
37}
38
39/// Create a shape on a body. Returns the raw shape id.
40/// (static b2CreateShapeInternal — the C void* geometry + type tag is the
41/// ShapeGeometry enum)
42pub(crate) fn create_shape_internal(
43    world: &mut crate::world::World,
44    body_id: i32,
45    transform: WorldTransform,
46    def: &crate::types::ShapeDef,
47    geometry: ShapeGeometry,
48) -> i32 {
49    let shape_id = world.shape_id_pool.alloc_id();
50
51    if shape_id == world.shapes.len() as i32 {
52        world.shapes.push(Shape::default());
53    } else {
54        debug_assert!(world.shapes[shape_id as usize].id == NULL_INDEX);
55    }
56
57    let (body_raw_id, body_set_index, body_type, head_shape_id) = {
58        let body = &world.bodies[body_id as usize];
59        (body.id, body.set_index, body.type_, body.head_shape_id)
60    };
61
62    {
63        let shape = &mut world.shapes[shape_id as usize];
64        shape.geometry = geometry;
65
66        shape.id = shape_id;
67        shape.body_id = body_raw_id;
68        shape.density = def.density;
69        shape.material = def.material;
70        shape.filter = def.filter;
71        shape.user_data = def.user_data;
72        shape.enlarged_aabb = false;
73        shape.enable_sensor_events = def.enable_sensor_events;
74        shape.enable_contact_events = def.enable_contact_events;
75        shape.enable_custom_filtering = def.enable_custom_filtering;
76        shape.enable_hit_events = def.enable_hit_events;
77        shape.enable_pre_solve_events = def.enable_pre_solve_events;
78        shape.proxy_key = NULL_INDEX;
79        shape.local_centroid = get_shape_centroid(shape);
80        shape.aabb_margin = compute_shape_margin(shape);
81        shape.aabb = Aabb::default();
82        shape.fat_aabb = Aabb::default();
83        shape.generation += 1;
84    }
85
86    if body_set_index != crate::solver_set::DISABLED_SET {
87        // Split borrows: the shape and the broad phase are different World fields.
88        let (shapes, broad_phase) = (&mut world.shapes, &mut world.broad_phase);
89        create_shape_proxy(
90            &mut shapes[shape_id as usize],
91            broad_phase,
92            body_type,
93            transform,
94            def.invoke_contact_creation || def.is_sensor,
95        );
96    }
97
98    // Add to shape doubly linked list
99    if head_shape_id != NULL_INDEX {
100        world.shapes[head_shape_id as usize].prev_shape_id = shape_id;
101    }
102
103    world.shapes[shape_id as usize].prev_shape_id = NULL_INDEX;
104    world.shapes[shape_id as usize].next_shape_id = head_shape_id;
105    world.bodies[body_id as usize].head_shape_id = shape_id;
106    world.bodies[body_id as usize].shape_count += 1;
107
108    if def.is_sensor {
109        world.shapes[shape_id as usize].sensor_index = world.sensors.len() as i32;
110        world.sensors.push(crate::sensor::Sensor::new(shape_id));
111    } else {
112        world.shapes[shape_id as usize].sensor_index = NULL_INDEX;
113    }
114
115    world.validate_solver_sets();
116
117    shape_id
118}
119
120/// (static b2CreateShape)
121fn create_shape(
122    world: &mut crate::world::World,
123    body_id: crate::id::BodyId,
124    def: &crate::types::ShapeDef,
125    geometry: ShapeGeometry,
126) -> crate::id::ShapeId {
127    use crate::body::body_flags::DIRTY_MASS;
128    use crate::body::{
129        get_body_full_id, get_body_transform, sync_body_flags, update_body_mass_data,
130    };
131    use crate::math_functions::is_valid_float;
132
133    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
134    debug_assert!(is_valid_float(def.density) && def.density >= 0.0);
135    debug_assert!(is_valid_float(def.material.friction) && def.material.friction >= 0.0);
136    debug_assert!(is_valid_float(def.material.restitution) && def.material.restitution >= 0.0);
137    debug_assert!(
138        is_valid_float(def.material.rolling_resistance) && def.material.rolling_resistance >= 0.0
139    );
140    debug_assert!(is_valid_float(def.material.tangent_speed));
141
142    let body_index = get_body_full_id(world, body_id);
143    let transform = get_body_transform(world, body_index);
144
145    let shape_id = create_shape_internal(world, body_index, transform, def, geometry);
146
147    if def.update_body_mass {
148        update_body_mass_data(world, body_index);
149    } else if world.bodies[body_index as usize].flags & DIRTY_MASS == 0 {
150        world.bodies[body_index as usize].flags |= DIRTY_MASS;
151        sync_body_flags(world, body_index);
152    }
153
154    world.validate_solver_sets();
155
156    crate::id::ShapeId {
157        index1: shape_id + 1,
158        world0: body_id.world0,
159        generation: world.shapes[shape_id as usize].generation,
160    }
161}
162
163/// (b2CreateCircleShape)
164pub fn create_circle_shape(
165    world: &mut crate::world::World,
166    body_id: crate::id::BodyId,
167    def: &crate::types::ShapeDef,
168    circle: &crate::collision::Circle,
169) -> crate::id::ShapeId {
170    let id = create_shape(world, body_id, def, ShapeGeometry::Circle(*circle));
171    crate::recording::record_op(world, |rec, _| {
172        crate::recording::write_create_shape(
173            rec,
174            crate::recording::OP_CREATE_CIRCLE_SHAPE,
175            body_id,
176            def,
177            |buf| crate::recording::rec_w_circle(buf, *circle),
178            id,
179        )
180    });
181    id
182}
183
184/// (b2CreateCapsuleShape)
185pub fn create_capsule_shape(
186    world: &mut crate::world::World,
187    body_id: crate::id::BodyId,
188    def: &crate::types::ShapeDef,
189    capsule: &crate::collision::Capsule,
190) -> crate::id::ShapeId {
191    use crate::constants::linear_slop;
192    use crate::math_functions::distance_squared;
193
194    let length_sqr = distance_squared(capsule.center1, capsule.center2);
195    if length_sqr <= linear_slop() * linear_slop() {
196        return crate::id::ShapeId::default();
197    }
198
199    let id = create_shape(world, body_id, def, ShapeGeometry::Capsule(*capsule));
200    crate::recording::record_op(world, |rec, _| {
201        crate::recording::write_create_shape(
202            rec,
203            crate::recording::OP_CREATE_CAPSULE_SHAPE,
204            body_id,
205            def,
206            |buf| crate::recording::rec_w_capsule(buf, *capsule),
207            id,
208        )
209    });
210    id
211}
212
213/// (b2CreatePolygonShape)
214pub fn create_polygon_shape(
215    world: &mut crate::world::World,
216    body_id: crate::id::BodyId,
217    def: &crate::types::ShapeDef,
218    polygon: &crate::collision::Polygon,
219) -> crate::id::ShapeId {
220    debug_assert!(crate::math_functions::is_valid_float(polygon.radius) && polygon.radius >= 0.0);
221
222    let id = create_shape(world, body_id, def, ShapeGeometry::Polygon(*polygon));
223    crate::recording::record_op(world, |rec, _| {
224        crate::recording::write_create_shape(
225            rec,
226            crate::recording::OP_CREATE_POLYGON_SHAPE,
227            body_id,
228            def,
229            |buf| crate::recording::rec_w_polygon(buf, polygon),
230            id,
231        )
232    });
233    id
234}
235
236/// (b2CreateSegmentShape)
237pub fn create_segment_shape(
238    world: &mut crate::world::World,
239    body_id: crate::id::BodyId,
240    def: &crate::types::ShapeDef,
241    segment: &crate::collision::Segment,
242) -> crate::id::ShapeId {
243    use crate::constants::linear_slop;
244    use crate::math_functions::distance_squared;
245
246    let length_sqr = distance_squared(segment.point1, segment.point2);
247    if length_sqr <= linear_slop() * linear_slop() {
248        debug_assert!(false);
249        return crate::id::ShapeId::default();
250    }
251
252    let id = create_shape(world, body_id, def, ShapeGeometry::Segment(*segment));
253    crate::recording::record_op(world, |rec, _| {
254        crate::recording::write_create_shape(
255            rec,
256            crate::recording::OP_CREATE_SEGMENT_SHAPE,
257            body_id,
258            def,
259            |buf| crate::recording::rec_w_segment(buf, *segment),
260            id,
261        )
262    });
263    id
264}
265
266/// (b2CreateChainSegmentShape)
267pub fn create_chain_segment_shape(
268    world: &mut crate::world::World,
269    body_id: crate::id::BodyId,
270    def: &crate::types::ShapeDef,
271    chain_segment: &crate::collision::ChainSegment,
272) -> crate::id::ShapeId {
273    use crate::constants::linear_slop;
274    use crate::math_functions::distance_squared;
275
276    let length_sqr = distance_squared(chain_segment.segment.point1, chain_segment.segment.point2);
277    if length_sqr <= linear_slop() * linear_slop() {
278        debug_assert!(false);
279        return crate::id::ShapeId::default();
280    }
281
282    // No parent chain shape
283    let mut local = *chain_segment;
284    local.chain_id = NULL_INDEX;
285
286    let id = create_shape(world, body_id, def, ShapeGeometry::ChainSegment(local));
287    // C records `local` (parent chain id nulled), not the caller value.
288    crate::recording::record_op(world, |rec, _| {
289        crate::recording::write_create_shape(
290            rec,
291            crate::recording::OP_CREATE_CHAIN_SEGMENT_SHAPE,
292            body_id,
293            def,
294            |buf| crate::recording::rec_w_chainseg(buf, local),
295            id,
296        )
297    });
298    id
299}
300
301/// Validate a ShapeId and return the raw shape index. (b2GetShape — C returns
302/// a pointer; Rust returns the index into `world.shapes`)
303pub fn get_shape_index(world: &crate::world::World, shape_id: crate::id::ShapeId) -> i32 {
304    let index = shape_id.index1 - 1;
305    debug_assert!((index as usize) < world.shapes.len());
306    let shape = &world.shapes[index as usize];
307    debug_assert!(shape.id == index && shape.generation == shape_id.generation);
308    index
309}
310
311/// (static b2DestroyShapeInternal — C takes shape/body pointers; the Rust
312/// port takes ids)
313pub(crate) fn destroy_shape_internal(
314    world: &mut crate::world::World,
315    shape_id: i32,
316    body_id: i32,
317    wake_bodies: bool,
318) {
319    // Remove the shape from the body's doubly linked list.
320    let (prev_shape_id, next_shape_id) = {
321        let shape = &world.shapes[shape_id as usize];
322        (shape.prev_shape_id, shape.next_shape_id)
323    };
324
325    if prev_shape_id != NULL_INDEX {
326        world.shapes[prev_shape_id as usize].next_shape_id = next_shape_id;
327    }
328
329    if next_shape_id != NULL_INDEX {
330        world.shapes[next_shape_id as usize].prev_shape_id = prev_shape_id;
331    }
332
333    {
334        let body = &mut world.bodies[body_id as usize];
335        if shape_id == body.head_shape_id {
336            body.head_shape_id = next_shape_id;
337        }
338        body.shape_count -= 1;
339    }
340
341    // Remove from broad-phase.
342    {
343        let (shapes, broad_phase) = (&mut world.shapes, &mut world.broad_phase);
344        super::destroy_shape_proxy(&mut shapes[shape_id as usize], broad_phase);
345    }
346
347    // Destroy any contacts associated with the shape.
348    let mut contact_key = world.bodies[body_id as usize].head_contact_key;
349    while contact_key != NULL_INDEX {
350        let contact_id = contact_key >> 1;
351        let edge_index = contact_key & 1;
352
353        contact_key = world.contacts[contact_id as usize].edges[edge_index as usize].next_key;
354
355        let (contact_shape_a, contact_shape_b) = {
356            let contact = &world.contacts[contact_id as usize];
357            (contact.shape_id_a, contact.shape_id_b)
358        };
359        if contact_shape_a == shape_id || contact_shape_b == shape_id {
360            crate::contact::destroy_contact(world, contact_id, wake_bodies);
361        }
362    }
363
364    if world.shapes[shape_id as usize].sensor_index != NULL_INDEX {
365        // The C code inlines a copy of b2DestroySensor here (end-touch events
366        // for active overlaps, swap-remove with sensorIndex fixup).
367        crate::sensor::destroy_sensor(world, shape_id);
368    }
369
370    // Return shape to free list.
371    world.shape_id_pool.free_id(shape_id);
372    world.shapes[shape_id as usize].id = NULL_INDEX;
373
374    world.validate_solver_sets();
375}
376
377/// Destroy a shape by id, optionally updating the body mass.
378/// (b2DestroyShape)
379pub fn destroy_shape(
380    world: &mut crate::world::World,
381    shape_id: crate::id::ShapeId,
382    update_body_mass: bool,
383) {
384    crate::recording::record_op(world, |rec, _| {
385        crate::recording::write_destroy_shape(rec, shape_id, update_body_mass)
386    });
387    let shape_index = get_shape_index(world, shape_id);
388
389    // Cannot destroy a chain segment that has a parent chain shape
390    if let ShapeGeometry::ChainSegment(chain_segment) = &world.shapes[shape_index as usize].geometry
391    {
392        if chain_segment.chain_id != NULL_INDEX {
393            debug_assert!(false, "cannot destroy a chain segment owned by a chain");
394            return;
395        }
396    }
397
398    // need to wake bodies because this might be a static body
399    let wake_bodies = true;
400    let body_id = world.shapes[shape_index as usize].body_id;
401    destroy_shape_internal(world, shape_index, body_id, wake_bodies);
402
403    if update_body_mass {
404        crate::body::update_body_mass_data(world, body_id);
405    }
406}