Skip to main content

box2d_rust/
constraint_graph.rs

1// Port of the constraint graph data model from
2// box2d-cpp-reference/src/constraint_graph.h. Logic from constraint_graph.c
3// lands in the solver bring-up commit.
4//
5// The C b2GraphColor carries a transient union of raw pointers into arena
6// scratch memory (wideConstraints/overflowConstraints), rebuilt every step by
7// the solver. The Rust solver phase owns that scratch as Vecs local to the
8// step; the persistent graph state here is the bitset and the sim arrays.
9//
10// SPDX-FileCopyrightText: 2023 Erin Catto
11// SPDX-License-Identifier: MIT
12
13use crate::bitset::BitSet;
14use crate::constants::GRAPH_COLOR_COUNT;
15use crate::contact::ContactSim;
16use crate::joint::JointSim;
17
18/// This holds constraints that cannot fit the graph color limit. This happens
19/// when a single dynamic body is touching many other bodies. (B2_OVERFLOW_INDEX)
20pub const OVERFLOW_INDEX: i32 = GRAPH_COLOR_COUNT - 1;
21
22/// This keeps constraints involving two dynamic bodies at a lower solver
23/// priority than constraints involving a dynamic and static bodies. This
24/// reduces tunneling due to push through. (B2_DYNAMIC_COLOR_COUNT)
25pub const DYNAMIC_COLOR_COUNT: i32 = GRAPH_COLOR_COUNT - 4;
26
27/// (b2GraphColor)
28#[derive(Debug, Clone, Default)]
29pub struct GraphColor {
30    /// This bitset is indexed by bodyId so it is over-sized to encompass
31    /// static bodies; the bits are never traversed or counted. Unused on the
32    /// overflow color.
33    pub body_set: BitSet,
34
35    /// cache friendly arrays
36    pub contact_sims: Vec<ContactSim>,
37    pub joint_sims: Vec<JointSim>,
38}
39
40/// (b2ConstraintGraph)
41#[derive(Debug, Clone, Default)]
42pub struct ConstraintGraph {
43    /// including overflow at the end
44    pub colors: Vec<GraphColor>,
45}
46
47use crate::math_functions::max_int;
48use crate::solver_set::AWAKE_SET;
49use crate::types::{BodyType, Capacity};
50use crate::world::World;
51
52impl ConstraintGraph {
53    /// (b2CreateGraph)
54    pub fn new(capacity: &Capacity) -> ConstraintGraph {
55        const _: () = assert!(GRAPH_COLOR_COUNT >= 2, "must have at least two colors");
56        const _: () = assert!(DYNAMIC_COLOR_COUNT >= 2, "need more dynamic colors");
57
58        let body_capacity = max_int(capacity.static_body_count + capacity.dynamic_body_count, 16);
59
60        let mut colors = Vec::with_capacity(GRAPH_COLOR_COUNT as usize);
61        // Initialize graph color bit set. No bitset for overflow color.
62        for i in 0..GRAPH_COLOR_COUNT {
63            let mut color = GraphColor::default();
64            if i < OVERFLOW_INDEX {
65                color.body_set = BitSet::new(body_capacity as u32);
66                color.body_set.set_bit_count_and_clear(body_capacity as u32);
67                color.contact_sims.reserve(16);
68            }
69            colors.push(color);
70        }
71
72        ConstraintGraph { colors }
73    }
74}
75
76/// Contacts are always created as non-touching. They get moved into the
77/// constraint graph once they are found to be touching. (b2AddContactToGraph)
78pub fn add_contact_to_graph(world: &mut World, contact_sim: ContactSim, contact_id: i32) {
79    use crate::contact::contact_flags::{SIM_TOUCHING, TOUCHING};
80
81    debug_assert!(contact_sim.manifold.point_count > 0);
82    debug_assert!(contact_sim.sim_flags & SIM_TOUCHING != 0);
83    debug_assert!(world.contacts[contact_id as usize].flags & TOUCHING != 0);
84
85    let mut color_index = OVERFLOW_INDEX;
86
87    let (body_id_a, body_id_b) = {
88        let contact = &world.contacts[contact_id as usize];
89        (contact.edges[0].body_id, contact.edges[1].body_id)
90    };
91    let type_a = world.bodies[body_id_a as usize].type_;
92    let type_b = world.bodies[body_id_b as usize].type_;
93    debug_assert!(type_a == BodyType::Dynamic || type_b == BodyType::Dynamic);
94
95    // (B2_FORCE_OVERFLOW == 0 path)
96    if type_a == BodyType::Dynamic && type_b == BodyType::Dynamic {
97        // Dynamic constraint colors cannot encroach on colors reserved for
98        // static constraints
99        for i in 0..DYNAMIC_COLOR_COUNT {
100            let color = &mut world.constraint_graph.colors[i as usize];
101            if color.body_set.get_bit(body_id_a as u32) || color.body_set.get_bit(body_id_b as u32)
102            {
103                continue;
104            }
105
106            color.body_set.set_bit_grow(body_id_a as u32);
107            color.body_set.set_bit_grow(body_id_b as u32);
108            color_index = i;
109            break;
110        }
111    } else if type_a == BodyType::Dynamic {
112        // Static constraint colors build from the end to get higher priority
113        // than dyn-dyn constraints
114        let mut i = OVERFLOW_INDEX - 1;
115        while i >= 1 {
116            let color = &mut world.constraint_graph.colors[i as usize];
117            if !color.body_set.get_bit(body_id_a as u32) {
118                color.body_set.set_bit_grow(body_id_a as u32);
119                color_index = i;
120                break;
121            }
122            i -= 1;
123        }
124    } else if type_b == BodyType::Dynamic {
125        let mut i = OVERFLOW_INDEX - 1;
126        while i >= 1 {
127            let color = &mut world.constraint_graph.colors[i as usize];
128            if !color.body_set.get_bit(body_id_b as u32) {
129                color.body_set.set_bit_grow(body_id_b as u32);
130                color_index = i;
131                break;
132            }
133            i -= 1;
134        }
135    }
136
137    let local_index = world.constraint_graph.colors[color_index as usize]
138        .contact_sims
139        .len() as i32;
140    {
141        let contact = &mut world.contacts[contact_id as usize];
142        contact.color_index = color_index;
143        contact.local_index = local_index;
144    }
145
146    let mut new_contact = contact_sim;
147
148    if type_a == BodyType::Static {
149        new_contact.body_sim_index_a = crate::core::NULL_INDEX;
150        new_contact.inv_mass_a = 0.0;
151        new_contact.inv_i_a = 0.0;
152    } else {
153        debug_assert!(world.bodies[body_id_a as usize].set_index == AWAKE_SET);
154        let local = world.bodies[body_id_a as usize].local_index;
155        new_contact.body_sim_index_a = local;
156
157        let body_sim_a = &world.solver_sets[AWAKE_SET as usize].body_sims[local as usize];
158        new_contact.inv_mass_a = body_sim_a.inv_mass;
159        new_contact.inv_i_a = body_sim_a.inv_inertia;
160    }
161
162    if type_b == BodyType::Static {
163        new_contact.body_sim_index_b = crate::core::NULL_INDEX;
164        new_contact.inv_mass_b = 0.0;
165        new_contact.inv_i_b = 0.0;
166    } else {
167        debug_assert!(world.bodies[body_id_b as usize].set_index == AWAKE_SET);
168        let local = world.bodies[body_id_b as usize].local_index;
169        new_contact.body_sim_index_b = local;
170
171        let body_sim_b = &world.solver_sets[AWAKE_SET as usize].body_sims[local as usize];
172        new_contact.inv_mass_b = body_sim_b.inv_mass;
173        new_contact.inv_i_b = body_sim_b.inv_inertia;
174    }
175
176    world.constraint_graph.colors[color_index as usize]
177        .contact_sims
178        .push(new_contact);
179}
180
181/// (b2RemoveContactFromGraph)
182pub fn remove_contact_from_graph(
183    world: &mut World,
184    body_id_a: i32,
185    body_id_b: i32,
186    color_index: i32,
187    local_index: i32,
188) {
189    debug_assert!((0..GRAPH_COLOR_COUNT).contains(&color_index));
190    let color = &mut world.constraint_graph.colors[color_index as usize];
191
192    if color_index != OVERFLOW_INDEX {
193        // This might clear a bit for a kinematic or static body, but this has
194        // no effect
195        color.body_set.clear_bit(body_id_a as u32);
196        color.body_set.clear_bit(body_id_b as u32);
197    }
198
199    let moved_index = color.contact_sims.len() as i32 - 1;
200    color.contact_sims.swap_remove(local_index as usize);
201    if moved_index != local_index {
202        // Fix moved contact
203        let moved_id = color.contact_sims[local_index as usize].contact_id;
204        let moved_contact = &mut world.contacts[moved_id as usize];
205        debug_assert!(moved_contact.set_index == AWAKE_SET);
206        debug_assert!(moved_contact.color_index == color_index);
207        debug_assert!(moved_contact.local_index == moved_index);
208        moved_contact.local_index = local_index;
209    }
210}
211
212/// Notice that a joint cannot share the same color as a contact between the
213/// same two bodies, so contacts and joints can be solved in parallel within
214/// each color. (static b2AssignJointColor)
215fn assign_joint_color(
216    graph: &mut ConstraintGraph,
217    body_id_a: i32,
218    body_id_b: i32,
219    type_a: BodyType,
220    type_b: BodyType,
221) -> i32 {
222    debug_assert!(type_a == BodyType::Dynamic || type_b == BodyType::Dynamic);
223
224    if type_a == BodyType::Dynamic && type_b == BodyType::Dynamic {
225        for i in 0..DYNAMIC_COLOR_COUNT {
226            let color = &mut graph.colors[i as usize];
227            if color.body_set.get_bit(body_id_a as u32) || color.body_set.get_bit(body_id_b as u32)
228            {
229                continue;
230            }
231
232            color.body_set.set_bit_grow(body_id_a as u32);
233            color.body_set.set_bit_grow(body_id_b as u32);
234            return i;
235        }
236    } else if type_a == BodyType::Dynamic {
237        let mut i = OVERFLOW_INDEX - 1;
238        while i >= 1 {
239            let color = &mut graph.colors[i as usize];
240            if !color.body_set.get_bit(body_id_a as u32) {
241                color.body_set.set_bit_grow(body_id_a as u32);
242                return i;
243            }
244            i -= 1;
245        }
246    } else if type_b == BodyType::Dynamic {
247        let mut i = OVERFLOW_INDEX - 1;
248        while i >= 1 {
249            let color = &mut graph.colors[i as usize];
250            if !color.body_set.get_bit(body_id_b as u32) {
251                color.body_set.set_bit_grow(body_id_b as u32);
252                return i;
253            }
254            i -= 1;
255        }
256    }
257
258    OVERFLOW_INDEX
259}
260
261/// Assign a color and slot in the graph for a joint. Returns
262/// (color_index, local_index) rather than the C interior pointer.
263/// (b2CreateJointInGraph)
264pub fn create_joint_in_graph(world: &mut World, joint_id: i32) -> (i32, i32) {
265    let (body_id_a, body_id_b) = {
266        let joint = &world.joints[joint_id as usize];
267        (joint.edges[0].body_id, joint.edges[1].body_id)
268    };
269    let type_a = world.bodies[body_id_a as usize].type_;
270    let type_b = world.bodies[body_id_b as usize].type_;
271
272    let color_index = assign_joint_color(
273        &mut world.constraint_graph,
274        body_id_a,
275        body_id_b,
276        type_a,
277        type_b,
278    );
279
280    world.constraint_graph.colors[color_index as usize]
281        .joint_sims
282        .push(JointSim::default());
283    let local_index = world.constraint_graph.colors[color_index as usize]
284        .joint_sims
285        .len() as i32
286        - 1;
287
288    let joint = &mut world.joints[joint_id as usize];
289    joint.color_index = color_index;
290    joint.local_index = local_index;
291    (color_index, local_index)
292}
293
294/// (b2AddJointToGraph)
295pub fn add_joint_to_graph(world: &mut World, joint_sim: JointSim, joint_id: i32) {
296    let (color_index, local_index) = create_joint_in_graph(world, joint_id);
297    world.constraint_graph.colors[color_index as usize].joint_sims[local_index as usize] =
298        joint_sim;
299}
300
301/// (b2RemoveJointFromGraph)
302pub fn remove_joint_from_graph(
303    world: &mut World,
304    body_id_a: i32,
305    body_id_b: i32,
306    color_index: i32,
307    local_index: i32,
308) {
309    debug_assert!((0..GRAPH_COLOR_COUNT).contains(&color_index));
310    let color = &mut world.constraint_graph.colors[color_index as usize];
311
312    if color_index != OVERFLOW_INDEX {
313        // May clear static bodies, no effect
314        color.body_set.clear_bit(body_id_a as u32);
315        color.body_set.clear_bit(body_id_b as u32);
316    }
317
318    let moved_index = color.joint_sims.len() as i32 - 1;
319    color.joint_sims.swap_remove(local_index as usize);
320    if moved_index != local_index {
321        // Fix moved joint
322        let moved_id = color.joint_sims[local_index as usize].joint_id;
323        let moved_joint = &mut world.joints[moved_id as usize];
324        debug_assert!(moved_joint.set_index == AWAKE_SET);
325        debug_assert!(moved_joint.color_index == color_index);
326        debug_assert!(moved_joint.local_index == moved_index);
327        moved_joint.local_index = local_index;
328    }
329}
330
331/// Visualization colors for the constraint graph slots. The last index
332/// (GRAPH_COLOR_COUNT - 1) is the overflow color. (b2_graphColors)
333const GRAPH_COLORS: [crate::debug_draw::HexColor; GRAPH_COLOR_COUNT as usize] = {
334    use crate::debug_draw::HexColor;
335    [
336        HexColor::RED,
337        HexColor::ORANGE,
338        HexColor::YELLOW,
339        HexColor::LIME_GREEN,
340        HexColor::SPRING_GREEN,
341        HexColor::AQUA,
342        HexColor::DODGER_BLUE,
343        HexColor::BLUE_VIOLET,
344        HexColor::MAGENTA,
345        HexColor::DEEP_PINK,
346        HexColor::CRIMSON,
347        HexColor::CORAL,
348        HexColor::GOLD,
349        HexColor::GREEN_YELLOW,
350        HexColor::MEDIUM_SEA_GREEN,
351        HexColor::TURQUOISE,
352        HexColor::DEEP_SKY_BLUE,
353        HexColor::CORNFLOWER_BLUE,
354        HexColor::MEDIUM_SLATE_BLUE,
355        HexColor::MEDIUM_ORCHID,
356        HexColor::HOT_PINK,
357        HexColor::TOMATO,
358        HexColor::KHAKI,
359        HexColor::SILVER,
360    ]
361};
362
363/// Get the visualization color assigned to a constraint graph color slot.
364/// (b2GetGraphColor)
365pub fn get_graph_color(index: i32) -> crate::debug_draw::HexColor {
366    debug_assert!((0..GRAPH_COLOR_COUNT).contains(&index));
367    GRAPH_COLORS[index as usize]
368}