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