Skip to main content

box2d_rust/joint/
solve.rs

1// Joint solve dispatch from joint.c: b2PrepareJoint/b2WarmStartJoint/
2// b2SolveJoint and the joint reaction used for joint events.
3//
4// The C dispatch passes b2StepContext (which carries world and body-state
5// pointers); the serial Rust port passes the world/states explicitly with the
6// same split used by the per-type files. The overflow/graph-color task loops
7// (b2PrepareJoints_Overflow, b2PrepareJointsTask, ...) belong to the solver
8// slice, which iterates the colors serially.
9//
10// SPDX-FileCopyrightText: 2023 Erin Catto
11// SPDX-License-Identifier: MIT
12//
13// bring-up: called by the solver slice.
14
15use super::*;
16use crate::body::BodyState;
17use crate::distance_joint::{
18    get_distance_joint_force, prepare_distance_joint, solve_distance_joint,
19    warm_start_distance_joint,
20};
21use crate::math_functions::{abs_float, add, length, min_float, Vec2, VEC2_ZERO};
22use crate::motor_joint::{
23    get_motor_joint_force, get_motor_joint_torque, prepare_motor_joint, solve_motor_joint,
24    warm_start_motor_joint,
25};
26use crate::prismatic_joint::{
27    get_prismatic_joint_force, get_prismatic_joint_torque, prepare_prismatic_joint,
28    solve_prismatic_joint, warm_start_prismatic_joint,
29};
30use crate::revolute_joint::{
31    get_revolute_joint_force, get_revolute_joint_torque, prepare_revolute_joint,
32    solve_revolute_joint, warm_start_revolute_joint,
33};
34use crate::solver::{make_soft, StepContext};
35use crate::weld_joint::{
36    get_weld_joint_force, get_weld_joint_torque, prepare_weld_joint, solve_weld_joint,
37    warm_start_weld_joint,
38};
39use crate::wheel_joint::{
40    get_wheel_joint_force, get_wheel_joint_torque, prepare_wheel_joint, solve_wheel_joint,
41    warm_start_wheel_joint,
42};
43use crate::world::World;
44
45/// (b2PrepareJoint)
46pub fn prepare_joint(world: &World, joint: &mut JointSim, context: &StepContext) {
47    // Clamp joint hertz based on the time step to reduce jitter.
48    let hertz = min_float(joint.constraint_hertz, 0.25 * context.inv_h);
49    joint.constraint_softness = make_soft(hertz, joint.constraint_damping_ratio, context.h);
50
51    match joint.joint_type() {
52        JointType::Distance => prepare_distance_joint(world, joint, context),
53        JointType::Motor => prepare_motor_joint(world, joint, context),
54        JointType::Filter => {}
55        JointType::Prismatic => prepare_prismatic_joint(world, joint, context),
56        JointType::Revolute => prepare_revolute_joint(world, joint, context),
57        JointType::Weld => prepare_weld_joint(world, joint, context),
58        JointType::Wheel => prepare_wheel_joint(world, joint, context),
59    }
60}
61
62/// (b2WarmStartJoint)
63pub fn warm_start_joint(joint: &mut JointSim, states: &mut [BodyState]) {
64    match joint.joint_type() {
65        JointType::Distance => warm_start_distance_joint(joint, states),
66        JointType::Motor => warm_start_motor_joint(joint, states),
67        JointType::Filter => {}
68        JointType::Prismatic => warm_start_prismatic_joint(joint, states),
69        JointType::Revolute => warm_start_revolute_joint(joint, states),
70        JointType::Weld => warm_start_weld_joint(joint, states),
71        JointType::Wheel => warm_start_wheel_joint(joint, states),
72    }
73}
74
75/// (b2SolveJoint)
76pub fn solve_joint(
77    joint: &mut JointSim,
78    context: &StepContext,
79    states: &mut [BodyState],
80    use_bias: bool,
81) {
82    match joint.joint_type() {
83        JointType::Distance => solve_distance_joint(joint, context, states, use_bias),
84        JointType::Motor => solve_motor_joint(joint, context, states),
85        JointType::Filter => {}
86        JointType::Prismatic => solve_prismatic_joint(joint, context, states, use_bias),
87        JointType::Revolute => solve_revolute_joint(joint, context, states, use_bias),
88        JointType::Weld => solve_weld_joint(joint, context, states, use_bias),
89        JointType::Wheel => solve_wheel_joint(joint, context, states, use_bias),
90    }
91}
92
93/// Reaction force/torque magnitudes for joint events. Returns
94/// (force, torque). (b2GetJointReaction — C returns through out-pointers)
95pub fn get_joint_reaction(sim: &JointSim, inv_time_step: f32) -> (f32, f32) {
96    let mut linear_impulse = 0.0;
97    let mut angular_impulse = 0.0;
98
99    match &sim.payload {
100        JointPayload::Distance(joint) => {
101            linear_impulse = abs_float(
102                joint.impulse + joint.lower_impulse - joint.upper_impulse + joint.motor_impulse,
103            );
104        }
105
106        JointPayload::Motor(joint) => {
107            linear_impulse = length(add(
108                joint.linear_velocity_impulse,
109                joint.linear_spring_impulse,
110            ));
111            angular_impulse =
112                abs_float(joint.angular_velocity_impulse + joint.angular_spring_impulse);
113        }
114
115        JointPayload::Prismatic(joint) => {
116            let perp_impulse = joint.impulse.x;
117            let axial_impulse = joint.motor_impulse + joint.lower_impulse - joint.upper_impulse;
118            linear_impulse = (perp_impulse * perp_impulse + axial_impulse * axial_impulse).sqrt();
119            angular_impulse = abs_float(joint.impulse.y);
120        }
121
122        JointPayload::Revolute(joint) => {
123            linear_impulse = length(joint.linear_impulse);
124            angular_impulse =
125                abs_float(joint.motor_impulse + joint.lower_impulse - joint.upper_impulse);
126        }
127
128        JointPayload::Weld(joint) => {
129            linear_impulse = length(joint.linear_impulse);
130            angular_impulse = abs_float(joint.angular_impulse);
131        }
132
133        JointPayload::Wheel(joint) => {
134            let perp_impulse = joint.perp_impulse;
135            let axial_impulse = joint.spring_impulse + joint.lower_impulse - joint.upper_impulse;
136            linear_impulse = (perp_impulse * perp_impulse + axial_impulse * axial_impulse).sqrt();
137            angular_impulse = abs_float(joint.motor_impulse);
138        }
139
140        JointPayload::Filter => {}
141    }
142
143    (
144        linear_impulse * inv_time_step,
145        angular_impulse * inv_time_step,
146    )
147}
148
149/// (static b2GetJointConstraintForce — takes the raw joint index)
150pub(crate) fn get_joint_constraint_force(world: &World, joint_index: i32) -> Vec2 {
151    let base = get_joint_sim_ref(world, joint_index);
152
153    match world.joints[joint_index as usize].type_ {
154        JointType::Distance => get_distance_joint_force(world, base),
155        JointType::Motor => get_motor_joint_force(world, base),
156        JointType::Filter => VEC2_ZERO,
157        JointType::Prismatic => get_prismatic_joint_force(world, base),
158        JointType::Revolute => get_revolute_joint_force(world, base),
159        JointType::Weld => get_weld_joint_force(world, base),
160        JointType::Wheel => get_wheel_joint_force(world, base),
161    }
162}
163
164/// (static b2GetJointConstraintTorque)
165pub(crate) fn get_joint_constraint_torque(world: &World, joint_index: i32) -> f32 {
166    let base = get_joint_sim_ref(world, joint_index);
167
168    match world.joints[joint_index as usize].type_ {
169        JointType::Distance => 0.0,
170        JointType::Motor => get_motor_joint_torque(world, base),
171        JointType::Filter => 0.0,
172        JointType::Prismatic => get_prismatic_joint_torque(world, base),
173        JointType::Revolute => get_revolute_joint_torque(world, base),
174        JointType::Weld => get_weld_joint_torque(world, base),
175        JointType::Wheel => get_wheel_joint_torque(world, base),
176    }
177}