Skip to main content

box2d_rust/prismatic_joint/
mod.rs

1// Port of prismatic_joint.c: public accessors, force/torque reporting, and
2// debug draw. The prepare/warm-start/solve simulation functions live in
3// solve.rs.
4//
5// Same conventions as distance_joint.rs.
6//
7// SPDX-FileCopyrightText: 2023 Erin Catto
8// SPDX-License-Identifier: MIT
9//
10
11use crate::body::get_body_transform;
12use crate::id::JointId;
13use crate::joint::{
14    get_joint_full_id, get_joint_sim_check_type, get_joint_sim_check_type_ref, get_joint_sim_ref,
15    JointSim, JointType,
16};
17use crate::math_functions::WorldTransform;
18use crate::math_functions::{
19    add, cross_sv, dot, left_perp, max_float, min_float, mul_sv, rotate_vector, sub, sub_pos,
20    to_relative_transform, transform_point, Vec2, VEC2_ZERO,
21};
22use crate::solver_set::AWAKE_SET;
23use crate::world::World;
24
25mod solve;
26
27pub use solve::*;
28
29/// (b2PrismaticJoint_EnableSpring)
30pub fn prismatic_joint_enable_spring(world: &mut World, joint_id: JointId, enable_spring: bool) {
31    crate::recording::record_op(world, |rec, _| {
32        crate::recording::write_joint_bool(
33            rec,
34            crate::recording::OP_PRISMATIC_ENABLE_SPRING,
35            joint_id,
36            enable_spring,
37        )
38    });
39    let joint = get_joint_sim_check_type(world, joint_id, JointType::Prismatic);
40    let prismatic = joint.prismatic_mut();
41    if enable_spring != prismatic.enable_spring {
42        prismatic.enable_spring = enable_spring;
43        prismatic.spring_impulse = 0.0;
44    }
45}
46
47/// (b2PrismaticJoint_IsSpringEnabled)
48pub fn prismatic_joint_is_spring_enabled(world: &World, joint_id: JointId) -> bool {
49    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
50    joint.prismatic().enable_spring
51}
52
53/// (b2PrismaticJoint_SetSpringHertz)
54pub fn prismatic_joint_set_spring_hertz(world: &mut World, joint_id: JointId, hertz: f32) {
55    crate::recording::record_op(world, |rec, _| {
56        crate::recording::write_joint_f32(
57            rec,
58            crate::recording::OP_PRISMATIC_SET_SPRING_HERTZ,
59            joint_id,
60            hertz,
61        )
62    });
63    let joint = get_joint_sim_check_type(world, joint_id, JointType::Prismatic);
64    joint.prismatic_mut().hertz = hertz;
65}
66
67/// (b2PrismaticJoint_GetSpringHertz)
68pub fn prismatic_joint_get_spring_hertz(world: &World, joint_id: JointId) -> f32 {
69    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
70    joint.prismatic().hertz
71}
72
73/// (b2PrismaticJoint_SetSpringDampingRatio)
74pub fn prismatic_joint_set_spring_damping_ratio(
75    world: &mut World,
76    joint_id: JointId,
77    damping_ratio: f32,
78) {
79    crate::recording::record_op(world, |rec, _| {
80        crate::recording::write_joint_f32(
81            rec,
82            crate::recording::OP_PRISMATIC_SET_SPRING_DAMPING_RATIO,
83            joint_id,
84            damping_ratio,
85        )
86    });
87    let joint = get_joint_sim_check_type(world, joint_id, JointType::Prismatic);
88    joint.prismatic_mut().damping_ratio = damping_ratio;
89}
90
91/// (b2PrismaticJoint_GetSpringDampingRatio)
92pub fn prismatic_joint_get_spring_damping_ratio(world: &World, joint_id: JointId) -> f32 {
93    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
94    joint.prismatic().damping_ratio
95}
96
97/// (b2PrismaticJoint_SetTargetTranslation)
98pub fn prismatic_joint_set_target_translation(
99    world: &mut World,
100    joint_id: JointId,
101    translation: f32,
102) {
103    crate::recording::record_op(world, |rec, _| {
104        crate::recording::write_joint_f32(
105            rec,
106            crate::recording::OP_PRISMATIC_SET_TARGET_TRANSLATION,
107            joint_id,
108            translation,
109        )
110    });
111    let joint = get_joint_sim_check_type(world, joint_id, JointType::Prismatic);
112    joint.prismatic_mut().target_translation = translation;
113}
114
115/// (b2PrismaticJoint_GetTargetTranslation)
116pub fn prismatic_joint_get_target_translation(world: &World, joint_id: JointId) -> f32 {
117    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
118    joint.prismatic().target_translation
119}
120
121/// (b2PrismaticJoint_EnableLimit)
122pub fn prismatic_joint_enable_limit(world: &mut World, joint_id: JointId, enable_limit: bool) {
123    crate::recording::record_op(world, |rec, _| {
124        crate::recording::write_joint_bool(
125            rec,
126            crate::recording::OP_PRISMATIC_ENABLE_LIMIT,
127            joint_id,
128            enable_limit,
129        )
130    });
131    let joint = get_joint_sim_check_type(world, joint_id, JointType::Prismatic);
132    let prismatic = joint.prismatic_mut();
133    if enable_limit != prismatic.enable_limit {
134        prismatic.enable_limit = enable_limit;
135        prismatic.lower_impulse = 0.0;
136        prismatic.upper_impulse = 0.0;
137    }
138}
139
140/// (b2PrismaticJoint_IsLimitEnabled)
141pub fn prismatic_joint_is_limit_enabled(world: &World, joint_id: JointId) -> bool {
142    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
143    joint.prismatic().enable_limit
144}
145
146/// (b2PrismaticJoint_GetLowerLimit)
147pub fn prismatic_joint_get_lower_limit(world: &World, joint_id: JointId) -> f32 {
148    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
149    joint.prismatic().lower_translation
150}
151
152/// (b2PrismaticJoint_GetUpperLimit)
153pub fn prismatic_joint_get_upper_limit(world: &World, joint_id: JointId) -> f32 {
154    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
155    joint.prismatic().upper_translation
156}
157
158/// (b2PrismaticJoint_SetLimits)
159pub fn prismatic_joint_set_limits(world: &mut World, joint_id: JointId, lower: f32, upper: f32) {
160    crate::recording::record_op(world, |rec, _| {
161        crate::recording::write_joint_f32_pair(
162            rec,
163            crate::recording::OP_PRISMATIC_SET_LIMITS,
164            joint_id,
165            lower,
166            upper,
167        )
168    });
169    debug_assert!(lower <= upper);
170
171    let joint = get_joint_sim_check_type(world, joint_id, JointType::Prismatic);
172    let prismatic = joint.prismatic_mut();
173    if lower != prismatic.lower_translation || upper != prismatic.upper_translation {
174        prismatic.lower_translation = min_float(lower, upper);
175        prismatic.upper_translation = max_float(lower, upper);
176        prismatic.lower_impulse = 0.0;
177        prismatic.upper_impulse = 0.0;
178    }
179}
180
181/// (b2PrismaticJoint_EnableMotor)
182pub fn prismatic_joint_enable_motor(world: &mut World, joint_id: JointId, enable_motor: bool) {
183    crate::recording::record_op(world, |rec, _| {
184        crate::recording::write_joint_bool(
185            rec,
186            crate::recording::OP_PRISMATIC_ENABLE_MOTOR,
187            joint_id,
188            enable_motor,
189        )
190    });
191    let joint = get_joint_sim_check_type(world, joint_id, JointType::Prismatic);
192    let prismatic = joint.prismatic_mut();
193    if enable_motor != prismatic.enable_motor {
194        prismatic.enable_motor = enable_motor;
195        prismatic.motor_impulse = 0.0;
196    }
197}
198
199/// (b2PrismaticJoint_IsMotorEnabled)
200pub fn prismatic_joint_is_motor_enabled(world: &World, joint_id: JointId) -> bool {
201    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
202    joint.prismatic().enable_motor
203}
204
205/// (b2PrismaticJoint_SetMotorSpeed)
206pub fn prismatic_joint_set_motor_speed(world: &mut World, joint_id: JointId, motor_speed: f32) {
207    crate::recording::record_op(world, |rec, _| {
208        crate::recording::write_joint_f32(
209            rec,
210            crate::recording::OP_PRISMATIC_SET_MOTOR_SPEED,
211            joint_id,
212            motor_speed,
213        )
214    });
215    let joint = get_joint_sim_check_type(world, joint_id, JointType::Prismatic);
216    joint.prismatic_mut().motor_speed = motor_speed;
217}
218
219/// (b2PrismaticJoint_GetMotorSpeed)
220pub fn prismatic_joint_get_motor_speed(world: &World, joint_id: JointId) -> f32 {
221    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
222    joint.prismatic().motor_speed
223}
224
225/// (b2PrismaticJoint_GetMotorForce)
226pub fn prismatic_joint_get_motor_force(world: &World, joint_id: JointId) -> f32 {
227    let base = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
228    world.inv_h * base.prismatic().motor_impulse
229}
230
231/// (b2PrismaticJoint_SetMaxMotorForce)
232pub fn prismatic_joint_set_max_motor_force(world: &mut World, joint_id: JointId, force: f32) {
233    crate::recording::record_op(world, |rec, _| {
234        crate::recording::write_joint_f32(
235            rec,
236            crate::recording::OP_PRISMATIC_SET_MAX_MOTOR_FORCE,
237            joint_id,
238            force,
239        )
240    });
241    let joint = get_joint_sim_check_type(world, joint_id, JointType::Prismatic);
242    joint.prismatic_mut().max_motor_force = force;
243}
244
245/// (b2PrismaticJoint_GetMaxMotorForce)
246pub fn prismatic_joint_get_max_motor_force(world: &World, joint_id: JointId) -> f32 {
247    let joint = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
248    joint.prismatic().max_motor_force
249}
250
251/// (b2PrismaticJoint_GetTranslation)
252pub fn prismatic_joint_get_translation(world: &World, joint_id: JointId) -> f32 {
253    let joint_sim = get_joint_sim_check_type_ref(world, joint_id, JointType::Prismatic);
254
255    // Relative to body A so the difference stays in float precision far from
256    // the origin
257    let wxf_a = get_body_transform(world, joint_sim.body_id_a);
258    let transform_a = to_relative_transform(wxf_a, wxf_a.p);
259    let transform_b =
260        to_relative_transform(get_body_transform(world, joint_sim.body_id_b), wxf_a.p);
261
262    let local_axis_a = rotate_vector(joint_sim.local_frame_a.q, Vec2 { x: 1.0, y: 0.0 });
263    let axis_a = rotate_vector(transform_a.q, local_axis_a);
264    let p_a = transform_point(transform_a, joint_sim.local_frame_a.p);
265    let p_b = transform_point(transform_b, joint_sim.local_frame_b.p);
266    let d = sub(p_b, p_a);
267    dot(d, axis_a)
268}
269
270/// (b2PrismaticJoint_GetSpeed)
271pub fn prismatic_joint_get_speed(world: &World, joint_id: JointId) -> f32 {
272    let joint_index = get_joint_full_id(world, joint_id);
273    debug_assert!(world.joints[joint_index as usize].type_ == JointType::Prismatic);
274    let base = get_joint_sim_ref(world, joint_index);
275    debug_assert!(base.joint_type() == JointType::Prismatic);
276
277    let body_a = &world.bodies[base.body_id_a as usize];
278    let body_b = &world.bodies[base.body_id_b as usize];
279    let body_sim_a =
280        &world.solver_sets[body_a.set_index as usize].body_sims[body_a.local_index as usize];
281    let body_sim_b =
282        &world.solver_sets[body_b.set_index as usize].body_sims[body_b.local_index as usize];
283    let body_state_a = if body_a.set_index == AWAKE_SET {
284        Some(&world.solver_sets[AWAKE_SET as usize].body_states[body_a.local_index as usize])
285    } else {
286        None
287    };
288    let body_state_b = if body_b.set_index == AWAKE_SET {
289        Some(&world.solver_sets[AWAKE_SET as usize].body_states[body_b.local_index as usize])
290    } else {
291        None
292    };
293
294    let q_a = body_sim_a.transform.q;
295    let q_b = body_sim_b.transform.q;
296
297    let local_axis_a = rotate_vector(base.local_frame_a.q, Vec2 { x: 1.0, y: 0.0 });
298    let axis_a = rotate_vector(q_a, local_axis_a);
299    let r_a = rotate_vector(q_a, sub(base.local_frame_a.p, body_sim_a.local_center));
300    let r_b = rotate_vector(q_b, sub(base.local_frame_b.p, body_sim_b.local_center));
301
302    // Difference the centers in double so the speed stays exact far from the
303    // origin
304    let dc = sub_pos(body_sim_b.center, body_sim_a.center);
305    let d = add(dc, sub(r_b, r_a));
306
307    let v_a = body_state_a.map_or(VEC2_ZERO, |s| s.linear_velocity);
308    let v_b = body_state_b.map_or(VEC2_ZERO, |s| s.linear_velocity);
309    let w_a = body_state_a.map_or(0.0, |s| s.angular_velocity);
310    let w_b = body_state_b.map_or(0.0, |s| s.angular_velocity);
311
312    let v_rel = sub(add(v_b, cross_sv(w_b, r_b)), add(v_a, cross_sv(w_a, r_a)));
313    dot(d, cross_sv(w_a, axis_a)) + dot(axis_a, v_rel)
314}
315
316/// (b2GetPrismaticJointForce)
317pub fn get_prismatic_joint_force(world: &World, base: &JointSim) -> Vec2 {
318    let q_a = get_body_transform(world, base.body_id_a).q;
319
320    let joint = base.prismatic();
321
322    let local_axis_a = rotate_vector(base.local_frame_a.q, Vec2 { x: 1.0, y: 0.0 });
323    let axis_a = rotate_vector(q_a, local_axis_a);
324    let perp_a = left_perp(axis_a);
325
326    let inv_h = world.inv_h;
327    let perp_force = inv_h * joint.impulse.x;
328    let axial_force = inv_h * (joint.motor_impulse + joint.lower_impulse - joint.upper_impulse);
329
330    add(mul_sv(perp_force, perp_a), mul_sv(axial_force, axis_a))
331}
332
333/// (b2GetPrismaticJointTorque)
334pub fn get_prismatic_joint_torque(world: &World, base: &JointSim) -> f32 {
335    world.inv_h * base.prismatic().impulse.y
336}
337
338// Linear constraint (point-to-line)
339// d = pB - pA = xB + rB - xA - rA
340// C = dot(perp, d)
341// Cdot = dot(d, cross(wA, perp)) + dot(perp, vB + cross(wB, rB) - vA - cross(wA, rA))
342//      = -dot(perp, vA) - dot(cross(rA + d, perp), wA) + dot(perp, vB) + dot(cross(rB, perp), vB)
343// J = [-perp, -cross(rA + d, perp), perp, cross(rB, perp)]
344//
345// Angular constraint
346// C = aB - aA + a_initial
347// Cdot = wB - wA
348// J = [0 0 -1 0 0 1]
349//
350// K = J * invM * JT
351//
352// J = [-a -sA a sB]
353//     [0  -1  0  1]
354// a = perp
355// sA = cross(rA + d, a) = cross(pB - xA, a)
356// sB = cross(rB, a) = cross(pB - xB, a)
357//
358// Motor/Limit linear constraint
359// C = dot(axA, d)
360// Cdot = -dot(axA, vA) - dot(cross(rA + d, axA), wA) + dot(axA, vB) + dot(cross(rB, axA), vB)
361// J = [-axA -cross(rA + d, axA) axA cross(rB, ax1)]
362//
363// Predictive limit is applied even when the limit is not active.
364// Prevents a constraint speed that can lead to a constraint error in one time step.
365// Want C2 = C1 + h * Cdot >= 0
366// Or:
367// Cdot + C1/h >= 0
368// I do not apply a negative constraint error because that is handled in position correction.
369// So:
370// Cdot + max(C1, 0)/h >= 0
371//
372// Block Solver
373// We develop a block solver that includes the angular and linear constraints.
374// This makes the limit stiffer.
375//
376// The Jacobian has 2 rows:
377// J = [-uT -s1 uT s2] // linear
378//     [0   -1   0  1] // angular
379//
380// u = perp
381// s1 = cross(d + r1, u), s2 = cross(r2, u)
382// a1 = cross(d + r1, v), a2 = cross(r2, v)
383
384/// (b2DrawPrismaticJoint)
385pub fn draw_prismatic_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::math_functions::{
394        left_perp, mul_sv, neg, offset_pos, offset_world_transform, rotate_vector, Vec2,
395    };
396
397    debug_assert!(base.joint_type() == JointType::Prismatic);
398
399    let joint = base.prismatic();
400
401    let frame_a = offset_world_transform(transform_a, base.local_frame_a);
402    let frame_b = offset_world_transform(transform_b, base.local_frame_b);
403    let axis_a = rotate_vector(frame_a.q, Vec2 { x: 1.0, y: 0.0 });
404
405    draw.draw_line(frame_a.p, frame_b.p, HexColor::DIM_GRAY);
406
407    if joint.enable_limit {
408        let b = 0.25 * draw_scale;
409        let lower = offset_pos(frame_a.p, mul_sv(joint.lower_translation, axis_a));
410        let upper = offset_pos(frame_a.p, mul_sv(joint.upper_translation, axis_a));
411        let perp = left_perp(axis_a);
412        draw.draw_line(lower, upper, HexColor::GRAY);
413        draw.draw_line(
414            offset_pos(lower, mul_sv(-b, perp)),
415            offset_pos(lower, mul_sv(b, perp)),
416            HexColor::GREEN,
417        );
418        draw.draw_line(
419            offset_pos(upper, mul_sv(-b, perp)),
420            offset_pos(upper, mul_sv(b, perp)),
421            HexColor::RED,
422        );
423    } else {
424        draw.draw_line(
425            offset_pos(frame_a.p, neg(axis_a)),
426            offset_pos(frame_a.p, axis_a),
427            HexColor::GRAY,
428        );
429    }
430
431    if joint.enable_spring {
432        let p = offset_pos(frame_a.p, mul_sv(joint.target_translation, axis_a));
433        draw.draw_point(p, 8.0, HexColor::VIOLET);
434    }
435
436    draw.draw_point(frame_a.p, 5.0, HexColor::GRAY);
437    draw.draw_point(frame_b.p, 5.0, HexColor::BLUE);
438}