Skip to main content

box2d_rust/shape/
chain.rs

1// Chain shapes from shape.c: b2CreateChain, b2DestroyChain, and the
2// b2Chain_* accessors. A chain is a sequence of chain-segment shapes with
3// ghost vertices for smooth sliding, owned by one body.
4//
5// b2Chain_GetWorld is omitted: there is no world registry in the Rust port.
6//
7// SPDX-FileCopyrightText: 2023 Erin Catto
8// SPDX-License-Identifier: MIT
9
10use super::{create_shape_internal, destroy_shape_internal, ChainShape};
11use crate::body::get_body_full_id;
12use crate::collision::{ChainSegment, Segment, ShapeGeometry};
13use crate::core::NULL_INDEX;
14use crate::id::{BodyId, ChainId, ShapeId};
15use crate::math_functions::is_valid_float;
16use crate::types::{default_shape_def, ChainDef, SurfaceMaterial};
17use crate::world::World;
18
19/// Chain id validity. (b2Chain_IsValid — the world-registry check collapses
20/// to the index/generation check in the registry-less port)
21pub fn chain_is_valid(world: &World, id: ChainId) -> bool {
22    let chain_id = id.index1 - 1;
23    if chain_id < 0 || world.chain_shapes.len() as i32 <= chain_id {
24        return false;
25    }
26
27    let chain = &world.chain_shapes[chain_id as usize];
28    if chain.id == NULL_INDEX {
29        // chain is free
30        return false;
31    }
32
33    debug_assert!(chain.id == chain_id);
34
35    id.generation == chain.generation
36}
37
38/// Validate a ChainId and return the raw chain index. (static b2GetChainShape
39/// — C returns a pointer; Rust returns the index into `world.chain_shapes`)
40pub fn get_chain_index(world: &World, chain_id: ChainId) -> i32 {
41    let index = chain_id.index1 - 1;
42    debug_assert!((index as usize) < world.chain_shapes.len());
43    let chain = &world.chain_shapes[index as usize];
44    debug_assert!(chain.id == index && chain.generation == chain_id.generation);
45    index
46}
47
48/// Create a chain shape: a sequence of chain segments attached to a body.
49/// The def must have at least 4 points. (b2CreateChain)
50pub fn create_chain(world: &mut World, body_id: BodyId, def: &ChainDef) -> ChainId {
51    debug_assert!(def.internal_value == crate::core::SECRET_COOKIE);
52    debug_assert!(def.points.len() >= 4);
53    debug_assert!(def.materials.len() == 1 || def.materials.len() == def.points.len());
54
55    debug_assert!(!world.locked);
56    if world.locked {
57        return ChainId::default();
58    }
59
60    let body_index = get_body_full_id(world, body_id);
61    let transform =
62        crate::body::get_body_transform_quick(world, &world.bodies[body_index as usize]);
63
64    let chain_id = world.chain_id_pool.alloc_id();
65
66    if chain_id == world.chain_shapes.len() as i32 {
67        world.chain_shapes.push(ChainShape {
68            id: NULL_INDEX,
69            body_id: NULL_INDEX,
70            next_chain_id: NULL_INDEX,
71            shape_indices: Vec::new(),
72            materials: Vec::new(),
73            generation: 0,
74        });
75    } else {
76        debug_assert!(world.chain_shapes[chain_id as usize].id == NULL_INDEX);
77    }
78
79    let material_count = def.materials.len();
80    for material in def.materials.iter() {
81        debug_assert!(is_valid_float(material.friction) && material.friction >= 0.0);
82        debug_assert!(is_valid_float(material.restitution) && material.restitution >= 0.0);
83        debug_assert!(
84            is_valid_float(material.rolling_resistance) && material.rolling_resistance >= 0.0
85        );
86        debug_assert!(is_valid_float(material.tangent_speed));
87    }
88
89    let head_chain_id = world.bodies[body_index as usize].head_chain_id;
90    {
91        let chain_shape = &mut world.chain_shapes[chain_id as usize];
92        chain_shape.id = chain_id;
93        chain_shape.body_id = world.bodies[body_index as usize].id;
94        chain_shape.next_chain_id = head_chain_id;
95        chain_shape.generation += 1;
96        chain_shape.materials = def.materials.clone();
97    }
98
99    world.bodies[body_index as usize].head_chain_id = chain_id;
100
101    let mut shape_def = default_shape_def();
102    shape_def.user_data = def.user_data;
103    shape_def.filter = def.filter;
104    shape_def.enable_sensor_events = def.enable_sensor_events;
105    shape_def.enable_contact_events = false;
106    shape_def.enable_hit_events = false;
107
108    let points = &def.points;
109    let n = points.len();
110
111    // Materials are indexed by the leading point of the solid segment (or 0
112    // when the chain has a single shared material).
113    let material_for = |index: usize| -> SurfaceMaterial {
114        if material_count == 1 {
115            def.materials[0]
116        } else {
117            def.materials[index]
118        }
119    };
120
121    let make_segment = |g1: usize, p1: usize, p2: usize, g2: usize| ChainSegment {
122        ghost1: points[g1],
123        segment: Segment {
124            point1: points[p1],
125            point2: points[p2],
126        },
127        ghost2: points[g2],
128        chain_id,
129    };
130
131    let mut shape_indices: Vec<i32>;
132
133    if def.is_loop {
134        shape_indices = Vec::with_capacity(n);
135
136        let mut prev_index = n - 1;
137        for i in 0..(n - 2) {
138            let chain_segment = make_segment(prev_index, i, i + 1, i + 2);
139            prev_index = i;
140
141            shape_def.material = material_for(i);
142            let shape_id = create_shape_internal(
143                world,
144                body_index,
145                transform,
146                &shape_def,
147                ShapeGeometry::ChainSegment(chain_segment),
148            );
149            shape_indices.push(shape_id);
150        }
151
152        {
153            let chain_segment = make_segment(n - 3, n - 2, n - 1, 0);
154            shape_def.material = material_for(n - 2);
155            let shape_id = create_shape_internal(
156                world,
157                body_index,
158                transform,
159                &shape_def,
160                ShapeGeometry::ChainSegment(chain_segment),
161            );
162            shape_indices.push(shape_id);
163        }
164
165        {
166            let chain_segment = make_segment(n - 2, n - 1, 0, 1);
167            shape_def.material = material_for(n - 1);
168            let shape_id = create_shape_internal(
169                world,
170                body_index,
171                transform,
172                &shape_def,
173                ShapeGeometry::ChainSegment(chain_segment),
174            );
175            shape_indices.push(shape_id);
176        }
177    } else {
178        shape_indices = Vec::with_capacity(n - 3);
179
180        for i in 0..(n - 3) {
181            let chain_segment = make_segment(i, i + 1, i + 2, i + 3);
182
183            // Material is associated with leading point of solid segment
184            shape_def.material = material_for(i + 1);
185            let shape_id = create_shape_internal(
186                world,
187                body_index,
188                transform,
189                &shape_def,
190                ShapeGeometry::ChainSegment(chain_segment),
191            );
192            shape_indices.push(shape_id);
193        }
194    }
195
196    world.chain_shapes[chain_id as usize].shape_indices = shape_indices;
197
198    let id = ChainId {
199        index1: chain_id + 1,
200        world0: world.world_id,
201        generation: world.chain_shapes[chain_id as usize].generation,
202    };
203
204    // (B2_REC_CREATE(world, CreateChain, id, bodyId, *def))
205    crate::recording::record_op(world, |rec, _| {
206        crate::recording::write_create_chain(rec, body_id, def, id)
207    });
208
209    id
210}
211
212/// Destroy a chain shape and all its segments. (b2DestroyChain +
213/// b2FreeChainData)
214pub fn destroy_chain(world: &mut World, chain_id: ChainId) {
215    crate::recording::record_op(world, |rec, _| {
216        crate::recording::write_destroy_chain(rec, chain_id)
217    });
218    debug_assert!(!world.locked);
219    if world.locked {
220        return;
221    }
222
223    let chain_index = get_chain_index(world, chain_id);
224    let body_id = world.chain_shapes[chain_index as usize].body_id;
225
226    // Remove the chain from the body's singly linked list.
227    let mut found = false;
228    if world.bodies[body_id as usize].head_chain_id == chain_index {
229        world.bodies[body_id as usize].head_chain_id =
230            world.chain_shapes[chain_index as usize].next_chain_id;
231        found = true;
232    } else {
233        let mut prev_chain_id = world.bodies[body_id as usize].head_chain_id;
234        while prev_chain_id != NULL_INDEX {
235            let next = world.chain_shapes[prev_chain_id as usize].next_chain_id;
236            if next == chain_index {
237                world.chain_shapes[prev_chain_id as usize].next_chain_id =
238                    world.chain_shapes[chain_index as usize].next_chain_id;
239                found = true;
240                break;
241            }
242            prev_chain_id = next;
243        }
244    }
245
246    debug_assert!(found);
247    if !found {
248        return;
249    }
250
251    let shape_indices = std::mem::take(&mut world.chain_shapes[chain_index as usize].shape_indices);
252    for shape_id in shape_indices {
253        let wake_bodies = true;
254        destroy_shape_internal(world, shape_id, body_id, wake_bodies);
255    }
256
257    // (b2FreeChainData)
258    world.chain_shapes[chain_index as usize].materials = Vec::new();
259
260    // Return chain to free list.
261    world.chain_id_pool.free_id(chain_index);
262    world.chain_shapes[chain_index as usize].id = NULL_INDEX;
263
264    world.validate_solver_sets();
265}
266
267/// Get the number of segments on this chain. (b2Chain_GetSegmentCount)
268pub fn chain_get_segment_count(world: &World, chain_id: ChainId) -> i32 {
269    debug_assert!(!world.locked);
270    if world.locked {
271        return 0;
272    }
273
274    let chain_index = get_chain_index(world, chain_id);
275    world.chain_shapes[chain_index as usize].shape_indices.len() as i32
276}
277
278/// Get the segment shape ids for a chain, up to `capacity`.
279/// (b2Chain_GetSegments — returns a Vec instead of filling a caller array)
280pub fn chain_get_segments(world: &World, chain_id: ChainId, capacity: usize) -> Vec<ShapeId> {
281    debug_assert!(!world.locked);
282    if world.locked {
283        return Vec::new();
284    }
285
286    let chain_index = get_chain_index(world, chain_id);
287    let chain = &world.chain_shapes[chain_index as usize];
288
289    let count = chain.shape_indices.len().min(capacity);
290    chain.shape_indices[..count]
291        .iter()
292        .map(|&shape_id| ShapeId {
293            index1: shape_id + 1,
294            world0: world.world_id,
295            generation: world.shapes[shape_id as usize].generation,
296        })
297        .collect()
298}
299
300/// Get the number of chain surface materials: 1 (shared) or the segment
301/// count. (b2Chain_GetSurfaceMaterialCount)
302pub fn chain_get_surface_material_count(world: &World, chain_id: ChainId) -> i32 {
303    let chain_index = get_chain_index(world, chain_id);
304    world.chain_shapes[chain_index as usize].materials.len() as i32
305}
306
307/// Set a chain surface material, propagating it to the affected segment
308/// shape(s). (b2Chain_SetSurfaceMaterial)
309pub fn chain_set_surface_material(
310    world: &mut World,
311    chain_id: ChainId,
312    material: SurfaceMaterial,
313    material_index: usize,
314) {
315    crate::recording::record_op(world, |rec, _| {
316        crate::recording::write_chain_set_surface_material(
317            rec,
318            chain_id,
319            material,
320            material_index as i32,
321        )
322    });
323    debug_assert!(!world.locked);
324    if world.locked {
325        return;
326    }
327
328    let chain_index = get_chain_index(world, chain_id);
329    debug_assert!(material_index < world.chain_shapes[chain_index as usize].materials.len());
330    world.chain_shapes[chain_index as usize].materials[material_index] = material;
331
332    let (material_count, count) = {
333        let chain = &world.chain_shapes[chain_index as usize];
334        (chain.materials.len(), chain.shape_indices.len())
335    };
336    debug_assert!(material_count == 1 || material_count == count);
337
338    if material_count == 1 {
339        for i in 0..count {
340            let shape_id = world.chain_shapes[chain_index as usize].shape_indices[i];
341            world.shapes[shape_id as usize].material = material;
342        }
343    } else {
344        let shape_id = world.chain_shapes[chain_index as usize].shape_indices[material_index];
345        world.shapes[shape_id as usize].material = material;
346    }
347}
348
349/// Get a chain surface material by segment index. (b2Chain_GetSurfaceMaterial)
350pub fn chain_get_surface_material(
351    world: &World,
352    chain_id: ChainId,
353    segment_index: usize,
354) -> SurfaceMaterial {
355    let chain_index = get_chain_index(world, chain_id);
356    let chain = &world.chain_shapes[chain_index as usize];
357    // C asserts against the segment count and indexes the material array
358    // directly; with a single shared material any index above 0 would read
359    // past the C allocation, so a shared material is returned for all
360    // segments here.
361    debug_assert!(segment_index < chain.shape_indices.len());
362    if chain.materials.len() == 1 {
363        chain.materials[0]
364    } else {
365        chain.materials[segment_index]
366    }
367}