/************************************************************************* * * * Open Dynamics Engine, Copyright (C) 2001,2002 Russell L. Smith. * * All rights reserved. Email: russ@q12.org Web: www.q12.org * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of EITHER: * * (1) The GNU Lesser General Public License as published by the Free * * Software Foundation; either version 2.1 of the License, or (at * * your option) any later version. The text of the GNU Lesser * * General Public License is included with this library in the * * file LICENSE.TXT. * * (2) The BSD-style license that is included with this library in * * the file LICENSE-BSD.TXT. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files * * LICENSE.TXT and LICENSE-BSD.TXT for more details. * * * *************************************************************************/ #include #include "config.h" #include "universal.h" #include "joint_internal.h" //**************************************************************************** // universal // I just realized that the universal joint is equivalent to a hinge 2 joint with // perfectly stiff suspension. By comparing the hinge 2 implementation to // the universal implementation, you may be able to improve this // implementation (or, less likely, the hinge2 implementation). dxJointUniversal::dxJointUniversal( dxWorld *w ) : dxJoint( w ) { dSetZero( anchor1, 4 ); dSetZero( anchor2, 4 ); dSetZero( axis1, 4 ); axis1[0] = 1; dSetZero( axis2, 4 ); axis2[1] = 1; dSetZero( qrel1, 4 ); dSetZero( qrel2, 4 ); limot1.init( world ); limot2.init( world ); } void dxJointUniversal::getAxes( dVector3 ax1, dVector3 ax2 ) { // This says "ax1 = joint->node[0].body->posr.R * joint->axis1" dMultiply0_331( ax1, node[0].body->posr.R, axis1 ); if ( node[1].body ) { dMultiply0_331( ax2, node[1].body->posr.R, axis2 ); } else { ax2[0] = axis2[0]; ax2[1] = axis2[1]; ax2[2] = axis2[2]; } } void dxJointUniversal::getAngles( dReal *angle1, dReal *angle2 ) { if ( node[0].body ) { // length 1 joint axis in global coordinates, from each body dVector3 ax1, ax2; dMatrix3 R; dQuaternion qcross, qq, qrel; getAxes( ax1, ax2 ); // It should be possible to get both angles without explicitly // constructing the rotation matrix of the cross. Basically, // orientation of the cross about axis1 comes from body 2, // about axis 2 comes from body 1, and the perpendicular // axis can come from the two bodies somehow. (We don't really // want to assume it's 90 degrees, because in general the // constraints won't be perfectly satisfied, or even very well // satisfied.) // // However, we'd need a version of getHingeAngleFromRElativeQuat() // that CAN handle when its relative quat is rotated along a direction // other than the given axis. What I have here works, // although it's probably much slower than need be. dRFrom2Axes( R, ax1[0], ax1[1], ax1[2], ax2[0], ax2[1], ax2[2] ); dRtoQ( R, qcross ); // This code is essentialy the same as getHingeAngle(), see the comments // there for details. // get qrel = relative rotation between node[0] and the cross dQMultiply1( qq, node[0].body->q, qcross ); dQMultiply2( qrel, qq, qrel1 ); *angle1 = getHingeAngleFromRelativeQuat( qrel, axis1 ); // This is equivalent to // dRFrom2Axes(R, ax2[0], ax2[1], ax2[2], ax1[0], ax1[1], ax1[2]); // You see that the R is constructed from the same 2 axis as for angle1 // but the first and second axis are swapped. // So we can take the first R and rapply a rotation to it. // The rotation is around the axis between the 2 axes (ax1 and ax2). // We do a rotation of 180deg. dQuaternion qcross2; // Find the vector between ax1 and ax2 (i.e. in the middle) // We need to turn around this vector by 180deg // The 2 axes should be normalize so to find the vector between the 2. // Add and devide by 2 then normalize or simply normalize // ax2 // ^ // | // | /// *------------> ax1 // We want the vector a 45deg // // N.B. We don't need to normalize the ax1 and ax2 since there are // normalized when we set them. // We set the quaternion q = [cos(theta), dir*sin(theta)] = [w, x, y, Z] qrel[0] = 0; // equivalent to cos(Pi/2) qrel[1] = ax1[0] + ax2[0]; // equivalent to x*sin(Pi/2); since sin(Pi/2) = 1 qrel[2] = ax1[1] + ax2[1]; qrel[3] = ax1[2] + ax2[2]; dReal l = dRecip( sqrt( qrel[1] * qrel[1] + qrel[2] * qrel[2] + qrel[3] * qrel[3] ) ); qrel[1] *= l; qrel[2] *= l; qrel[3] *= l; dQMultiply0( qcross2, qrel, qcross ); if ( node[1].body ) { dQMultiply1( qq, node[1].body->q, qcross2 ); dQMultiply2( qrel, qq, qrel2 ); } else { // pretend joint->node[1].body->q is the identity dQMultiply2( qrel, qcross2, qrel2 ); } *angle2 = - getHingeAngleFromRelativeQuat( qrel, axis2 ); } else { *angle1 = 0; *angle2 = 0; } } dReal dxJointUniversal::getAngle1() { if ( node[0].body ) { // length 1 joint axis in global coordinates, from each body dVector3 ax1, ax2; dMatrix3 R; dQuaternion qcross, qq, qrel; getAxes( ax1, ax2 ); // It should be possible to get both angles without explicitly // constructing the rotation matrix of the cross. Basically, // orientation of the cross about axis1 comes from body 2, // about axis 2 comes from body 1, and the perpendicular // axis can come from the two bodies somehow. (We don't really // want to assume it's 90 degrees, because in general the // constraints won't be perfectly satisfied, or even very well // satisfied.) // // However, we'd need a version of getHingeAngleFromRElativeQuat() // that CAN handle when its relative quat is rotated along a direction // other than the given axis. What I have here works, // although it's probably much slower than need be. dRFrom2Axes( R, ax1[0], ax1[1], ax1[2], ax2[0], ax2[1], ax2[2] ); dRtoQ( R, qcross ); // This code is essential the same as getHingeAngle(), see the comments // there for details. // get qrel = relative rotation between node[0] and the cross dQMultiply1( qq, node[0].body->q, qcross ); dQMultiply2( qrel, qq, qrel1 ); return getHingeAngleFromRelativeQuat( qrel, axis1 ); } return 0; } dReal dxJointUniversal::getAngle2() { if ( node[0].body ) { // length 1 joint axis in global coordinates, from each body dVector3 ax1, ax2; dMatrix3 R; dQuaternion qcross, qq, qrel; getAxes( ax1, ax2 ); // It should be possible to get both angles without explicitly // constructing the rotation matrix of the cross. Basically, // orientation of the cross about axis1 comes from body 2, // about axis 2 comes from body 1, and the perpendicular // axis can come from the two bodies somehow. (We don't really // want to assume it's 90 degrees, because in general the // constraints won't be perfectly satisfied, or even very well // satisfied.) // // However, we'd need a version of getHingeAngleFromRElativeQuat() // that CAN handle when its relative quat is rotated along a direction // other than the given axis. What I have here works, // although it's probably much slower than need be. dRFrom2Axes( R, ax2[0], ax2[1], ax2[2], ax1[0], ax1[1], ax1[2] ); dRtoQ( R, qcross ); if ( node[1].body ) { dQMultiply1( qq, node[1].body->q, qcross ); dQMultiply2( qrel, qq, qrel2 ); } else { // pretend joint->node[1].body->q is the identity dQMultiply2( qrel, qcross, qrel2 ); } return - getHingeAngleFromRelativeQuat( qrel, axis2 ); } return 0; } void dxJointUniversal::getSureMaxInfo( SureMaxInfo* info ) { info->max_m = 6; } void dxJointUniversal::getInfo1( dxJoint::Info1 *info ) { info->nub = 4; info->m = 4; bool limiting1 = ( limot1.lostop >= -M_PI || limot1.histop <= M_PI ) && limot1.lostop <= limot1.histop; bool limiting2 = ( limot2.lostop >= -M_PI || limot2.histop <= M_PI ) && limot2.lostop <= limot2.histop; // We need to call testRotationLimit() even if we're motored, since it // records the result. limot1.limit = 0; limot2.limit = 0; if ( limiting1 || limiting2 ) { dReal angle1, angle2; getAngles( &angle1, &angle2 ); if ( limiting1 ) limot1.testRotationalLimit( angle1 ); if ( limiting2 ) limot2.testRotationalLimit( angle2 ); } if ( limot1.limit || limot1.fmax > 0 ) info->m++; if ( limot2.limit || limot2.fmax > 0 ) info->m++; } void dxJointUniversal::getInfo2( dReal worldFPS, dReal worldERP, int rowskip, dReal *J1, dReal *J2, int pairskip, dReal *pairRhsCfm, dReal *pairLoHi, int *findex ) { // set the three ball-and-socket rows setBall( this, worldFPS, worldERP, rowskip, J1, J2, pairskip, pairRhsCfm, anchor1, anchor2 ); // set the universal joint row. the angular velocity about an axis // perpendicular to both joint axes should be equal. thus the constraint // equation is // p*w1 - p*w2 = 0 // where p is a vector normal to both joint axes, and w1 and w2 // are the angular velocity vectors of the two bodies. // length 1 joint axis in global coordinates, from each body dVector3 ax1, ax2; // length 1 vector perpendicular to ax1 and ax2. Neither body can rotate // about this. dVector3 p; // Since axis1 and axis2 may not be perpendicular // we find a axis2_tmp which is really perpendicular to axis1 // and in the plane of axis1 and axis2 getAxes( ax1, ax2 ); dReal k = dCalcVectorDot3( ax1, ax2 ); dVector3 ax2_temp; dAddVectorScaledVector3(ax2_temp, ax2, ax1, -k); dCalcVectorCross3( p, ax1, ax2_temp ); dNormalize3( p ); int currRowSkip = 3 * rowskip; { dCopyVector3( J1 + currRowSkip + GI2__JA_MIN, p); if ( node[1].body ) { dCopyNegatedVector3( J2 + currRowSkip + GI2__JA_MIN, p); } } // compute the right hand side of the constraint equation. set relative // body velocities along p to bring the axes back to perpendicular. // If ax1, ax2 are unit length joint axes as computed from body1 and // body2, we need to rotate both bodies along the axis p. If theta // is the angle between ax1 and ax2, we need an angular velocity // along p to cover the angle erp * (theta - Pi/2) in one step: // // |angular_velocity| = angle/time = erp*(theta - Pi/2) / stepsize // = (erp*fps) * (theta - Pi/2) // // if theta is close to Pi/2, // theta - Pi/2 ~= cos(theta), so // |angular_velocity| ~= (erp*fps) * (ax1 dot ax2) int currPairSkip = 3 * pairskip; { pairRhsCfm[currPairSkip + GI2_RHS] = worldFPS * worldERP * (-k); } currRowSkip += rowskip; currPairSkip += pairskip; // if the first angle is powered, or has joint limits, add in the stuff if (limot1.addLimot( this, worldFPS, J1 + currRowSkip, J2 + currRowSkip, pairRhsCfm + currPairSkip, pairLoHi + currPairSkip, ax1, 1 )) { currRowSkip += rowskip; currPairSkip += pairskip; } // if the second angle is powered, or has joint limits, add in more stuff limot2.addLimot( this, worldFPS, J1 + currRowSkip, J2 + currRowSkip, pairRhsCfm + currPairSkip, pairLoHi + currPairSkip, ax2, 1 ); } void dxJointUniversal::computeInitialRelativeRotations() { if ( node[0].body ) { dVector3 ax1, ax2; dMatrix3 R; dQuaternion qcross; getAxes( ax1, ax2 ); // Axis 1. dRFrom2Axes( R, ax1[0], ax1[1], ax1[2], ax2[0], ax2[1], ax2[2] ); dRtoQ( R, qcross ); dQMultiply1( qrel1, node[0].body->q, qcross ); // Axis 2. dRFrom2Axes( R, ax2[0], ax2[1], ax2[2], ax1[0], ax1[1], ax1[2] ); dRtoQ( R, qcross ); if ( node[1].body ) { dQMultiply1( qrel2, node[1].body->q, qcross ); } else { // set joint->qrel to qcross for ( int i = 0; i < 4; i++ ) qrel2[i] = qcross[i]; } } } void dJointSetUniversalAnchor( dJointID j, dReal x, dReal y, dReal z ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); setAnchors( joint, x, y, z, joint->anchor1, joint->anchor2 ); joint->computeInitialRelativeRotations(); } void dJointSetUniversalAxis1( dJointID j, dReal x, dReal y, dReal z ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) setAxes( joint, x, y, z, NULL, joint->axis2 ); else setAxes( joint, x, y, z, joint->axis1, NULL ); joint->computeInitialRelativeRotations(); } void dJointSetUniversalAxis1Offset( dJointID j, dReal x, dReal y, dReal z, dReal offset1, dReal offset2 ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) { setAxes( joint, x, y, z, NULL, joint->axis2 ); offset1 = -offset1; offset2 = -offset2; } else setAxes( joint, x, y, z, joint->axis1, NULL ); joint->computeInitialRelativeRotations(); dVector3 ax2; getAxis2( joint, ax2, joint->axis2 ); { dVector3 ax1; joint->getAxes(ax1, ax2); } dQuaternion qAngle; dQFromAxisAndAngle(qAngle, x, y, z, offset1); dMatrix3 R; dRFrom2Axes( R, x, y, z, ax2[0], ax2[1], ax2[2] ); dQuaternion qcross; dRtoQ( R, qcross ); dQuaternion qOffset; dQMultiply0(qOffset, qAngle, qcross); dQMultiply1( joint->qrel1, joint->node[0].body->q, qOffset ); // Calculating the second offset dQFromAxisAndAngle(qAngle, ax2[0], ax2[1], ax2[2], offset2); dRFrom2Axes( R, ax2[0], ax2[1], ax2[2], x, y, z ); dRtoQ( R, qcross ); dQMultiply1(qOffset, qAngle, qcross); if ( joint->node[1].body ) { dQMultiply1( joint->qrel2, joint->node[1].body->q, qOffset ); } else { joint->qrel2[0] = qcross[0]; joint->qrel2[1] = qcross[1]; joint->qrel2[2] = qcross[2]; joint->qrel2[3] = qcross[3]; } } void dJointSetUniversalAxis2( dJointID j, dReal x, dReal y, dReal z ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) setAxes( joint, x, y, z, joint->axis1, NULL ); else setAxes( joint, x, y, z, NULL, joint->axis2 ); joint->computeInitialRelativeRotations(); } void dJointSetUniversalAxis2Offset( dJointID j, dReal x, dReal y, dReal z, dReal offset1, dReal offset2 ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) { setAxes( joint, x, y, z, joint->axis1, NULL ); offset1 = -offset2; offset2 = -offset1; } else setAxes( joint, x, y, z, NULL, joint->axis2 ); joint->computeInitialRelativeRotations(); // It is easier to retreive the 2 axes here since // when there is only one body B2 (the axes switch position) // Doing this way eliminate the need to write the code differently // for both case. dVector3 ax1, ax2; joint->getAxes(ax1, ax2 ); dQuaternion qAngle; dQFromAxisAndAngle(qAngle, ax1[0], ax1[1], ax1[2], offset1); dMatrix3 R; dRFrom2Axes( R, ax1[0], ax1[1], ax1[2], ax2[0], ax2[1], ax2[2]); dQuaternion qcross; dRtoQ( R, qcross ); dQuaternion qOffset; dQMultiply0(qOffset, qAngle, qcross); dQMultiply1( joint->qrel1, joint->node[0].body->q, qOffset ); // Calculating the second offset dQFromAxisAndAngle(qAngle, ax2[0], ax2[1], ax2[2], offset2); dRFrom2Axes( R, ax2[0], ax2[1], ax2[2], ax1[0], ax1[1], ax1[2]); dRtoQ( R, qcross ); dQMultiply1(qOffset, qAngle, qcross); if ( joint->node[1].body ) { dQMultiply1( joint->qrel2, joint->node[1].body->q, qOffset ); } else { joint->qrel2[0] = qcross[0]; joint->qrel2[1] = qcross[1]; joint->qrel2[2] = qcross[2]; joint->qrel2[3] = qcross[3]; } } void dJointGetUniversalAnchor( dJointID j, dVector3 result ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); dUASSERT( result, "bad result argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) getAnchor2( joint, result, joint->anchor2 ); else getAnchor( joint, result, joint->anchor1 ); } void dJointGetUniversalAnchor2( dJointID j, dVector3 result ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); dUASSERT( result, "bad result argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) getAnchor( joint, result, joint->anchor1 ); else getAnchor2( joint, result, joint->anchor2 ); } void dJointGetUniversalAxis1( dJointID j, dVector3 result ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); dUASSERT( result, "bad result argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) getAxis2( joint, result, joint->axis2 ); else getAxis( joint, result, joint->axis1 ); } void dJointGetUniversalAxis2( dJointID j, dVector3 result ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); dUASSERT( result, "bad result argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) getAxis( joint, result, joint->axis1 ); else getAxis2( joint, result, joint->axis2 ); } void dJointSetUniversalParam( dJointID j, int parameter, dReal value ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if (( parameter & 0xff00 ) == 0x100 ) { joint->limot2.set( parameter & 0xff, value ); } else { joint->limot1.set( parameter, value ); } } dReal dJointGetUniversalParam( dJointID j, int parameter ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if (( parameter & 0xff00 ) == 0x100 ) { return joint->limot2.get( parameter & 0xff ); } else { return joint->limot1.get( parameter ); } } void dJointGetUniversalAngles( dJointID j, dReal *angle1, dReal *angle2 ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) { joint->getAngles( angle2, angle1 ); *angle2 = -(*angle2); return; } else return joint->getAngles( angle1, angle2 ); } dReal dJointGetUniversalAngle1( dJointID j ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) return joint->getAngle2(); else return joint->getAngle1(); } dReal dJointGetUniversalAngle2( dJointID j ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) return -joint->getAngle1(); else return joint->getAngle2(); } dReal dJointGetUniversalAngle1Rate( dJointID j ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if ( joint->node[0].body ) { dVector3 axis; if ( joint->flags & dJOINT_REVERSE ) getAxis2( joint, axis, joint->axis2 ); else getAxis( joint, axis, joint->axis1 ); dReal rate = dCalcVectorDot3( axis, joint->node[0].body->avel ); if ( joint->node[1].body ) rate -= dCalcVectorDot3( axis, joint->node[1].body->avel ); return rate; } return 0; } dReal dJointGetUniversalAngle2Rate( dJointID j ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dUASSERT( joint, "bad joint argument" ); checktype( joint, Universal ); if ( joint->node[0].body ) { dVector3 axis; if ( joint->flags & dJOINT_REVERSE ) getAxis( joint, axis, joint->axis1 ); else getAxis2( joint, axis, joint->axis2 ); dReal rate = dCalcVectorDot3( axis, joint->node[0].body->avel ); if ( joint->node[1].body ) rate -= dCalcVectorDot3( axis, joint->node[1].body->avel ); return rate; } return 0; } void dJointAddUniversalTorques( dJointID j, dReal torque1, dReal torque2 ) { dxJointUniversal* joint = ( dxJointUniversal* )j; dVector3 axis1, axis2; dAASSERT( joint ); checktype( joint, Universal ); if ( joint->flags & dJOINT_REVERSE ) { dReal temp = torque1; torque1 = - torque2; torque2 = - temp; } getAxis( joint, axis1, joint->axis1 ); getAxis2( joint, axis2, joint->axis2 ); axis1[0] = axis1[0] * torque1 + axis2[0] * torque2; axis1[1] = axis1[1] * torque1 + axis2[1] * torque2; axis1[2] = axis1[2] * torque1 + axis2[2] * torque2; if ( joint->node[0].body != 0 ) dBodyAddTorque( joint->node[0].body, axis1[0], axis1[1], axis1[2] ); if ( joint->node[1].body != 0 ) dBodyAddTorque( joint->node[1].body, -axis1[0], -axis1[1], -axis1[2] ); } dJointType dxJointUniversal::type() const { return dJointTypeUniversal; } sizeint dxJointUniversal::size() const { return sizeof( *this ); } void dxJointUniversal::setRelativeValues() { dVector3 anchor; dJointGetUniversalAnchor(this, anchor); setAnchors( this, anchor[0], anchor[1], anchor[2], anchor1, anchor2 ); dVector3 ax1,ax2; dJointGetUniversalAxis1(this, ax1); dJointGetUniversalAxis2(this, ax2); if ( flags & dJOINT_REVERSE ) { setAxes( this, ax1[0],ax1[1],ax1[2], NULL, axis2 ); setAxes( this, ax2[0],ax2[1],ax2[2], axis1, NULL ); } else { setAxes( this, ax1[0],ax1[1],ax1[2], axis1, NULL ); setAxes( this, ax2[0],ax2[1],ax2[2], NULL, axis2 ); } computeInitialRelativeRotations(); }