1 /*************************************************************************
3 * Open Dynamics Engine, Copyright (C) 2001,2002 Russell L. Smith. *
4 * All rights reserved. Email: russ@q12.org Web: www.q12.org *
6 * This library is free software; you can redistribute it and/or *
7 * modify it under the terms of EITHER: *
8 * (1) The GNU Lesser General Public License as published by the Free *
9 * Software Foundation; either version 2.1 of the License, or (at *
10 * your option) any later version. The text of the GNU Lesser *
11 * General Public License is included with this library in the *
13 * (2) The BSD-style license that is included with this library in *
14 * the file LICENSE-BSD.TXT. *
16 * This library is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files *
19 * LICENSE.TXT and LICENSE-BSD.TXT for more details. *
21 *************************************************************************/
24 #include <ode/odeconfig.h>
26 #include "universal.h"
27 #include "joint_internal.h"
31 //****************************************************************************
34 // I just realized that the universal joint is equivalent to a hinge 2 joint with
35 // perfectly stiff suspension. By comparing the hinge 2 implementation to
36 // the universal implementation, you may be able to improve this
37 // implementation (or, less likely, the hinge2 implementation).
39 dxJointUniversal::dxJointUniversal( dxWorld
*w
) :
42 dSetZero( anchor1
, 4 );
43 dSetZero( anchor2
, 4 );
56 dxJointUniversal::getAxes( dVector3 ax1
, dVector3 ax2
)
58 // This says "ax1 = joint->node[0].body->posr.R * joint->axis1"
59 dMultiply0_331( ax1
, node
[0].body
->posr
.R
, axis1
);
63 dMultiply0_331( ax2
, node
[1].body
->posr
.R
, axis2
);
74 dxJointUniversal::getAngles( dReal
*angle1
, dReal
*angle2
)
78 // length 1 joint axis in global coordinates, from each body
81 dQuaternion qcross
, qq
, qrel
;
85 // It should be possible to get both angles without explicitly
86 // constructing the rotation matrix of the cross. Basically,
87 // orientation of the cross about axis1 comes from body 2,
88 // about axis 2 comes from body 1, and the perpendicular
89 // axis can come from the two bodies somehow. (We don't really
90 // want to assume it's 90 degrees, because in general the
91 // constraints won't be perfectly satisfied, or even very well
94 // However, we'd need a version of getHingeAngleFromRElativeQuat()
95 // that CAN handle when its relative quat is rotated along a direction
96 // other than the given axis. What I have here works,
97 // although it's probably much slower than need be.
99 dRFrom2Axes( R
, ax1
[0], ax1
[1], ax1
[2], ax2
[0], ax2
[1], ax2
[2] );
104 // This code is essentialy the same as getHingeAngle(), see the comments
105 // there for details.
107 // get qrel = relative rotation between node[0] and the cross
108 dQMultiply1( qq
, node
[0].body
->q
, qcross
);
109 dQMultiply2( qrel
, qq
, qrel1
);
111 *angle1
= getHingeAngleFromRelativeQuat( qrel
, axis1
);
113 // This is equivalent to
114 // dRFrom2Axes(R, ax2[0], ax2[1], ax2[2], ax1[0], ax1[1], ax1[2]);
115 // You see that the R is constructed from the same 2 axis as for angle1
116 // but the first and second axis are swapped.
117 // So we can take the first R and rapply a rotation to it.
118 // The rotation is around the axis between the 2 axes (ax1 and ax2).
119 // We do a rotation of 180deg.
122 // Find the vector between ax1 and ax2 (i.e. in the middle)
123 // We need to turn around this vector by 180deg
125 // The 2 axes should be normalize so to find the vector between the 2.
126 // Add and devide by 2 then normalize or simply normalize
131 /// *------------> ax1
132 // We want the vector a 45deg
134 // N.B. We don't need to normalize the ax1 and ax2 since there are
135 // normalized when we set them.
137 // We set the quaternion q = [cos(theta), dir*sin(theta)] = [w, x, y, Z]
138 qrel
[0] = 0; // equivalent to cos(Pi/2)
139 qrel
[1] = ax1
[0] + ax2
[0]; // equivalent to x*sin(Pi/2); since sin(Pi/2) = 1
140 qrel
[2] = ax1
[1] + ax2
[1];
141 qrel
[3] = ax1
[2] + ax2
[2];
143 dReal l
= dRecip( sqrt( qrel
[1] * qrel
[1] + qrel
[2] * qrel
[2] + qrel
[3] * qrel
[3] ) );
148 dQMultiply0( qcross2
, qrel
, qcross
);
152 dQMultiply1( qq
, node
[1].body
->q
, qcross2
);
153 dQMultiply2( qrel
, qq
, qrel2
);
157 // pretend joint->node[1].body->q is the identity
158 dQMultiply2( qrel
, qcross2
, qrel2
);
161 *angle2
= - getHingeAngleFromRelativeQuat( qrel
, axis2
);
171 dxJointUniversal::getAngle1()
175 // length 1 joint axis in global coordinates, from each body
178 dQuaternion qcross
, qq
, qrel
;
182 // It should be possible to get both angles without explicitly
183 // constructing the rotation matrix of the cross. Basically,
184 // orientation of the cross about axis1 comes from body 2,
185 // about axis 2 comes from body 1, and the perpendicular
186 // axis can come from the two bodies somehow. (We don't really
187 // want to assume it's 90 degrees, because in general the
188 // constraints won't be perfectly satisfied, or even very well
191 // However, we'd need a version of getHingeAngleFromRElativeQuat()
192 // that CAN handle when its relative quat is rotated along a direction
193 // other than the given axis. What I have here works,
194 // although it's probably much slower than need be.
196 dRFrom2Axes( R
, ax1
[0], ax1
[1], ax1
[2], ax2
[0], ax2
[1], ax2
[2] );
199 // This code is essential the same as getHingeAngle(), see the comments
200 // there for details.
202 // get qrel = relative rotation between node[0] and the cross
203 dQMultiply1( qq
, node
[0].body
->q
, qcross
);
204 dQMultiply2( qrel
, qq
, qrel1
);
206 return getHingeAngleFromRelativeQuat( qrel
, axis1
);
213 dxJointUniversal::getAngle2()
217 // length 1 joint axis in global coordinates, from each body
220 dQuaternion qcross
, qq
, qrel
;
224 // It should be possible to get both angles without explicitly
225 // constructing the rotation matrix of the cross. Basically,
226 // orientation of the cross about axis1 comes from body 2,
227 // about axis 2 comes from body 1, and the perpendicular
228 // axis can come from the two bodies somehow. (We don't really
229 // want to assume it's 90 degrees, because in general the
230 // constraints won't be perfectly satisfied, or even very well
233 // However, we'd need a version of getHingeAngleFromRElativeQuat()
234 // that CAN handle when its relative quat is rotated along a direction
235 // other than the given axis. What I have here works,
236 // although it's probably much slower than need be.
238 dRFrom2Axes( R
, ax2
[0], ax2
[1], ax2
[2], ax1
[0], ax1
[1], ax1
[2] );
243 dQMultiply1( qq
, node
[1].body
->q
, qcross
);
244 dQMultiply2( qrel
, qq
, qrel2
);
248 // pretend joint->node[1].body->q is the identity
249 dQMultiply2( qrel
, qcross
, qrel2
);
252 return - getHingeAngleFromRelativeQuat( qrel
, axis2
);
259 dxJointUniversal::getSureMaxInfo( SureMaxInfo
* info
)
266 dxJointUniversal::getInfo1( dxJoint::Info1
*info
)
271 bool limiting1
= ( limot1
.lostop
>= -M_PI
|| limot1
.histop
<= M_PI
) &&
272 limot1
.lostop
<= limot1
.histop
;
273 bool limiting2
= ( limot2
.lostop
>= -M_PI
|| limot2
.histop
<= M_PI
) &&
274 limot2
.lostop
<= limot2
.histop
;
276 // We need to call testRotationLimit() even if we're motored, since it
277 // records the result.
281 if ( limiting1
|| limiting2
)
283 dReal angle1
, angle2
;
284 getAngles( &angle1
, &angle2
);
286 limot1
.testRotationalLimit( angle1
);
288 limot2
.testRotationalLimit( angle2
);
291 if ( limot1
.limit
|| limot1
.fmax
> 0 ) info
->m
++;
292 if ( limot2
.limit
|| limot2
.fmax
> 0 ) info
->m
++;
297 dxJointUniversal::getInfo2( dReal worldFPS
, dReal worldERP
,
298 int rowskip
, dReal
*J1
, dReal
*J2
,
299 int pairskip
, dReal
*pairRhsCfm
, dReal
*pairLoHi
,
302 // set the three ball-and-socket rows
303 setBall( this, worldFPS
, worldERP
, rowskip
, J1
, J2
, pairskip
, pairRhsCfm
, anchor1
, anchor2
);
305 // set the universal joint row. the angular velocity about an axis
306 // perpendicular to both joint axes should be equal. thus the constraint
309 // where p is a vector normal to both joint axes, and w1 and w2
310 // are the angular velocity vectors of the two bodies.
312 // length 1 joint axis in global coordinates, from each body
314 // length 1 vector perpendicular to ax1 and ax2. Neither body can rotate
318 // Since axis1 and axis2 may not be perpendicular
319 // we find a axis2_tmp which is really perpendicular to axis1
320 // and in the plane of axis1 and axis2
323 dReal k
= dCalcVectorDot3( ax1
, ax2
);
326 dAddVectorScaledVector3(ax2_temp
, ax2
, ax1
, -k
);
327 dCalcVectorCross3( p
, ax1
, ax2_temp
);
330 int currRowSkip
= 3 * rowskip
;
332 dCopyVector3( J1
+ currRowSkip
+ GI2__JA_MIN
, p
);
336 dCopyNegatedVector3( J2
+ currRowSkip
+ GI2__JA_MIN
, p
);
340 // compute the right hand side of the constraint equation. set relative
341 // body velocities along p to bring the axes back to perpendicular.
342 // If ax1, ax2 are unit length joint axes as computed from body1 and
343 // body2, we need to rotate both bodies along the axis p. If theta
344 // is the angle between ax1 and ax2, we need an angular velocity
345 // along p to cover the angle erp * (theta - Pi/2) in one step:
347 // |angular_velocity| = angle/time = erp*(theta - Pi/2) / stepsize
348 // = (erp*fps) * (theta - Pi/2)
350 // if theta is close to Pi/2,
351 // theta - Pi/2 ~= cos(theta), so
352 // |angular_velocity| ~= (erp*fps) * (ax1 dot ax2)
354 int currPairSkip
= 3 * pairskip
;
356 pairRhsCfm
[currPairSkip
+ GI2_RHS
] = worldFPS
* worldERP
* (-k
);
359 currRowSkip
+= rowskip
; currPairSkip
+= pairskip
;
361 // if the first angle is powered, or has joint limits, add in the stuff
362 if (limot1
.addLimot( this, worldFPS
, J1
+ currRowSkip
, J2
+ currRowSkip
, pairRhsCfm
+ currPairSkip
, pairLoHi
+ currPairSkip
, ax1
, 1 ))
364 currRowSkip
+= rowskip
; currPairSkip
+= pairskip
;
367 // if the second angle is powered, or has joint limits, add in more stuff
368 limot2
.addLimot( this, worldFPS
, J1
+ currRowSkip
, J2
+ currRowSkip
, pairRhsCfm
+ currPairSkip
, pairLoHi
+ currPairSkip
, ax2
, 1 );
373 dxJointUniversal::computeInitialRelativeRotations()
384 dRFrom2Axes( R
, ax1
[0], ax1
[1], ax1
[2], ax2
[0], ax2
[1], ax2
[2] );
386 dQMultiply1( qrel1
, node
[0].body
->q
, qcross
);
389 dRFrom2Axes( R
, ax2
[0], ax2
[1], ax2
[2], ax1
[0], ax1
[1], ax1
[2] );
393 dQMultiply1( qrel2
, node
[1].body
->q
, qcross
);
397 // set joint->qrel to qcross
398 for ( int i
= 0; i
< 4; i
++ ) qrel2
[i
] = qcross
[i
];
404 dxJointUniversal::buildFirstBodyTorqueVector(dVector3 torqueVector
, dReal torque1
, dReal torque2
)
406 if (this->flags
& dJOINT_REVERSE
)
408 dReal temp
= torque1
;
413 dVector3 axis1
, axis2
;
414 getAxis(this, axis1
, this->axis1
);
415 getAxis2(this, axis2
, this->axis2
);
416 torqueVector
[0] = axis1
[0] * torque1
+ axis2
[0] * torque2
;
417 torqueVector
[1] = axis1
[1] * torque1
+ axis2
[1] * torque2
;
418 torqueVector
[2] = axis1
[2] * torque1
+ axis2
[2] * torque2
;
421 void dJointSetUniversalAnchor( dJointID j
, dReal x
, dReal y
, dReal z
)
423 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
424 dUASSERT( joint
, "bad joint argument" );
425 checktype( joint
, Universal
);
426 setAnchors( joint
, x
, y
, z
, joint
->anchor1
, joint
->anchor2
);
427 joint
->computeInitialRelativeRotations();
431 void dJointSetUniversalAxis1( dJointID j
, dReal x
, dReal y
, dReal z
)
433 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
434 dUASSERT( joint
, "bad joint argument" );
435 checktype( joint
, Universal
);
436 if ( joint
->flags
& dJOINT_REVERSE
)
437 setAxes( joint
, x
, y
, z
, NULL
, joint
->axis2
);
439 setAxes( joint
, x
, y
, z
, joint
->axis1
, NULL
);
440 joint
->computeInitialRelativeRotations();
443 void dJointSetUniversalAxis1Offset( dJointID j
, dReal x
, dReal y
, dReal z
,
444 dReal offset1
, dReal offset2
)
446 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
447 dUASSERT( joint
, "bad joint argument" );
448 checktype( joint
, Universal
);
449 if ( joint
->flags
& dJOINT_REVERSE
)
451 setAxes( joint
, x
, y
, z
, NULL
, joint
->axis2
);
456 setAxes( joint
, x
, y
, z
, joint
->axis1
, NULL
);
458 joint
->computeInitialRelativeRotations();
462 getAxis2( joint
, ax2
, joint
->axis2
);
466 joint
->getAxes(ax1
, ax2
);
472 dQFromAxisAndAngle(qAngle
, x
, y
, z
, offset1
);
475 dRFrom2Axes( R
, x
, y
, z
, ax2
[0], ax2
[1], ax2
[2] );
481 dQMultiply0(qOffset
, qAngle
, qcross
);
483 dQMultiply1( joint
->qrel1
, joint
->node
[0].body
->q
, qOffset
);
485 // Calculating the second offset
486 dQFromAxisAndAngle(qAngle
, ax2
[0], ax2
[1], ax2
[2], offset2
);
488 dRFrom2Axes( R
, ax2
[0], ax2
[1], ax2
[2], x
, y
, z
);
491 dQMultiply1(qOffset
, qAngle
, qcross
);
492 if ( joint
->node
[1].body
)
494 dQMultiply1( joint
->qrel2
, joint
->node
[1].body
->q
, qOffset
);
498 joint
->qrel2
[0] = qcross
[0];
499 joint
->qrel2
[1] = qcross
[1];
500 joint
->qrel2
[2] = qcross
[2];
501 joint
->qrel2
[3] = qcross
[3];
506 void dJointSetUniversalAxis2( dJointID j
, dReal x
, dReal y
, dReal z
)
508 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
509 dUASSERT( joint
, "bad joint argument" );
510 checktype( joint
, Universal
);
511 if ( joint
->flags
& dJOINT_REVERSE
)
512 setAxes( joint
, x
, y
, z
, joint
->axis1
, NULL
);
514 setAxes( joint
, x
, y
, z
, NULL
, joint
->axis2
);
515 joint
->computeInitialRelativeRotations();
518 void dJointSetUniversalAxis2Offset( dJointID j
, dReal x
, dReal y
, dReal z
,
519 dReal offset1
, dReal offset2
)
521 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
522 dUASSERT( joint
, "bad joint argument" );
523 checktype( joint
, Universal
);
525 if ( joint
->flags
& dJOINT_REVERSE
)
527 setAxes( joint
, x
, y
, z
, joint
->axis1
, NULL
);
532 setAxes( joint
, x
, y
, z
, NULL
, joint
->axis2
);
535 joint
->computeInitialRelativeRotations();
537 // It is easier to retreive the 2 axes here since
538 // when there is only one body B2 (the axes switch position)
539 // Doing this way eliminate the need to write the code differently
542 joint
->getAxes(ax1
, ax2
);
547 dQFromAxisAndAngle(qAngle
, ax1
[0], ax1
[1], ax1
[2], offset1
);
550 dRFrom2Axes( R
, ax1
[0], ax1
[1], ax1
[2], ax2
[0], ax2
[1], ax2
[2]);
556 dQMultiply0(qOffset
, qAngle
, qcross
);
560 dQMultiply1( joint
->qrel1
, joint
->node
[0].body
->q
, qOffset
);
563 // Calculating the second offset
564 dQFromAxisAndAngle(qAngle
, ax2
[0], ax2
[1], ax2
[2], offset2
);
566 dRFrom2Axes( R
, ax2
[0], ax2
[1], ax2
[2], ax1
[0], ax1
[1], ax1
[2]);
569 dQMultiply1(qOffset
, qAngle
, qcross
);
570 if ( joint
->node
[1].body
)
572 dQMultiply1( joint
->qrel2
, joint
->node
[1].body
->q
, qOffset
);
576 joint
->qrel2
[0] = qcross
[0];
577 joint
->qrel2
[1] = qcross
[1];
578 joint
->qrel2
[2] = qcross
[2];
579 joint
->qrel2
[3] = qcross
[3];
584 void dJointGetUniversalAnchor( dJointID j
, dVector3 result
)
586 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
587 dUASSERT( joint
, "bad joint argument" );
588 dUASSERT( result
, "bad result argument" );
589 checktype( joint
, Universal
);
590 if ( joint
->flags
& dJOINT_REVERSE
)
591 getAnchor2( joint
, result
, joint
->anchor2
);
593 getAnchor( joint
, result
, joint
->anchor1
);
597 void dJointGetUniversalAnchor2( dJointID j
, dVector3 result
)
599 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
600 dUASSERT( joint
, "bad joint argument" );
601 dUASSERT( result
, "bad result argument" );
602 checktype( joint
, Universal
);
603 if ( joint
->flags
& dJOINT_REVERSE
)
604 getAnchor( joint
, result
, joint
->anchor1
);
606 getAnchor2( joint
, result
, joint
->anchor2
);
610 void dJointGetUniversalAxis1( dJointID j
, dVector3 result
)
612 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
613 dUASSERT( joint
, "bad joint argument" );
614 dUASSERT( result
, "bad result argument" );
615 checktype( joint
, Universal
);
616 if ( joint
->flags
& dJOINT_REVERSE
)
617 getAxis2( joint
, result
, joint
->axis2
);
619 getAxis( joint
, result
, joint
->axis1
);
623 void dJointGetUniversalAxis2( dJointID j
, dVector3 result
)
625 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
626 dUASSERT( joint
, "bad joint argument" );
627 dUASSERT( result
, "bad result argument" );
628 checktype( joint
, Universal
);
629 if ( joint
->flags
& dJOINT_REVERSE
)
630 getAxis( joint
, result
, joint
->axis1
);
632 getAxis2( joint
, result
, joint
->axis2
);
636 void dJointSetUniversalParam( dJointID j
, int parameter
, dReal value
)
638 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
639 dUASSERT( joint
, "bad joint argument" );
640 checktype( joint
, Universal
);
641 if (( parameter
& 0xff00 ) == 0x100 )
643 joint
->limot2
.set( parameter
& 0xff, value
);
647 joint
->limot1
.set( parameter
, value
);
652 dReal
dJointGetUniversalParam( dJointID j
, int parameter
)
654 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
655 dUASSERT( joint
, "bad joint argument" );
656 checktype( joint
, Universal
);
657 if (( parameter
& 0xff00 ) == 0x100 )
659 return joint
->limot2
.get( parameter
& 0xff );
663 return joint
->limot1
.get( parameter
);
667 void dJointGetUniversalAngles( dJointID j
, dReal
*angle1
, dReal
*angle2
)
669 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
670 dUASSERT( joint
, "bad joint argument" );
671 checktype( joint
, Universal
);
672 if ( joint
->flags
& dJOINT_REVERSE
)
674 joint
->getAngles( angle2
, angle1
);
675 *angle2
= -(*angle2
);
679 return joint
->getAngles( angle1
, angle2
);
683 dReal
dJointGetUniversalAngle1( dJointID j
)
685 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
686 dUASSERT( joint
, "bad joint argument" );
687 checktype( joint
, Universal
);
688 if ( joint
->flags
& dJOINT_REVERSE
)
689 return joint
->getAngle2();
691 return joint
->getAngle1();
695 dReal
dJointGetUniversalAngle2( dJointID j
)
697 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
698 dUASSERT( joint
, "bad joint argument" );
699 checktype( joint
, Universal
);
700 if ( joint
->flags
& dJOINT_REVERSE
)
701 return -joint
->getAngle1();
703 return joint
->getAngle2();
707 dReal
dJointGetUniversalAngle1Rate( dJointID j
)
709 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
710 dUASSERT( joint
, "bad joint argument" );
711 checktype( joint
, Universal
);
713 if ( joint
->node
[0].body
)
717 if ( joint
->flags
& dJOINT_REVERSE
)
718 getAxis2( joint
, axis
, joint
->axis2
);
720 getAxis( joint
, axis
, joint
->axis1
);
722 dReal rate
= dCalcVectorDot3( axis
, joint
->node
[0].body
->avel
);
723 if ( joint
->node
[1].body
)
724 rate
-= dCalcVectorDot3( axis
, joint
->node
[1].body
->avel
);
731 dReal
dJointGetUniversalAngle2Rate( dJointID j
)
733 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
734 dUASSERT( joint
, "bad joint argument" );
735 checktype( joint
, Universal
);
737 if ( joint
->node
[0].body
)
741 if ( joint
->flags
& dJOINT_REVERSE
)
742 getAxis( joint
, axis
, joint
->axis1
);
744 getAxis2( joint
, axis
, joint
->axis2
);
746 dReal rate
= dCalcVectorDot3( axis
, joint
->node
[0].body
->avel
);
747 if ( joint
->node
[1].body
) rate
-= dCalcVectorDot3( axis
, joint
->node
[1].body
->avel
);
754 void dJointAddUniversalTorques( dJointID j
, dReal torque1
, dReal torque2
)
756 dxJointUniversal
* joint
= ( dxJointUniversal
* )j
;
758 checktype( joint
, Universal
);
761 joint
->buildFirstBodyTorqueVector(axis
, torque1
, torque2
);
763 if ( joint
->node
[0].body
!= 0 )
764 dBodyAddTorque( joint
->node
[0].body
, axis
[0], axis
[1], axis
[2] );
765 if ( joint
->node
[1].body
!= 0 )
766 dBodyAddTorque( joint
->node
[1].body
, -axis
[0], -axis
[1], -axis
[2] );
771 dxJointUniversal::type() const
773 return dJointTypeUniversal
;
778 dxJointUniversal::size() const
780 return sizeof( *this );
786 dxJointUniversal::setRelativeValues()
789 dJointGetUniversalAnchor(this, anchor
);
790 setAnchors( this, anchor
[0], anchor
[1], anchor
[2], anchor1
, anchor2
);
793 dJointGetUniversalAxis1(this, ax1
);
794 dJointGetUniversalAxis2(this, ax2
);
796 if ( flags
& dJOINT_REVERSE
)
798 setAxes( this, ax1
[0],ax1
[1],ax1
[2], NULL
, axis2
);
799 setAxes( this, ax2
[0],ax2
[1],ax2
[2], axis1
, NULL
);
803 setAxes( this, ax1
[0],ax1
[1],ax1
[2], axis1
, NULL
);
804 setAxes( this, ax2
[0],ax2
[1],ax2
[2], NULL
, axis2
);
807 computeInitialRelativeRotations();