Skip to main content

box2d_rust/solver/
solve.rs

1// The solve driver from solver.c: b2Solve with the b2SolverTask stage
2// sequence run serially.
3//
4// The C partitions each stage into blocks claimed by workers via atomic CAS;
5// with one worker every stage is a single full-range block, so the sync
6// machinery (b2SolverStage/b2SyncBlock/atomicSyncBits) disappears and the
7// orchestrator loop in b2SolverTask becomes plain loops here. The stage
8// order, the overflow-before-colors ordering, and the ascending color order
9// are preserved exactly — they determine the float accumulation order.
10//
11// Per-color contact constraints live in a local Vec per color (the C uses
12// arena scratch hung off b2GraphColor). All colors run the scalar kernels;
13// see contact_solver.rs for why this matches the C wide path.
14//
15// SPDX-FileCopyrightText: 2023 Erin Catto
16// SPDX-License-Identifier: MIT
17//
18// bring-up: called by the world step slice.
19#![allow(dead_code)]
20// The range loops index two parallel arrays (constraints and graph colors).
21#![allow(clippy::needless_range_loop)]
22
23use super::integrate::{finalize_bodies, integrate_positions, integrate_velocities};
24use super::StepContext;
25use crate::constants::GRAPH_COLOR_COUNT;
26use crate::constraint_graph::OVERFLOW_INDEX;
27use crate::contact::contact_flags;
28use crate::contact_solver::{
29    apply_restitution, prepare_contacts, solve_contacts, store_impulses, warm_start_contacts,
30    ContactConstraint,
31};
32use crate::core::NULL_INDEX;
33use crate::events::{BodyMoveEvent, ContactHitEvent, JointEvent};
34use crate::id::{BodyId, ContactId, JointId, ShapeId};
35use crate::joint::{get_joint_reaction, prepare_joint, solve_joint, warm_start_joint};
36use crate::math_functions::{make_world_transform, offset_pos, TRANSFORM_IDENTITY};
37use crate::solver_set::AWAKE_SET;
38use crate::types::BodyType;
39use crate::world::World;
40
41// (solver.c: ITERATIONS / RELAX_ITERATIONS)
42const ITERATIONS: i32 = 1;
43const RELAX_ITERATIONS: i32 = 1;
44
45/// Prepare the joints of one graph color. The C prepare stage reads world
46/// data while writing the sim, so the sims are copied out and back.
47fn prepare_color_joints(world: &mut World, color_index: i32, context: &StepContext) {
48    let count = world.constraint_graph.colors[color_index as usize]
49        .joint_sims
50        .len();
51    for i in 0..count {
52        let mut sim = world.constraint_graph.colors[color_index as usize].joint_sims[i];
53        prepare_joint(world, &mut sim, context);
54        world.constraint_graph.colors[color_index as usize].joint_sims[i] = sim;
55    }
56}
57
58/// Solve with graph coloring. (b2Solve)
59pub fn solve(world: &mut World, context: &StepContext) {
60    // Only count steps that advance the simulation
61    world.step_index += 1;
62
63    // Are there any awake bodies? This scenario should not be important for
64    // profiling.
65    let awake_body_count = world.solver_sets[AWAKE_SET as usize].body_sims.len();
66    if awake_body_count == 0 {
67        world.broad_phase.validate_no_enlarged();
68        return;
69    }
70
71    // Prepare buffer for bullets (arena in C)
72    let mut bullet_bodies: Vec<i32> = Vec::with_capacity(awake_body_count);
73
74    // prepare for move events
75    world.body_move_events.resize(
76        awake_body_count,
77        BodyMoveEvent {
78            user_data: 0,
79            transform: make_world_transform(TRANSFORM_IDENTITY),
80            body_id: BodyId::default(),
81            fell_asleep: false,
82        },
83    );
84
85    // Reset the per-step event bit sets (b2Solve does this per worker before
86    // spawning the solver tasks)
87    let joint_id_capacity = world.joint_id_pool.id_capacity();
88    let contact_id_capacity = world.contact_id_pool.id_capacity();
89    {
90        let task_context = &mut world.task_contexts[0];
91        task_context
92            .joint_state_bit_set
93            .set_bit_count_and_clear(joint_id_capacity as u32);
94        task_context
95            .hit_event_bit_set
96            .set_bit_count_and_clear(contact_id_capacity as u32);
97        task_context.has_hit_events = false;
98    }
99
100    // Split an awake island. The C enqueues b2SplitIslandTask to run
101    // concurrently with the constraint solve and finishes it before body
102    // finalization; the serial port runs it inline here.
103    if world.split_island_id != NULL_INDEX {
104        let split_id = world.split_island_id;
105        crate::island::split_island(world, split_id);
106        world.split_island_id = NULL_INDEX;
107    }
108
109    // === Prepare constraints ===
110    // (stage b2_stagePrepareJoints: colored joints in ascending color order)
111    for color_index in 0..OVERFLOW_INDEX {
112        prepare_color_joints(world, color_index, context);
113    }
114
115    // (stage b2_stagePrepareContacts: colored contacts)
116    let mut color_constraints: Vec<Vec<ContactConstraint>> =
117        (0..GRAPH_COLOR_COUNT).map(|_| Vec::new()).collect();
118    for color_index in 0..GRAPH_COLOR_COUNT as usize {
119        // Overflow contacts prepare below to match the C stage order; the
120        // constraint storage is sized here either way.
121        let count = world.constraint_graph.colors[color_index]
122            .contact_sims
123            .len();
124        color_constraints[color_index].resize(count, ContactConstraint::default());
125    }
126    for color_index in 0..OVERFLOW_INDEX as usize {
127        let contacts = &world.constraint_graph.colors[color_index].contact_sims;
128        let states = &world.solver_sets[AWAKE_SET as usize].body_states;
129        prepare_contacts(
130            &mut color_constraints[color_index],
131            contacts,
132            states,
133            context,
134        );
135    }
136
137    // Single-threaded overflow work. These constraints don't fit in the graph
138    // coloring. (b2PrepareJoints_Overflow / b2PrepareContacts_Overflow)
139    prepare_color_joints(world, OVERFLOW_INDEX, context);
140    {
141        let contacts = &world.constraint_graph.colors[OVERFLOW_INDEX as usize].contact_sims;
142        let states = &world.solver_sets[AWAKE_SET as usize].body_states;
143        prepare_contacts(
144            &mut color_constraints[OVERFLOW_INDEX as usize],
145            contacts,
146            states,
147            context,
148        );
149    }
150
151    // === Sub-step loop ===
152    let sub_step_count = context.sub_step_count;
153    for _sub_step_index in 0..sub_step_count {
154        // Integrate velocities
155        integrate_velocities(world, context);
156
157        // Warm start constraints: overflow joints, overflow contacts, then
158        // each color (joints before contacts, matching the graph block
159        // layout).
160        {
161            let world_parts = &mut *world;
162            let graph = &mut world_parts.constraint_graph;
163            let states = &mut world_parts.solver_sets[AWAKE_SET as usize].body_states;
164
165            for joint in graph.colors[OVERFLOW_INDEX as usize].joint_sims.iter_mut() {
166                warm_start_joint(joint, states);
167            }
168            warm_start_contacts(&mut color_constraints[OVERFLOW_INDEX as usize], states);
169
170            for color_index in 0..OVERFLOW_INDEX as usize {
171                for joint in graph.colors[color_index].joint_sims.iter_mut() {
172                    warm_start_joint(joint, states);
173                }
174                warm_start_contacts(&mut color_constraints[color_index], states);
175            }
176        }
177
178        // Solve constraints
179        for _ in 0..ITERATIONS {
180            let use_bias = true;
181            let world_parts = &mut *world;
182            let graph = &mut world_parts.constraint_graph;
183            let states = &mut world_parts.solver_sets[AWAKE_SET as usize].body_states;
184            let joint_state_bit_set = &mut world_parts.task_contexts[0].joint_state_bit_set;
185
186            // Overflow constraints have lower priority. Typically these are
187            // dynamic-vs-dynamic. (b2SolveJoints_Overflow does not report
188            // joint events; only the colored b2SolveJointsTask does.)
189            for joint in graph.colors[OVERFLOW_INDEX as usize].joint_sims.iter_mut() {
190                solve_joint(joint, context, states, use_bias);
191            }
192            solve_contacts(
193                &mut color_constraints[OVERFLOW_INDEX as usize],
194                states,
195                context,
196                use_bias,
197            );
198
199            for color_index in 0..OVERFLOW_INDEX as usize {
200                for joint in graph.colors[color_index].joint_sims.iter_mut() {
201                    solve_joint(joint, context, states, use_bias);
202
203                    if (joint.force_threshold < f32::MAX || joint.torque_threshold < f32::MAX)
204                        && !joint_state_bit_set.get_bit(joint.joint_id as u32)
205                    {
206                        let (force, torque) = get_joint_reaction(joint, context.inv_h);
207
208                        // Check thresholds. A zero threshold means all awake
209                        // joints get reported.
210                        if force >= joint.force_threshold || torque >= joint.torque_threshold {
211                            // Flag this joint for processing.
212                            joint_state_bit_set.set_bit(joint.joint_id as u32);
213                        }
214                    }
215                }
216                solve_contacts(
217                    &mut color_constraints[color_index],
218                    states,
219                    context,
220                    use_bias,
221                );
222            }
223        }
224
225        // Integrate positions
226        integrate_positions(world, context);
227
228        // Relax constraints
229        for _ in 0..RELAX_ITERATIONS {
230            let use_bias = false;
231            let world_parts = &mut *world;
232            let graph = &mut world_parts.constraint_graph;
233            let states = &mut world_parts.solver_sets[AWAKE_SET as usize].body_states;
234
235            for joint in graph.colors[OVERFLOW_INDEX as usize].joint_sims.iter_mut() {
236                solve_joint(joint, context, states, use_bias);
237            }
238            solve_contacts(
239                &mut color_constraints[OVERFLOW_INDEX as usize],
240                states,
241                context,
242                use_bias,
243            );
244
245            for color_index in 0..OVERFLOW_INDEX as usize {
246                for joint in graph.colors[color_index].joint_sims.iter_mut() {
247                    solve_joint(joint, context, states, use_bias);
248                }
249                solve_contacts(
250                    &mut color_constraints[color_index],
251                    states,
252                    context,
253                    use_bias,
254                );
255            }
256        }
257    }
258
259    // Restitution: overflow first, then each color (contacts only)
260    {
261        let world_parts = &mut *world;
262        let states = &mut world_parts.solver_sets[AWAKE_SET as usize].body_states;
263
264        apply_restitution(
265            &mut color_constraints[OVERFLOW_INDEX as usize],
266            states,
267            context,
268        );
269        for color_index in 0..OVERFLOW_INDEX as usize {
270            apply_restitution(&mut color_constraints[color_index], states, context);
271        }
272    }
273
274    // Store impulses: overflow first (no hit-event flagging in the C overflow
275    // path), then the colored contacts with hit-event flagging
276    // (b2StoreImpulsesTask).
277    {
278        let world_parts = &mut *world;
279        let graph = &mut world_parts.constraint_graph;
280        let task_context = &mut world_parts.task_contexts[0];
281        let neg_hit_threshold = -world_parts.hit_event_threshold;
282
283        store_impulses(
284            &color_constraints[OVERFLOW_INDEX as usize],
285            &mut graph.colors[OVERFLOW_INDEX as usize].contact_sims,
286        );
287
288        for color_index in 0..OVERFLOW_INDEX as usize {
289            store_impulses(
290                &color_constraints[color_index],
291                &mut graph.colors[color_index].contact_sims,
292            );
293
294            // Check for hit events to speed up serial processing later in the
295            // step
296            for contact_sim in &graph.colors[color_index].contact_sims {
297                if contact_sim.sim_flags & contact_flags::SIM_ENABLE_HIT_EVENT != 0 {
298                    for k in 0..contact_sim.manifold.point_count as usize {
299                        let mp = &contact_sim.manifold.points[k];
300
301                        // Need to check total impulse because the point may be
302                        // speculative and not colliding
303                        if mp.normal_velocity < neg_hit_threshold && mp.total_normal_impulse > 0.0 {
304                            task_context
305                                .hit_event_bit_set
306                                .set_bit(contact_sim.contact_id as u32);
307                            task_context.has_hit_events = true;
308                            break;
309                        }
310                    }
311                }
312            }
313        }
314    }
315
316    // === Finalize bodies ===
317    // Prepare contact, enlarged body, and island bit sets used in body
318    // finalization.
319    {
320        let awake_island_count = world.solver_sets[AWAKE_SET as usize].island_sims.len();
321        let task_context = &mut world.task_contexts[0];
322        task_context.sensor_hits.clear();
323        task_context
324            .enlarged_sim_bit_set
325            .set_bit_count_and_clear(awake_body_count as u32);
326        task_context
327            .awake_island_bit_set
328            .set_bit_count_and_clear(awake_island_count as u32);
329        task_context.split_island_id = NULL_INDEX;
330        task_context.split_sleep_time = 0.0;
331    }
332
333    // Finalize bodies. Must happen after the constraint solver and after
334    // island splitting.
335    finalize_bodies(world, context, &mut bullet_bodies);
336
337    // === Report joint events ===
338    {
339        let world_id = world.world_id;
340        let word_count = world.task_contexts[0].joint_state_bit_set.block_count();
341        for k in 0..word_count {
342            let mut word = world.task_contexts[0].joint_state_bit_set.block(k);
343            while word != 0 {
344                let ctz = word.trailing_zeros();
345                let joint_id = (64 * k + ctz) as i32;
346
347                let joint = &world.joints[joint_id as usize];
348                debug_assert!(joint.set_index == AWAKE_SET);
349
350                let event = JointEvent {
351                    joint_id: JointId {
352                        index1: joint_id + 1,
353                        world0: world_id,
354                        generation: joint.generation,
355                    },
356                    user_data: joint.user_data,
357                };
358
359                world.joint_events.push(event);
360
361                // Clear the smallest set bit
362                word &= word - 1;
363            }
364        }
365    }
366
367    // === Report hit events ===
368    {
369        debug_assert!(world.contact_hit_events.is_empty());
370
371        if world.task_contexts[0].has_hit_events {
372            let threshold = world.hit_event_threshold;
373            let world_id = world.world_id;
374
375            let word_count = world.task_contexts[0].hit_event_bit_set.block_count();
376            for k in 0..word_count {
377                let mut word = world.task_contexts[0].hit_event_bit_set.block(k);
378                while word != 0 {
379                    let ctz = word.trailing_zeros();
380                    let contact_id = (64 * k + ctz) as i32;
381
382                    let contact = world.contacts[contact_id as usize];
383                    debug_assert!(
384                        contact.set_index == AWAKE_SET && contact.color_index != NULL_INDEX
385                    );
386
387                    let contact_sim = &world.constraint_graph.colors[contact.color_index as usize]
388                        .contact_sims[contact.local_index as usize];
389
390                    let mut approach_speed = threshold;
391                    let mut best_point: Option<usize> = None;
392                    for p in 0..contact_sim.manifold.point_count as usize {
393                        let mp = &contact_sim.manifold.points[p];
394                        let point_approach_speed = -mp.normal_velocity;
395
396                        // Need to check total impulse because the point may be
397                        // speculative and not colliding
398                        if point_approach_speed > approach_speed && mp.total_normal_impulse > 0.0 {
399                            approach_speed = point_approach_speed;
400                            best_point = Some(p);
401                        }
402                    }
403
404                    if let Some(p) = best_point {
405                        let best = contact_sim.manifold.points[p];
406                        let normal = contact_sim.manifold.normal;
407
408                        let shape_a = &world.shapes[contact_sim.shape_id_a as usize];
409                        let shape_b = &world.shapes[contact_sim.shape_id_b as usize];
410
411                        // World contact point reconstructed from a body center
412                        // of mass and the matching anchor. The anchors were
413                        // built with the manifold, so a body that has moved
414                        // since drags the point with it. A static body has not
415                        // moved, prefer one so the common case of a fast body
416                        // striking the world stays exact.
417                        let body_a = &world.bodies[shape_a.body_id as usize];
418                        let body_b = &world.bodies[shape_b.body_id as usize];
419                        let point = if body_a.type_ != BodyType::Static
420                            && body_b.type_ == BodyType::Static
421                        {
422                            let body_sim_b = &world.solver_sets[body_b.set_index as usize]
423                                .body_sims[body_b.local_index as usize];
424                            offset_pos(body_sim_b.center, best.anchor_b)
425                        } else {
426                            let body_sim_a = &world.solver_sets[body_a.set_index as usize]
427                                .body_sims[body_a.local_index as usize];
428                            offset_pos(body_sim_a.center, best.anchor_a)
429                        };
430
431                        let event = ContactHitEvent {
432                            shape_id_a: ShapeId {
433                                index1: shape_a.id + 1,
434                                world0: world_id,
435                                generation: shape_a.generation,
436                            },
437                            shape_id_b: ShapeId {
438                                index1: shape_b.id + 1,
439                                world0: world_id,
440                                generation: shape_b.generation,
441                            },
442                            contact_id: ContactId {
443                                index1: contact.contact_id + 1,
444                                world0: world_id,
445                                padding: 0,
446                                generation: contact.generation,
447                            },
448                            point,
449                            normal,
450                            approach_speed,
451                        };
452
453                        world.contact_hit_events.push(event);
454                    }
455
456                    // Clear the smallest set bit
457                    word &= word - 1;
458                }
459            }
460        }
461    }
462
463    // === Refit broad phase ===
464    {
465        world.broad_phase.validate_no_enlarged();
466
467        // Enlarge broad-phase proxies and build move array.
468        // Apply shape AABB changes to broad-phase. This also creates the move
469        // array which must be in deterministic order. Sim bodies are tracked
470        // because the number of shape ids can be huge. This has to happen
471        // before bullets are processed.
472        let word_count = world.task_contexts[0].enlarged_sim_bit_set.block_count();
473        for k in 0..word_count {
474            let mut word = world.task_contexts[0].enlarged_sim_bit_set.block(k);
475            while word != 0 {
476                let ctz = word.trailing_zeros();
477                let body_sim_index = (64 * k + ctz) as usize;
478
479                let (body_id, sim_flags) = {
480                    let body_sim = &world.solver_sets[AWAKE_SET as usize].body_sims[body_sim_index];
481                    (body_sim.body_id, body_sim.flags)
482                };
483
484                let mut shape_id = world.bodies[body_id as usize].head_shape_id;
485                if sim_flags
486                    & (crate::body::body_flags::IS_BULLET | crate::body::body_flags::IS_FAST)
487                    == (crate::body::body_flags::IS_BULLET | crate::body::body_flags::IS_FAST)
488                {
489                    // Fast bullet bodies don't have their final AABB yet
490                    while shape_id != NULL_INDEX {
491                        let proxy_key = world.shapes[shape_id as usize].proxy_key;
492
493                        // Shape is fast. Its aabb will be enlarged in
494                        // continuous collision. Update the move array here for
495                        // determinism because bullets are processed below in
496                        // non-deterministic order.
497                        world.broad_phase.buffer_move(proxy_key);
498
499                        shape_id = world.shapes[shape_id as usize].next_shape_id;
500                    }
501                } else {
502                    while shape_id != NULL_INDEX {
503                        // The AABB may not have been enlarged, despite the
504                        // body being flagged as enlarged. For example, a body
505                        // with multiple shapes may have not have all shapes
506                        // enlarged. A fast body may have been flagged as
507                        // enlarged despite having no shapes enlarged.
508                        if world.shapes[shape_id as usize].enlarged_aabb {
509                            let proxy_key = world.shapes[shape_id as usize].proxy_key;
510                            let fat_aabb = world.shapes[shape_id as usize].fat_aabb;
511                            world.broad_phase.enlarge_proxy(proxy_key, fat_aabb);
512                            world.shapes[shape_id as usize].enlarged_aabb = false;
513                        }
514
515                        shape_id = world.shapes[shape_id as usize].next_shape_id;
516                    }
517                }
518
519                // Clear the smallest set bit
520                word &= word - 1;
521            }
522        }
523
524        world.broad_phase.validate();
525    }
526
527    // === Bullets ===
528    if !bullet_bodies.is_empty() {
529        // Fast bullet bodies. Note: a bullet body may be moving slow.
530        // (b2BulletBodyTask)
531        for &sim_index in &bullet_bodies {
532            super::continuous::solve_continuous(world, sim_index);
533        }
534
535        // Serially enlarge broad-phase proxies for bullet shapes.
536        // This loop has non-deterministic order in C but it shouldn't affect
537        // the result; the serial port follows the bullet array order.
538        for &sim_index in &bullet_bodies {
539            let (body_id, enlarge) = {
540                let bullet_body_sim =
541                    &world.solver_sets[AWAKE_SET as usize].body_sims[sim_index as usize];
542                (
543                    bullet_body_sim.body_id,
544                    bullet_body_sim.flags & crate::body::body_flags::ENLARGE_BOUNDS != 0,
545                )
546            };
547            if !enlarge {
548                continue;
549            }
550
551            // Clear flag
552            world.solver_sets[AWAKE_SET as usize].body_sims[sim_index as usize].flags &=
553                !crate::body::body_flags::ENLARGE_BOUNDS;
554
555            let mut shape_id = world.bodies[body_id as usize].head_shape_id;
556            while shape_id != NULL_INDEX {
557                if !world.shapes[shape_id as usize].enlarged_aabb {
558                    shape_id = world.shapes[shape_id as usize].next_shape_id;
559                    continue;
560                }
561
562                // Clear flag
563                world.shapes[shape_id as usize].enlarged_aabb = false;
564
565                let proxy_key = world.shapes[shape_id as usize].proxy_key;
566                let proxy_id = crate::broad_phase::proxy_id(proxy_key);
567                debug_assert!(crate::broad_phase::proxy_type(proxy_key) == BodyType::Dynamic);
568
569                // all fast bullet shapes should already be in the move buffer
570                debug_assert!(world.broad_phase.moved_proxies[BodyType::Dynamic as usize]
571                    .get_bit(proxy_id as u32));
572
573                let fat_aabb = world.shapes[shape_id as usize].fat_aabb;
574                world.broad_phase.trees[BodyType::Dynamic as usize]
575                    .enlarge_proxy(proxy_id, fat_aabb);
576
577                shape_id = world.shapes[shape_id as usize].next_shape_id;
578            }
579        }
580    }
581
582    // === Report sensor hits ===
583    // This may include bullet sensor hits.
584    {
585        let hits = std::mem::take(&mut world.task_contexts[0].sensor_hits);
586        for hit in hits {
587            let sensor_index = world.shapes[hit.sensor_id as usize].sensor_index;
588            let generation = world.shapes[hit.visitor_id as usize].generation;
589
590            let shape_ref = crate::sensor::Visitor {
591                shape_id: hit.visitor_id,
592                generation,
593            };
594            world.sensors[sensor_index as usize].hits.push(shape_ref);
595        }
596    }
597
598    // === Island sleeping ===
599    // This must be done last because putting islands to sleep invalidates the
600    // enlarged body bits.
601    if world.enable_sleep {
602        // Collect split island candidate for the next time step. No need to
603        // split if sleeping is disabled.
604        debug_assert!(world.split_island_id == NULL_INDEX);
605        {
606            let task_context = &world.task_contexts[0];
607            if task_context.split_island_id != NULL_INDEX && task_context.split_sleep_time >= 0.0 {
608                debug_assert!(task_context.split_sleep_time > 0.0);
609                world.split_island_id = task_context.split_island_id;
610            }
611        }
612
613        // Need to process in reverse because this moves islands to sleeping
614        // solver sets.
615        let count = world.solver_sets[AWAKE_SET as usize].island_sims.len();
616        for island_index in (0..count).rev() {
617            if world.task_contexts[0]
618                .awake_island_bit_set
619                .get_bit(island_index as u32)
620            {
621                // this island is still awake
622                continue;
623            }
624
625            let island_id =
626                world.solver_sets[AWAKE_SET as usize].island_sims[island_index].island_id;
627
628            crate::solver_set::try_sleep_island(world, island_id);
629        }
630
631        world.validate_solver_sets();
632    }
633}