1/*
2* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
3*
4* This software is provided 'as-is', without any express or implied
5* warranty. In no event will the authors be held liable for any damages
6* arising from the use of this software.
7* Permission is granted to anyone to use this software for any purpose,
8* including commercial applications, and to alter it and redistribute it
9* freely, subject to the following restrictions:
10* 1. The origin of this software must not be misrepresented; you must not
11* claim that you wrote the original software. If you use this software
12* in a product, an acknowledgment in the product documentation would be
13* appreciated but is not required.
14* 2. Altered source versions must be plainly marked as such, and must not be
15* misrepresented as being the original software.
16* 3. This notice may not be removed or altered from any source distribution.
17*/
18
19#include <Box2D/Dynamics/Joints/b2PrismaticJoint.h>
20#include <Box2D/Dynamics/b2Body.h>
21#include <Box2D/Dynamics/b2TimeStep.h>
22
23// Linear constraint (point-to-line)
24// d = p2 - p1 = x2 + r2 - x1 - r1
25// C = dot(perp, d)
26// Cdot = dot(d, cross(w1, perp)) + dot(perp, v2 + cross(w2, r2) - v1 - cross(w1, r1))
27// = -dot(perp, v1) - dot(cross(d + r1, perp), w1) + dot(perp, v2) + dot(cross(r2, perp), v2)
28// J = [-perp, -cross(d + r1, perp), perp, cross(r2,perp)]
29//
30// Angular constraint
31// C = a2 - a1 + a_initial
32// Cdot = w2 - w1
33// J = [0 0 -1 0 0 1]
34//
35// K = J * invM * JT
36//
37// J = [-a -s1 a s2]
38// [0 -1 0 1]
39// a = perp
40// s1 = cross(d + r1, a) = cross(p2 - x1, a)
41// s2 = cross(r2, a) = cross(p2 - x2, a)
42
43
44// Motor/Limit linear constraint
45// C = dot(ax1, d)
46// Cdot = = -dot(ax1, v1) - dot(cross(d + r1, ax1), w1) + dot(ax1, v2) + dot(cross(r2, ax1), v2)
47// J = [-ax1 -cross(d+r1,ax1) ax1 cross(r2,ax1)]
48
49// Block Solver
50// We develop a block solver that includes the joint limit. This makes the limit stiff (inelastic) even
51// when the mass has poor distribution (leading to large torques about the joint anchor points).
52//
53// The Jacobian has 3 rows:
54// J = [-uT -s1 uT s2] // linear
55// [0 -1 0 1] // angular
56// [-vT -a1 vT a2] // limit
57//
58// u = perp
59// v = axis
60// s1 = cross(d + r1, u), s2 = cross(r2, u)
61// a1 = cross(d + r1, v), a2 = cross(r2, v)
62
63// M * (v2 - v1) = JT * df
64// J * v2 = bias
65//
66// v2 = v1 + invM * JT * df
67// J * (v1 + invM * JT * df) = bias
68// K * df = bias - J * v1 = -Cdot
69// K = J * invM * JT
70// Cdot = J * v1 - bias
71//
72// Now solve for f2.
73// df = f2 - f1
74// K * (f2 - f1) = -Cdot
75// f2 = invK * (-Cdot) + f1
76//
77// Clamp accumulated limit impulse.
78// lower: f2(3) = max(f2(3), 0)
79// upper: f2(3) = min(f2(3), 0)
80//
81// Solve for correct f2(1:2)
82// K(1:2, 1:2) * f2(1:2) = -Cdot(1:2) - K(1:2,3) * f2(3) + K(1:2,1:3) * f1
83// = -Cdot(1:2) - K(1:2,3) * f2(3) + K(1:2,1:2) * f1(1:2) + K(1:2,3) * f1(3)
84// K(1:2, 1:2) * f2(1:2) = -Cdot(1:2) - K(1:2,3) * (f2(3) - f1(3)) + K(1:2,1:2) * f1(1:2)
85// f2(1:2) = invK(1:2,1:2) * (-Cdot(1:2) - K(1:2,3) * (f2(3) - f1(3))) + f1(1:2)
86//
87// Now compute impulse to be applied:
88// df = f2 - f1
89
90void b2PrismaticJointDef::Initialize(b2Body* bA, b2Body* bB, const b2Vec2& anchor, const b2Vec2& axis)
91{
92 bodyA = bA;
93 bodyB = bB;
94 localAnchorA = bodyA->GetLocalPoint(anchor);
95 localAnchorB = bodyB->GetLocalPoint(anchor);
96 localAxisA = bodyA->GetLocalVector(axis);
97 referenceAngle = bodyB->GetAngle() - bodyA->GetAngle();
98}
99
100b2PrismaticJoint::b2PrismaticJoint(const b2PrismaticJointDef* def)
101: b2Joint(def)
102{
103 m_localAnchorA = def->localAnchorA;
104 m_localAnchorB = def->localAnchorB;
105 m_localXAxisA = def->localAxisA;
106 m_localXAxisA.Normalize();
107 m_localYAxisA = b2Cross(1.0f, m_localXAxisA);
108 m_referenceAngle = def->referenceAngle;
109
110 m_impulse.SetZero();
111 m_motorMass = 0.0f;
112 m_motorImpulse = 0.0f;
113
114 m_lowerTranslation = def->lowerTranslation;
115 m_upperTranslation = def->upperTranslation;
116 m_maxMotorForce = def->maxMotorForce;
117 m_motorSpeed = def->motorSpeed;
118 m_enableLimit = def->enableLimit;
119 m_enableMotor = def->enableMotor;
120 m_limitState = e_inactiveLimit;
121
122 m_axis.SetZero();
123 m_perp.SetZero();
124}
125
126void b2PrismaticJoint::InitVelocityConstraints(const b2SolverData& data)
127{
128 m_indexA = m_bodyA->m_islandIndex;
129 m_indexB = m_bodyB->m_islandIndex;
130 m_localCenterA = m_bodyA->m_sweep.localCenter;
131 m_localCenterB = m_bodyB->m_sweep.localCenter;
132 m_invMassA = m_bodyA->m_invMass;
133 m_invMassB = m_bodyB->m_invMass;
134 m_invIA = m_bodyA->m_invI;
135 m_invIB = m_bodyB->m_invI;
136
137 b2Vec2 cA = data.positions[m_indexA].c;
138 float32 aA = data.positions[m_indexA].a;
139 b2Vec2 vA = data.velocities[m_indexA].v;
140 float32 wA = data.velocities[m_indexA].w;
141
142 b2Vec2 cB = data.positions[m_indexB].c;
143 float32 aB = data.positions[m_indexB].a;
144 b2Vec2 vB = data.velocities[m_indexB].v;
145 float32 wB = data.velocities[m_indexB].w;
146
147 b2Rot qA(aA), qB(aB);
148
149 // Compute the effective masses.
150 b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA);
151 b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB);
152 b2Vec2 d = (cB - cA) + rB - rA;
153
154 float32 mA = m_invMassA, mB = m_invMassB;
155 float32 iA = m_invIA, iB = m_invIB;
156
157 // Compute motor Jacobian and effective mass.
158 {
159 m_axis = b2Mul(qA, m_localXAxisA);
160 m_a1 = b2Cross(d + rA, m_axis);
161 m_a2 = b2Cross(rB, m_axis);
162
163 m_motorMass = mA + mB + iA * m_a1 * m_a1 + iB * m_a2 * m_a2;
164 if (m_motorMass > 0.0f)
165 {
166 m_motorMass = 1.0f / m_motorMass;
167 }
168 }
169
170 // Prismatic constraint.
171 {
172 m_perp = b2Mul(qA, m_localYAxisA);
173
174 m_s1 = b2Cross(d + rA, m_perp);
175 m_s2 = b2Cross(rB, m_perp);
176
177 float32 s1test;
178 s1test = b2Cross(rA, m_perp);
179
180 float32 k11 = mA + mB + iA * m_s1 * m_s1 + iB * m_s2 * m_s2;
181 float32 k12 = iA * m_s1 + iB * m_s2;
182 float32 k13 = iA * m_s1 * m_a1 + iB * m_s2 * m_a2;
183 float32 k22 = iA + iB;
184 if (k22 == 0.0f)
185 {
186 // For bodies with fixed rotation.
187 k22 = 1.0f;
188 }
189 float32 k23 = iA * m_a1 + iB * m_a2;
190 float32 k33 = mA + mB + iA * m_a1 * m_a1 + iB * m_a2 * m_a2;
191
192 m_K.ex.Set(k11, k12, k13);
193 m_K.ey.Set(k12, k22, k23);
194 m_K.ez.Set(k13, k23, k33);
195 }
196
197 // Compute motor and limit terms.
198 if (m_enableLimit)
199 {
200 float32 jointTranslation = b2Dot(m_axis, d);
201 if (b2Abs(m_upperTranslation - m_lowerTranslation) < 2.0f * b2_linearSlop)
202 {
203 m_limitState = e_equalLimits;
204 }
205 else if (jointTranslation <= m_lowerTranslation)
206 {
207 if (m_limitState != e_atLowerLimit)
208 {
209 m_limitState = e_atLowerLimit;
210 m_impulse.z = 0.0f;
211 }
212 }
213 else if (jointTranslation >= m_upperTranslation)
214 {
215 if (m_limitState != e_atUpperLimit)
216 {
217 m_limitState = e_atUpperLimit;
218 m_impulse.z = 0.0f;
219 }
220 }
221 else
222 {
223 m_limitState = e_inactiveLimit;
224 m_impulse.z = 0.0f;
225 }
226 }
227 else
228 {
229 m_limitState = e_inactiveLimit;
230 m_impulse.z = 0.0f;
231 }
232
233 if (m_enableMotor == false)
234 {
235 m_motorImpulse = 0.0f;
236 }
237
238 if (data.step.warmStarting)
239 {
240 // Account for variable time step.
241 m_impulse *= data.step.dtRatio;
242 m_motorImpulse *= data.step.dtRatio;
243
244 b2Vec2 P = m_impulse.x * m_perp + (m_motorImpulse + m_impulse.z) * m_axis;
245 float32 LA = m_impulse.x * m_s1 + m_impulse.y + (m_motorImpulse + m_impulse.z) * m_a1;
246 float32 LB = m_impulse.x * m_s2 + m_impulse.y + (m_motorImpulse + m_impulse.z) * m_a2;
247
248 vA -= mA * P;
249 wA -= iA * LA;
250
251 vB += mB * P;
252 wB += iB * LB;
253 }
254 else
255 {
256 m_impulse.SetZero();
257 m_motorImpulse = 0.0f;
258 }
259
260 data.velocities[m_indexA].v = vA;
261 data.velocities[m_indexA].w = wA;
262 data.velocities[m_indexB].v = vB;
263 data.velocities[m_indexB].w = wB;
264}
265
266void b2PrismaticJoint::SolveVelocityConstraints(const b2SolverData& data)
267{
268 b2Vec2 vA = data.velocities[m_indexA].v;
269 float32 wA = data.velocities[m_indexA].w;
270 b2Vec2 vB = data.velocities[m_indexB].v;
271 float32 wB = data.velocities[m_indexB].w;
272
273 float32 mA = m_invMassA, mB = m_invMassB;
274 float32 iA = m_invIA, iB = m_invIB;
275
276 // Solve linear motor constraint.
277 if (m_enableMotor && m_limitState != e_equalLimits)
278 {
279 float32 Cdot = b2Dot(m_axis, vB - vA) + m_a2 * wB - m_a1 * wA;
280 float32 impulse = m_motorMass * (m_motorSpeed - Cdot);
281 float32 oldImpulse = m_motorImpulse;
282 float32 maxImpulse = data.step.dt * m_maxMotorForce;
283 m_motorImpulse = b2Clamp(m_motorImpulse + impulse, -maxImpulse, maxImpulse);
284 impulse = m_motorImpulse - oldImpulse;
285
286 b2Vec2 P = impulse * m_axis;
287 float32 LA = impulse * m_a1;
288 float32 LB = impulse * m_a2;
289
290 vA -= mA * P;
291 wA -= iA * LA;
292
293 vB += mB * P;
294 wB += iB * LB;
295 }
296
297 b2Vec2 Cdot1;
298 Cdot1.x = b2Dot(m_perp, vB - vA) + m_s2 * wB - m_s1 * wA;
299 Cdot1.y = wB - wA;
300
301 if (m_enableLimit && m_limitState != e_inactiveLimit)
302 {
303 // Solve prismatic and limit constraint in block form.
304 float32 Cdot2;
305 Cdot2 = b2Dot(m_axis, vB - vA) + m_a2 * wB - m_a1 * wA;
306 b2Vec3 Cdot(Cdot1.x, Cdot1.y, Cdot2);
307
308 b2Vec3 f1 = m_impulse;
309 b2Vec3 df = m_K.Solve33(-Cdot);
310 m_impulse += df;
311
312 if (m_limitState == e_atLowerLimit)
313 {
314 m_impulse.z = b2Max(m_impulse.z, 0.0f);
315 }
316 else if (m_limitState == e_atUpperLimit)
317 {
318 m_impulse.z = b2Min(m_impulse.z, 0.0f);
319 }
320
321 // f2(1:2) = invK(1:2,1:2) * (-Cdot(1:2) - K(1:2,3) * (f2(3) - f1(3))) + f1(1:2)
322 b2Vec2 b = -Cdot1 - (m_impulse.z - f1.z) * b2Vec2(m_K.ez.x, m_K.ez.y);
323 b2Vec2 f2r = m_K.Solve22(b) + b2Vec2(f1.x, f1.y);
324 m_impulse.x = f2r.x;
325 m_impulse.y = f2r.y;
326
327 df = m_impulse - f1;
328
329 b2Vec2 P = df.x * m_perp + df.z * m_axis;
330 float32 LA = df.x * m_s1 + df.y + df.z * m_a1;
331 float32 LB = df.x * m_s2 + df.y + df.z * m_a2;
332
333 vA -= mA * P;
334 wA -= iA * LA;
335
336 vB += mB * P;
337 wB += iB * LB;
338 }
339 else
340 {
341 // Limit is inactive, just solve the prismatic constraint in block form.
342 b2Vec2 df = m_K.Solve22(-Cdot1);
343 m_impulse.x += df.x;
344 m_impulse.y += df.y;
345
346 b2Vec2 P = df.x * m_perp;
347 float32 LA = df.x * m_s1 + df.y;
348 float32 LB = df.x * m_s2 + df.y;
349
350 vA -= mA * P;
351 wA -= iA * LA;
352
353 vB += mB * P;
354 wB += iB * LB;
355 }
356
357 data.velocities[m_indexA].v = vA;
358 data.velocities[m_indexA].w = wA;
359 data.velocities[m_indexB].v = vB;
360 data.velocities[m_indexB].w = wB;
361}
362
363bool b2PrismaticJoint::SolvePositionConstraints(const b2SolverData& data)
364{
365 b2Vec2 cA = data.positions[m_indexA].c;
366 float32 aA = data.positions[m_indexA].a;
367 b2Vec2 cB = data.positions[m_indexB].c;
368 float32 aB = data.positions[m_indexB].a;
369
370 b2Rot qA(aA), qB(aB);
371
372 float32 mA = m_invMassA, mB = m_invMassB;
373 float32 iA = m_invIA, iB = m_invIB;
374
375 // Compute fresh Jacobians
376 b2Vec2 rA = b2Mul(qA, m_localAnchorA - m_localCenterA);
377 b2Vec2 rB = b2Mul(qB, m_localAnchorB - m_localCenterB);
378 b2Vec2 d = cB + rB - cA - rA;
379
380 b2Vec2 axis = b2Mul(qA, m_localXAxisA);
381 float32 a1 = b2Cross(d + rA, axis);
382 float32 a2 = b2Cross(rB, axis);
383 b2Vec2 perp = b2Mul(qA, m_localYAxisA);
384
385 float32 s1 = b2Cross(d + rA, perp);
386 float32 s2 = b2Cross(rB, perp);
387
388 b2Vec3 impulse;
389 b2Vec2 C1;
390 C1.x = b2Dot(perp, d);
391 C1.y = aB - aA - m_referenceAngle;
392
393 float32 linearError = b2Abs(C1.x);
394 float32 angularError = b2Abs(C1.y);
395
396 bool active = false;
397 float32 C2 = 0.0f;
398 if (m_enableLimit)
399 {
400 float32 translation = b2Dot(axis, d);
401 if (b2Abs(m_upperTranslation - m_lowerTranslation) < 2.0f * b2_linearSlop)
402 {
403 // Prevent large angular corrections
404 C2 = b2Clamp(translation, -b2_maxLinearCorrection, b2_maxLinearCorrection);
405 linearError = b2Max(linearError, b2Abs(translation));
406 active = true;
407 }
408 else if (translation <= m_lowerTranslation)
409 {
410 // Prevent large linear corrections and allow some slop.
411 C2 = b2Clamp(translation - m_lowerTranslation + b2_linearSlop, -b2_maxLinearCorrection, 0.0f);
412 linearError = b2Max(linearError, m_lowerTranslation - translation);
413 active = true;
414 }
415 else if (translation >= m_upperTranslation)
416 {
417 // Prevent large linear corrections and allow some slop.
418 C2 = b2Clamp(translation - m_upperTranslation - b2_linearSlop, 0.0f, b2_maxLinearCorrection);
419 linearError = b2Max(linearError, translation - m_upperTranslation);
420 active = true;
421 }
422 }
423
424 if (active)
425 {
426 float32 k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2;
427 float32 k12 = iA * s1 + iB * s2;
428 float32 k13 = iA * s1 * a1 + iB * s2 * a2;
429 float32 k22 = iA + iB;
430 if (k22 == 0.0f)
431 {
432 // For fixed rotation
433 k22 = 1.0f;
434 }
435 float32 k23 = iA * a1 + iB * a2;
436 float32 k33 = mA + mB + iA * a1 * a1 + iB * a2 * a2;
437
438 b2Mat33 K;
439 K.ex.Set(k11, k12, k13);
440 K.ey.Set(k12, k22, k23);
441 K.ez.Set(k13, k23, k33);
442
443 b2Vec3 C;
444 C.x = C1.x;
445 C.y = C1.y;
446 C.z = C2;
447
448 impulse = K.Solve33(-C);
449 }
450 else
451 {
452 float32 k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2;
453 float32 k12 = iA * s1 + iB * s2;
454 float32 k22 = iA + iB;
455 if (k22 == 0.0f)
456 {
457 k22 = 1.0f;
458 }
459
460 b2Mat22 K;
461 K.ex.Set(k11, k12);
462 K.ey.Set(k12, k22);
463
464 b2Vec2 impulse1 = K.Solve(-C1);
465 impulse.x = impulse1.x;
466 impulse.y = impulse1.y;
467 impulse.z = 0.0f;
468 }
469
470 b2Vec2 P = impulse.x * perp + impulse.z * axis;
471 float32 LA = impulse.x * s1 + impulse.y + impulse.z * a1;
472 float32 LB = impulse.x * s2 + impulse.y + impulse.z * a2;
473
474 cA -= mA * P;
475 aA -= iA * LA;
476 cB += mB * P;
477 aB += iB * LB;
478
479 data.positions[m_indexA].c = cA;
480 data.positions[m_indexA].a = aA;
481 data.positions[m_indexB].c = cB;
482 data.positions[m_indexB].a = aB;
483
484 return linearError <= b2_linearSlop && angularError <= b2_angularSlop;
485}
486
487b2Vec2 b2PrismaticJoint::GetAnchorA() const
488{
489 return m_bodyA->GetWorldPoint(m_localAnchorA);
490}
491
492b2Vec2 b2PrismaticJoint::GetAnchorB() const
493{
494 return m_bodyB->GetWorldPoint(m_localAnchorB);
495}
496
497b2Vec2 b2PrismaticJoint::GetReactionForce(float32 inv_dt) const
498{
499 return inv_dt * (m_impulse.x * m_perp + (m_motorImpulse + m_impulse.z) * m_axis);
500}
501
502float32 b2PrismaticJoint::GetReactionTorque(float32 inv_dt) const
503{
504 return inv_dt * m_impulse.y;
505}
506
507float32 b2PrismaticJoint::GetJointTranslation() const
508{
509 b2Vec2 pA = m_bodyA->GetWorldPoint(m_localAnchorA);
510 b2Vec2 pB = m_bodyB->GetWorldPoint(m_localAnchorB);
511 b2Vec2 d = pB - pA;
512 b2Vec2 axis = m_bodyA->GetWorldVector(m_localXAxisA);
513
514 float32 translation = b2Dot(d, axis);
515 return translation;
516}
517
518float32 b2PrismaticJoint::GetJointSpeed() const
519{
520 b2Body* bA = m_bodyA;
521 b2Body* bB = m_bodyB;
522
523 b2Vec2 rA = b2Mul(bA->m_xf.q, m_localAnchorA - bA->m_sweep.localCenter);
524 b2Vec2 rB = b2Mul(bB->m_xf.q, m_localAnchorB - bB->m_sweep.localCenter);
525 b2Vec2 p1 = bA->m_sweep.c + rA;
526 b2Vec2 p2 = bB->m_sweep.c + rB;
527 b2Vec2 d = p2 - p1;
528 b2Vec2 axis = b2Mul(bA->m_xf.q, m_localXAxisA);
529
530 b2Vec2 vA = bA->m_linearVelocity;
531 b2Vec2 vB = bB->m_linearVelocity;
532 float32 wA = bA->m_angularVelocity;
533 float32 wB = bB->m_angularVelocity;
534
535 float32 speed = b2Dot(d, b2Cross(wA, axis)) + b2Dot(axis, vB + b2Cross(wB, rB) - vA - b2Cross(wA, rA));
536 return speed;
537}
538
539bool b2PrismaticJoint::IsLimitEnabled() const
540{
541 return m_enableLimit;
542}
543
544void b2PrismaticJoint::EnableLimit(bool flag)
545{
546 if (flag != m_enableLimit)
547 {
548 m_bodyA->SetAwake(true);
549 m_bodyB->SetAwake(true);
550 m_enableLimit = flag;
551 m_impulse.z = 0.0f;
552 }
553}
554
555float32 b2PrismaticJoint::GetLowerLimit() const
556{
557 return m_lowerTranslation;
558}
559
560float32 b2PrismaticJoint::GetUpperLimit() const
561{
562 return m_upperTranslation;
563}
564
565void b2PrismaticJoint::SetLimits(float32 lower, float32 upper)
566{
567 b2Assert(lower <= upper);
568 if (lower != m_lowerTranslation || upper != m_upperTranslation)
569 {
570 m_bodyA->SetAwake(true);
571 m_bodyB->SetAwake(true);
572 m_lowerTranslation = lower;
573 m_upperTranslation = upper;
574 m_impulse.z = 0.0f;
575 }
576}
577
578bool b2PrismaticJoint::IsMotorEnabled() const
579{
580 return m_enableMotor;
581}
582
583void b2PrismaticJoint::EnableMotor(bool flag)
584{
585 m_bodyA->SetAwake(true);
586 m_bodyB->SetAwake(true);
587 m_enableMotor = flag;
588}
589
590void b2PrismaticJoint::SetMotorSpeed(float32 speed)
591{
592 m_bodyA->SetAwake(true);
593 m_bodyB->SetAwake(true);
594 m_motorSpeed = speed;
595}
596
597void b2PrismaticJoint::SetMaxMotorForce(float32 force)
598{
599 m_bodyA->SetAwake(true);
600 m_bodyB->SetAwake(true);
601 m_maxMotorForce = force;
602}
603
604float32 b2PrismaticJoint::GetMotorForce(float32 inv_dt) const
605{
606 return inv_dt * m_motorImpulse;
607}
608
609void b2PrismaticJoint::Dump()
610{
611 int32 indexA = m_bodyA->m_islandIndex;
612 int32 indexB = m_bodyB->m_islandIndex;
613
614 b2Log(" b2PrismaticJointDef jd;\n");
615 b2Log(" jd.bodyA = bodies[%d];\n", indexA);
616 b2Log(" jd.bodyB = bodies[%d];\n", indexB);
617 b2Log(" jd.collideConnected = bool(%d);\n", m_collideConnected);
618 b2Log(" jd.localAnchorA.Set(%.15lef, %.15lef);\n", m_localAnchorA.x, m_localAnchorA.y);
619 b2Log(" jd.localAnchorB.Set(%.15lef, %.15lef);\n", m_localAnchorB.x, m_localAnchorB.y);
620 b2Log(" jd.localAxisA.Set(%.15lef, %.15lef);\n", m_localXAxisA.x, m_localXAxisA.y);
621 b2Log(" jd.referenceAngle = %.15lef;\n", m_referenceAngle);
622 b2Log(" jd.enableLimit = bool(%d);\n", m_enableLimit);
623 b2Log(" jd.lowerTranslation = %.15lef;\n", m_lowerTranslation);
624 b2Log(" jd.upperTranslation = %.15lef;\n", m_upperTranslation);
625 b2Log(" jd.enableMotor = bool(%d);\n", m_enableMotor);
626 b2Log(" jd.motorSpeed = %.15lef;\n", m_motorSpeed);
627 b2Log(" jd.maxMotorForce = %.15lef;\n", m_maxMotorForce);
628 b2Log(" joints[%d] = m_world->CreateJoint(&jd);\n", m_index);
629}
630