Skip to main content

box2d_rust/
weld_joint.rs

1// Port of weld_joint.c: public accessors, force/torque reporting, and the
2// prepare/warm-start/solve simulation functions.
3//
4// Same conventions as distance_joint.rs. The C B2_WELD_BLOCK_SOLVE branch is
5// compiled out upstream (block solve doesn't work correctly with mixed
6// stiffness values) and is not ported.
7//
8// SPDX-FileCopyrightText: 2023 Erin Catto
9// SPDX-License-Identifier: MIT
10//
11// bring-up: prepare/warm-start/solve are called by the solver slice.
12
13use crate::body::{body_flags, BodyState, IDENTITY_BODY_STATE};
14use crate::core::NULL_INDEX;
15use crate::id::JointId;
16use crate::joint::{get_joint_sim_check_type, get_joint_sim_check_type_ref, JointSim, JointType};
17use crate::math_functions::WorldTransform;
18use crate::math_functions::{
19    add, cross, cross_sv, inv_mul_rot, is_valid_float, is_valid_vec2, mul_add, mul_rot, mul_sub,
20    mul_sv, rot_get_angle, rotate_vector, solve_22, sub, sub_pos, Vec2, MAT22_ZERO, VEC2_ZERO,
21};
22use crate::solver::{make_soft, StepContext};
23use crate::solver_set::AWAKE_SET;
24use crate::world::World;
25
26/// (b2WeldJoint_SetLinearHertz)
27pub fn weld_joint_set_linear_hertz(world: &mut World, joint_id: JointId, hertz: f32) {
28    crate::recording::record_op(world, |rec, _| {
29        crate::recording::write_joint_f32(
30            rec,
31            crate::recording::OP_WELD_SET_LINEAR_HERTZ,
32            joint_id,
33            hertz,
34        )
35    });
36    debug_assert!(is_valid_float(hertz) && hertz >= 0.0);
37    let joint = get_joint_sim_check_type(world, joint_id, JointType::Weld);
38    joint.weld_mut().linear_hertz = hertz;
39}
40
41/// (b2WeldJoint_GetLinearHertz)
42pub fn weld_joint_get_linear_hertz(world: &World, joint_id: JointId) -> f32 {
43    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Weld);
44    joint.weld().linear_hertz
45}
46
47/// (b2WeldJoint_SetLinearDampingRatio)
48pub fn weld_joint_set_linear_damping_ratio(
49    world: &mut World,
50    joint_id: JointId,
51    damping_ratio: f32,
52) {
53    crate::recording::record_op(world, |rec, _| {
54        crate::recording::write_joint_f32(
55            rec,
56            crate::recording::OP_WELD_SET_LINEAR_DAMPING_RATIO,
57            joint_id,
58            damping_ratio,
59        )
60    });
61    debug_assert!(is_valid_float(damping_ratio) && damping_ratio >= 0.0);
62    let joint = get_joint_sim_check_type(world, joint_id, JointType::Weld);
63    joint.weld_mut().linear_damping_ratio = damping_ratio;
64}
65
66/// (b2WeldJoint_GetLinearDampingRatio)
67pub fn weld_joint_get_linear_damping_ratio(world: &World, joint_id: JointId) -> f32 {
68    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Weld);
69    joint.weld().linear_damping_ratio
70}
71
72/// (b2WeldJoint_SetAngularHertz)
73pub fn weld_joint_set_angular_hertz(world: &mut World, joint_id: JointId, hertz: f32) {
74    crate::recording::record_op(world, |rec, _| {
75        crate::recording::write_joint_f32(
76            rec,
77            crate::recording::OP_WELD_SET_ANGULAR_HERTZ,
78            joint_id,
79            hertz,
80        )
81    });
82    debug_assert!(is_valid_float(hertz) && hertz >= 0.0);
83    let joint = get_joint_sim_check_type(world, joint_id, JointType::Weld);
84    joint.weld_mut().angular_hertz = hertz;
85}
86
87/// (b2WeldJoint_GetAngularHertz)
88pub fn weld_joint_get_angular_hertz(world: &World, joint_id: JointId) -> f32 {
89    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Weld);
90    joint.weld().angular_hertz
91}
92
93/// (b2WeldJoint_SetAngularDampingRatio)
94pub fn weld_joint_set_angular_damping_ratio(
95    world: &mut World,
96    joint_id: JointId,
97    damping_ratio: f32,
98) {
99    crate::recording::record_op(world, |rec, _| {
100        crate::recording::write_joint_f32(
101            rec,
102            crate::recording::OP_WELD_SET_ANGULAR_DAMPING_RATIO,
103            joint_id,
104            damping_ratio,
105        )
106    });
107    debug_assert!(is_valid_float(damping_ratio) && damping_ratio >= 0.0);
108    let joint = get_joint_sim_check_type(world, joint_id, JointType::Weld);
109    joint.weld_mut().angular_damping_ratio = damping_ratio;
110}
111
112/// (b2WeldJoint_GetAngularDampingRatio)
113pub fn weld_joint_get_angular_damping_ratio(world: &World, joint_id: JointId) -> f32 {
114    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Weld);
115    joint.weld().angular_damping_ratio
116}
117
118/// (b2GetWeldJointForce)
119pub fn get_weld_joint_force(world: &World, base: &JointSim) -> Vec2 {
120    mul_sv(world.inv_h, base.weld().linear_impulse)
121}
122
123/// (b2GetWeldJointTorque)
124pub fn get_weld_joint_torque(world: &World, base: &JointSim) -> f32 {
125    world.inv_h * base.weld().angular_impulse
126}
127
128// Point-to-point constraint
129// C = p2 - p1
130// Cdot = v2 - v1
131//      = v2 + cross(w2, r2) - v1 - cross(w1, r1)
132// J = [-E -r1_skew E r2_skew ]
133// Identity used:
134// w k % (rx i + ry j) = w * (-ry i + rx j)
135//
136// Angle constraint
137// C = angle2 - angle1 - referenceAngle
138// Cdot = w2 - w1
139// J = [0 0 -1 0 0 1]
140// K = invI1 + invI2
141
142/// (b2PrepareWeldJoint)
143pub fn prepare_weld_joint(world: &World, base: &mut JointSim, context: &StepContext) {
144    debug_assert!(base.joint_type() == JointType::Weld);
145
146    // chase body id to the solver set where the body lives
147    let id_a = base.body_id_a;
148    let id_b = base.body_id_b;
149
150    let body_a = &world.bodies[id_a as usize];
151    let body_b = &world.bodies[id_b as usize];
152
153    debug_assert!(body_a.set_index == AWAKE_SET || body_b.set_index == AWAKE_SET);
154
155    let body_sim_a =
156        &world.solver_sets[body_a.set_index as usize].body_sims[body_a.local_index as usize];
157    let body_sim_b =
158        &world.solver_sets[body_b.set_index as usize].body_sims[body_b.local_index as usize];
159
160    let m_a = body_sim_a.inv_mass;
161    let i_a = body_sim_a.inv_inertia;
162    let m_b = body_sim_b.inv_mass;
163    let i_b = body_sim_b.inv_inertia;
164
165    base.inv_mass_a = m_a;
166    base.inv_mass_b = m_b;
167    base.inv_i_a = i_a;
168    base.inv_i_b = i_b;
169
170    let local_frame_a = base.local_frame_a;
171    let local_frame_b = base.local_frame_b;
172    let constraint_softness = base.constraint_softness;
173
174    let index_a = if body_a.set_index == AWAKE_SET {
175        body_a.local_index
176    } else {
177        NULL_INDEX
178    };
179    let index_b = if body_b.set_index == AWAKE_SET {
180        body_b.local_index
181    } else {
182        NULL_INDEX
183    };
184
185    let joint = base.weld_mut();
186    joint.index_a = index_a;
187    joint.index_b = index_b;
188
189    // Compute joint anchor frames with world space rotation, relative to
190    // center of mass
191    joint.frame_a.q = mul_rot(body_sim_a.transform.q, local_frame_a.q);
192    joint.frame_a.p = rotate_vector(
193        body_sim_a.transform.q,
194        sub(local_frame_a.p, body_sim_a.local_center),
195    );
196    joint.frame_b.q = mul_rot(body_sim_b.transform.q, local_frame_b.q);
197    joint.frame_b.p = rotate_vector(
198        body_sim_b.transform.q,
199        sub(local_frame_b.p, body_sim_b.local_center),
200    );
201
202    // Compute the initial center delta. Incremental position updates are
203    // relative to this.
204    joint.delta_center = sub_pos(body_sim_b.center, body_sim_a.center);
205
206    let ka = i_a + i_b;
207    joint.axial_mass = if ka > 0.0 { 1.0 / ka } else { 0.0 };
208
209    if joint.linear_hertz == 0.0 {
210        joint.linear_spring = constraint_softness;
211    } else {
212        joint.linear_spring = make_soft(joint.linear_hertz, joint.linear_damping_ratio, context.h);
213    }
214
215    if joint.angular_hertz == 0.0 {
216        joint.angular_spring = constraint_softness;
217    } else {
218        joint.angular_spring =
219            make_soft(joint.angular_hertz, joint.angular_damping_ratio, context.h);
220    }
221
222    if !context.enable_warm_starting {
223        joint.linear_impulse = VEC2_ZERO;
224        joint.angular_impulse = 0.0;
225    }
226}
227
228/// (b2WarmStartWeldJoint)
229pub fn warm_start_weld_joint(base: &mut JointSim, states: &mut [BodyState]) {
230    let m_a = base.inv_mass_a;
231    let m_b = base.inv_mass_b;
232    let i_a = base.inv_i_a;
233    let i_b = base.inv_i_b;
234
235    let joint = base.weld_mut();
236
237    // dummy state for static bodies
238    let mut state_a = if joint.index_a == NULL_INDEX {
239        IDENTITY_BODY_STATE
240    } else {
241        states[joint.index_a as usize]
242    };
243    let mut state_b = if joint.index_b == NULL_INDEX {
244        IDENTITY_BODY_STATE
245    } else {
246        states[joint.index_b as usize]
247    };
248
249    let r_a = rotate_vector(state_a.delta_rotation, joint.frame_a.p);
250    let r_b = rotate_vector(state_b.delta_rotation, joint.frame_b.p);
251
252    if state_a.flags & body_flags::DYNAMIC_FLAG != 0 {
253        state_a.linear_velocity = mul_sub(state_a.linear_velocity, m_a, joint.linear_impulse);
254        state_a.angular_velocity -=
255            i_a * (cross(r_a, joint.linear_impulse) + joint.angular_impulse);
256        states[joint.index_a as usize] = state_a;
257    }
258
259    if state_b.flags & body_flags::DYNAMIC_FLAG != 0 {
260        state_b.linear_velocity = mul_add(state_b.linear_velocity, m_b, joint.linear_impulse);
261        state_b.angular_velocity +=
262            i_b * (cross(r_b, joint.linear_impulse) + joint.angular_impulse);
263        states[joint.index_b as usize] = state_b;
264    }
265}
266
267/// (b2SolveWeldJoint)
268pub fn solve_weld_joint(
269    base: &mut JointSim,
270    _context: &StepContext,
271    states: &mut [BodyState],
272    use_bias: bool,
273) {
274    debug_assert!(base.joint_type() == JointType::Weld);
275
276    let m_a = base.inv_mass_a;
277    let m_b = base.inv_mass_b;
278    let i_a = base.inv_i_a;
279    let i_b = base.inv_i_b;
280
281    let joint = base.weld_mut();
282
283    // dummy state for static bodies
284    let mut state_a = if joint.index_a == NULL_INDEX {
285        IDENTITY_BODY_STATE
286    } else {
287        states[joint.index_a as usize]
288    };
289    let mut state_b = if joint.index_b == NULL_INDEX {
290        IDENTITY_BODY_STATE
291    } else {
292        states[joint.index_b as usize]
293    };
294
295    let mut v_a = state_a.linear_velocity;
296    let mut w_a = state_a.angular_velocity;
297    let mut v_b = state_b.linear_velocity;
298    let mut w_b = state_b.angular_velocity;
299
300    // angular constraint
301    {
302        let q_a = mul_rot(state_a.delta_rotation, joint.frame_a.q);
303        let q_b = mul_rot(state_b.delta_rotation, joint.frame_b.q);
304        let rel_q = inv_mul_rot(q_a, q_b);
305        let joint_angle = rot_get_angle(rel_q);
306
307        let mut bias = 0.0;
308        let mut mass_scale = 1.0;
309        let mut impulse_scale = 0.0;
310        if use_bias || joint.angular_hertz > 0.0 {
311            let c = joint_angle;
312            bias = joint.angular_spring.bias_rate * c;
313            mass_scale = joint.angular_spring.mass_scale;
314            impulse_scale = joint.angular_spring.impulse_scale;
315        }
316
317        let c_dot = w_b - w_a;
318        let impulse =
319            -mass_scale * joint.axial_mass * (c_dot + bias) - impulse_scale * joint.angular_impulse;
320        joint.angular_impulse += impulse;
321
322        w_a -= i_a * impulse;
323        w_b += i_b * impulse;
324    }
325
326    // linear constraint
327    {
328        let r_a = rotate_vector(state_a.delta_rotation, joint.frame_a.p);
329        let r_b = rotate_vector(state_b.delta_rotation, joint.frame_b.p);
330
331        let mut bias = VEC2_ZERO;
332        let mut mass_scale = 1.0;
333        let mut impulse_scale = 0.0;
334        if use_bias || joint.linear_hertz > 0.0 {
335            let dc_a = state_a.delta_position;
336            let dc_b = state_b.delta_position;
337            let c = add(add(sub(dc_b, dc_a), sub(r_b, r_a)), joint.delta_center);
338
339            bias = mul_sv(joint.linear_spring.bias_rate, c);
340            mass_scale = joint.linear_spring.mass_scale;
341            impulse_scale = joint.linear_spring.impulse_scale;
342        }
343
344        let c_dot = sub(add(v_b, cross_sv(w_b, r_b)), add(v_a, cross_sv(w_a, r_a)));
345
346        let mut k = MAT22_ZERO;
347        k.cx.x = m_a + m_b + r_a.y * r_a.y * i_a + r_b.y * r_b.y * i_b;
348        k.cy.x = -r_a.y * r_a.x * i_a - r_b.y * r_b.x * i_b;
349        k.cx.y = k.cy.x;
350        k.cy.y = m_a + m_b + r_a.x * r_a.x * i_a + r_b.x * r_b.x * i_b;
351        let b = solve_22(k, add(c_dot, bias));
352
353        let impulse = Vec2 {
354            x: -mass_scale * b.x - impulse_scale * joint.linear_impulse.x,
355            y: -mass_scale * b.y - impulse_scale * joint.linear_impulse.y,
356        };
357
358        joint.linear_impulse = add(joint.linear_impulse, impulse);
359
360        v_a = mul_sub(v_a, m_a, impulse);
361        w_a -= i_a * cross(r_a, impulse);
362        v_b = mul_add(v_b, m_b, impulse);
363        w_b += i_b * cross(r_b, impulse);
364    }
365
366    debug_assert!(is_valid_vec2(v_a));
367    debug_assert!(is_valid_float(w_a));
368    debug_assert!(is_valid_vec2(v_b));
369    debug_assert!(is_valid_float(w_b));
370
371    if state_a.flags & body_flags::DYNAMIC_FLAG != 0 {
372        state_a.linear_velocity = v_a;
373        state_a.angular_velocity = w_a;
374        states[joint.index_a as usize] = state_a;
375    }
376
377    if state_b.flags & body_flags::DYNAMIC_FLAG != 0 {
378        state_b.linear_velocity = v_b;
379        state_b.angular_velocity = w_b;
380        states[joint.index_b as usize] = state_b;
381    }
382}
383
384/// (b2DrawWeldJoint)
385pub fn draw_weld_joint(
386    draw: &mut dyn crate::debug_draw::DebugDraw,
387    base: &JointSim,
388    transform_a: WorldTransform,
389    transform_b: WorldTransform,
390    draw_scale: f32,
391) {
392    use crate::debug_draw::HexColor;
393    use crate::geometry::make_box;
394    use crate::math_functions::offset_world_transform;
395
396    debug_assert!(base.joint_type() == JointType::Weld);
397
398    let frame_a = offset_world_transform(transform_a, base.local_frame_a);
399    let frame_b = offset_world_transform(transform_b, base.local_frame_b);
400
401    let box_ = make_box(0.25 * draw_scale, 0.125 * draw_scale);
402    draw.draw_polygon(frame_a, &box_.vertices[..4], HexColor::DARK_ORANGE);
403    draw.draw_polygon(frame_b, &box_.vertices[..4], HexColor::DARK_CYAN);
404}