Tekkotsu Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

IKThreeLink.cc

Go to the documentation of this file.
00001 #include "IKThreeLink.h"
00002 #include "Shared/debuget.h"
00003 #include "Shared/fmatSpatial.h"
00004 #include <limits>
00005 
00006 using namespace std;
00007 
00008 const float IKThreeLink::EPSILON=1e-3f;
00009 const std::string IKThreeLink::autoRegisterIKThreeLink = IKSolver::getRegistry().registerType<IKThreeLink>("IKThreeLink");
00010 
00011 bool IKThreeLink::solve(const Point& pEff, const Rotation& oriEff, KinematicJoint& j, const Position& pTgt, float posPri, const Orientation& oriTgt, float oriPri) const {
00012   bool converges[3] = { true, true, true };
00013   KinematicJoint* links[3] = { NULL, NULL, NULL };
00014   float origKnee;
00015   setLinks(j,links,3);
00016   
00017   // we're going to do an analytic solution for closest point on target surface.
00018   // find this target point
00019   fmat::Transform Tj = j.getFullT();
00020   fmat::Column<3> pEffBase(Tj*pEff);
00021   fmat::Quaternion qj = j.getWorldQuaternion(); //cout << "WorldQ: " << qj;
00022   Rotation oEffBase = qj*oriEff;
00023   fmat::Column<3> de;
00024   pTgt.computeErrorGradient(pEffBase,oEffBase,de);
00025   
00026   if(links[0]==NULL || posPri<=0) { // no mobile links
00027     // maybe we're conveniently at the solution... check error
00028     std::cerr << "ERROR: IKThreeLink trying to solve without any mobile joints (" << j.outputOffset.get() << ")" << std::endl;
00029     return de.norm()<=EPSILON;
00030   }
00031   
00032   origKnee = (links[2]!=NULL) ? links[2]->getQ() : 0;
00033   hasInverseSolution=false;
00034   fmat::Column<3> pTgtV = pEffBase + de;
00035   
00036   /*std::cout << "Q prior " << links[0]->getQ();
00037   if(links[1])
00038     cout << ' ' << links[1]->getQ();
00039   if(links[2])
00040     cout << ' ' << links[2]->getQ();
00041   cout << std::endl;*/
00042   if(links[1]!=NULL) {
00043     if(links[2]!=NULL)
00044       computeThirdLink(*links[2], pTgtV, j, pEff, invertThird, converges[2]);
00045     computeSecondLink(*links[1], pTgtV, j, pEff, false, converges[1]);
00046   }
00047   computeFirstLink(*links[0], pTgtV, j, pEff, converges[0]);
00048   
00049   if(hasInverseSolution && (!converges[0] || !converges[1])) {
00050     // we hit a joint limit, try inverse solution?
00051     //cout << "Hit limit, trying inverse " << inverseKnee << endl;
00052     float defaultKnee = links[2]->getQ();
00053     links[2]->tryQ(inverseKnee);
00054     if(links[1]!=NULL)
00055       computeSecondLink(*links[1], pTgtV, j, pEff, false, converges[1]);
00056     computeFirstLink(*links[0], pTgtV, j, pEff, converges[0]);
00057 
00058     /*std::cout << "Inverse Q post " << links[0]->getQ();
00059     if(links[1])
00060       cout << ' ' << links[1]->getQ();
00061     if(links[2])
00062       cout << ' ' << links[2]->getQ();
00063     cout << std::endl;*/
00064 
00065     if((!converges[0] || !converges[1]) && std::abs(origKnee-inverseKnee) > std::abs(origKnee-defaultKnee)) {
00066       // no luck, go back to default if it was closer to current position
00067       //cout << "Still hit limit, going back" << endl;
00068       links[2]->tryQ(defaultKnee);
00069       if(links[1]!=NULL)
00070         computeSecondLink(*links[1], pTgtV, j, pEff, false, converges[1]);
00071       computeFirstLink(*links[0], pTgtV, j, pEff, converges[0]);
00072     }
00073   }
00074   
00075   /*std::cout << "Q post " << links[0]->getQ();
00076   if(links[1])
00077     cout << ' ' << links[1]->getQ();
00078   if(links[2])
00079     cout << ' ' << links[2]->getQ();
00080   cout << std::endl;*/
00081   
00082   //check if root link is maxed out
00083   if(!converges[0]) {
00084     //redo child links since their parent was limited, first child is now root
00085     if(links[1]!=NULL) {
00086       hasInverseSolution=false;
00087       if(links[2]!=NULL) {
00088         computeSecondLink(*links[2], pTgtV, j, pEff, invertThird, converges[2]);
00089       }
00090       computeFirstLink(*links[1], pTgtV, j, pEff, converges[1]);
00091       if(hasInverseSolution && !converges[1]) {
00092         float defaultKnee = links[2]->getQ();
00093         links[2]->tryQ(inverseKnee);
00094         computeFirstLink(*links[1], pTgtV, j, pEff, converges[1]);
00095         if(!converges[1] && std::abs(origKnee-inverseKnee) > std::abs(origKnee-defaultKnee)) {
00096           // no luck, go back to default if it was closer to current position
00097           links[2]->tryQ(defaultKnee);
00098           computeFirstLink(*links[1], pTgtV, j, pEff, converges[1]);
00099         }
00100       }
00101     }
00102   }
00103   
00104   //check again, maybe now middle link is limited
00105   if(!converges[1] && links[2]!=NULL) {
00106     //redo last link
00107     computeFirstLink(*links[2], pTgtV, j, pEff, converges[2]);
00108   }
00109   
00110   de = j.getFullT()*pEff - pTgtV;
00111   return de.norm()<=EPSILON;
00112 }
00113 
00114 IKSolver::StepResult_t IKThreeLink::step(const Point& pEff, const Rotation& oriEff, KinematicJoint& j, const Position& pTgt, float pDist, float posPri, const Orientation& oriTgt, float oriDist, float oriPri) const {
00115   // find the closest target point
00116   fmat::Transform Tj = j.getFullT();
00117   fmat::Column<3> pEffBase(Tj*pEff);
00118   fmat::Quaternion qj = j.getWorldQuaternion(); //cout << "WorldQ: " << qj;
00119   Rotation oEffBase = qj*oriEff;
00120   Point de;
00121   pTgt.computeErrorGradient(pEffBase,oEffBase,de);
00122   
00123   StepResult_t res=SUCCESS;
00124   if(de.norm()>pDist) {
00125     de*=pDist/de.norm();
00126     res = PROGRESS;
00127   }
00128   Point pTgtP = pEffBase+de;
00129   if(solve(pEff,oriEff,j,pTgtP,posPri,oriTgt,oriPri))
00130     return res;
00131   return LIMITS;
00132 }
00133 
00134 unsigned int IKThreeLink::setLinks(KinematicJoint& eff, KinematicJoint* links[], unsigned int remain) {
00135   if(remain==0)
00136     return 0;
00137   if(eff.isMobile())
00138     --remain;
00139   unsigned int unused=remain;
00140   if(eff.getParent()!=NULL)
00141     unused=setLinks(*eff.getParent(),links,remain);
00142   if(eff.isMobile()) {
00143     links[remain-unused]=&eff;
00144     //cout << "Link " << remain-unused << " " << eff.outputOffset << " range " << eff.qmin << " to " << eff.qmax << endl;
00145   }
00146   return unused;
00147 }
00148 
00149 void IKThreeLink::computeFirstLinkRevolute(KinematicJoint& curlink, const fmat::Column<3>& Pobj, KinematicJoint& endlink, const fmat::Column<3>& Plink, bool& valid) const {
00150   // transform from world to joint origin (not applying link tranform, so as if q=0)
00151   fmat::Transform tr = (curlink.getParent()==NULL) ? curlink.getTo().inverse() : (curlink.getParent()->getFullT() * curlink.getTo()).inverse();
00152   fmat::Column<3> cObj = tr*Pobj; // so objective in 'native' reference frame
00153   fmat::Column<3> cEff = endlink.getT(curlink)*Plink; // effector relative to link transform
00154   
00155   //cout << "LINK1 " << cObj << ' ' << cEff << endl;
00156     
00157   if(std::abs(cEff[0])<EPSILON && std::abs(cEff[1])<EPSILON) {
00158     //Plink is along axis of rotation - nothing we do is going to move it, so don't move at all
00159     valid=true; //debatable
00160     return;
00161   }
00162   if(std::abs(cObj[0])<EPSILON && std::abs(cObj[1])<EPSILON) {
00163     //objective point is along axis of rotation - current location is as good as any, so don't move at all
00164     valid=true; //debatable
00165     return;
00166   }
00167   float ao=atan2(cObj[1],cObj[0]); // angle of objective
00168   float ae=atan2(cEff[1],cEff[0]); // offset caused by effector
00169   valid=curlink.tryQ(normalize_angle(ao-ae-curlink.qOffset));
00170 }
00171 
00172 void IKThreeLink::computeFirstLinkPrismatic(KinematicJoint& curlink, const fmat::Column<3>& Pobj, KinematicJoint& endlink, const fmat::Column<3>& Plink, bool& valid) const {
00173   // transform from world to joint origin (not applying link tranform, so as if q=0)
00174   fmat::Transform tr = (curlink.getParent()==NULL) ? curlink.getTo().inverse() : (curlink.getParent()->getFullT() * curlink.getTo()).inverse();
00175   fmat::Column<3> cObj = tr*Pobj; // so objective in 'native' reference frame
00176   //cout << "LINK1 " << cObj << ' ' << endl;
00177   valid=curlink.tryQ(cObj[2] - curlink.qOffset);
00178 }
00179 
00180 /*! assumes that z axis of parent frame and current frame are either parallel or orthogonal
00181  *  
00182  *  todo: would be nice if this could handle intermediary angles... currently anything not parallel is assumed to be orthogonal */
00183 void IKThreeLink::computeSecondLinkRevolute(KinematicJoint& curlink, const fmat::Column<3>& Pobj, KinematicJoint& endlink, const fmat::Column<3>& Plink, bool invert, bool& valid) const {
00184   // link point in current frame
00185   fmat::Column<3> cEff = endlink.getT(curlink)*Plink;
00186   if(std::abs(cEff[0])<EPSILON && std::abs(cEff[1])<EPSILON) {
00187     //Plink is along axis of rotation - nothing we do is going to move it, so don't move at all
00188     valid=true; //debatable
00189     return;
00190   }
00191   
00192   //cout << "LINK2 " << cEff << endl;
00193   
00194   // object point in parent frame
00195   fmat::Column<3> pObj = curlink.getParent()->getFullInvT()*Pobj;
00196   
00197   if(std::abs(curlink.getRotation()(2,2)) >= (1-EPSILON)) {
00198     // z axis parallel
00199     
00200     // law of cosines
00201     //fmat::Column<3> pCur = curlink.getPosition();
00202     float adj1 = curlink.r;
00203     float adj2 = hypotf(cEff[0],cEff[1]);
00204     float opp = hypotf(pObj[0],pObj[1]);
00205     float a;
00206     if(adj1 + opp <= adj2 || adj2 + opp <= adj1) {
00207       // objective too close in -- fold up
00208       //cout << "too close!" << endl;
00209       a = 0;
00210     } else if(adj1 + adj2 <= opp) {
00211       // objective too far away -- extend out
00212       //cout << "too far!" << endl;
00213       a = (float)M_PI;
00214     } else {
00215       float ca = ( adj1*adj1 + adj2*adj2 - opp*opp ) / ( 2*adj1*adj2);
00216       //cout << "Law of cosines: " << adj1 << ' ' << adj2 << ' ' << opp << " = " << ca << endl;
00217       a = std::acos(ca);
00218     }
00219     
00220     float offset = -curlink.qOffset - std::atan2(cEff[1],cEff[0]);
00221     if(curlink.r>0)
00222       offset+=(float)M_PI;
00223     
00224     // solutions are offset±a
00225     float q1,q2;
00226     if(invert) {
00227       q1 = offset-a;
00228       q2 = offset+a;
00229     } else {
00230       q1 = offset+a;
00231       q2 = offset-a;
00232     }
00233     
00234     //std::cout << "angle " << a << " (" << a/M_PI*180 << "°) offset " << offset << " q1 " << q1 << " (" << q1/M_PI*180 << "°)" << " q2 " << q2 << " (" << q2/M_PI*180 << "°)" << std::endl;
00235     valid = curlink.tryQ( normalize_angle(q1) );
00236     if(!valid) { // try other solution
00237       //cout << "trying inverse" << endl;
00238       valid = curlink.tryQ( normalize_angle(q2) );
00239       if(!valid) { // go back
00240         //cout << "reverting inverse" << endl;
00241         valid = curlink.tryQ( normalize_angle(q1) );
00242       }
00243     } else if(std::abs(q1-q2)>EPSILON && curlink.qmin<=q2 && q2<=curlink.qmax) {
00244       //cout << "has inverse" << endl;
00245       hasInverseSolution=true;
00246       inverseKnee=q2;
00247     }
00248     
00249   } else {
00250     // Assume orthogonal curlink z and parent z
00251     // Need to solve for q: (cr + coh/tan(q))² + lz² = por²
00252     // Simplifies to quadratic: x² + 2·cr·x + cr² + lz² - por² = 0, where x=coh/tan(q)
00253     
00254     float cr = std::abs((float)curlink.r); // radius from parent z aka hip length
00255     //float lr = hypotf(cEff[0],cEff[1]); // radius of end link aka effector about current z
00256     float lz = std::abs(cEff[2]); // effector offset along current z
00257     float coh = pObj[2] - curlink.d; // height of objective from plane of current link about parent z
00258     if(curlink.alpha<0)
00259       coh=-coh; // sign depends on direction of parent z vs. curlink
00260     float por = hypotf(pObj[0],pObj[1]); // radius of objective from parent z
00261     //cout << "cr " << cr << " lz " << lz << " coh " << coh << " por " << por << endl;
00262     
00263     float x;
00264     if(por<lz) {
00265       // no solution — effector is too far to the side (b*b - 4*c) < 0
00266       //cout << "no sol" << endl;
00267       // stay at current position?
00268       valid=false; //debatable
00269       return;
00270     } else if(false /*por < cr*/) {
00271       // above the hip, choose "inner" solution
00272       //cout << "inner" << endl;
00273       //float a = 1
00274       float b = 2*cr;
00275       float c = cr*cr + lz*lz - por*por;
00276       x = ( -b + std::sqrt(b*b - 4*c) ) / 2;
00277       //cout << "b " << b << " c " << c << " x " << x << endl;
00278     } else {
00279       // outside the hip, choose "outer" solution
00280       //cout << "outer" << endl;
00281       //float a = 1
00282       float b = 2*cr;
00283       float c = cr*cr + lz*lz - por*por;
00284       x = ( -b + std::sqrt(b*b - 4*c) ) / 2;
00285       //cout << "b " << b << " c " << c << " x " << x << endl;
00286     }
00287     //if(cEff[0]<0)
00288     //  x=-x;
00289     
00290     // x = coh / tan(q), solve for q:
00291     float q = std::atan2(coh, x);
00292     //cout << "pre offset q " << q << " offset " << -std::atan2(cEff[1],cEff[0]) << endl;
00293     q -= curlink.qOffset + std::atan2(cEff[1],cEff[0]); // cancel offsets from effector point and qOffset
00294     valid = curlink.tryQ( normalize_angle(q) );
00295   }
00296 }
00297 
00298 /*! First link will perform some rotation, this function needs to set
00299  *  the distance to match the projection of the objective into the
00300  *  plane of rotation.  Very similar to computeThirdLinkPrismatic()
00301  *  except how this projection is done (no ring of motion) */
00302 void IKThreeLink::computeSecondLinkPrismatic(KinematicJoint& curlink, const fmat::Column<3>& Pobj, KinematicJoint& endlink, const fmat::Column<3>& Plink, bool invert, bool& valid) const {
00303   KinematicJoint* tilt = curlink.getParent();
00304   while(!tilt->isMobile())
00305     tilt=tilt->getParent();
00306   
00307   // object point in pan's frame
00308   fmat::Column<3> tObj = tilt->getFullInvT()*Pobj;
00309   //cout << "tObj " << tObj << endl;
00310   
00311   fmat::Transform curToTilt = curlink.getParent()->getT(*tilt) * curlink.getTo();
00312   
00313   const fmat::Column<3>& neck = curToTilt.translation();
00314   float neckD2 = neck.sumSq();
00315   
00316   const fmat::Column<3>& tcz = curToTilt.column(2);
00317   float inner = fmat::dotProduct(tcz,-neck); // a·b·cos(C), |tcz|=1 so actually neckD·cos(C) 
00318   //cout << "neck " << std::sqrt(neckD2) << " inner " << inner << endl;
00319   
00320   float tod2 = tObj[0]*tObj[0] + tObj[1]*tObj[1];
00321   //cout << "tod " << std::sqrt(tod2) << endl;
00322   
00323   valid = curlink.tryQ(computePrismaticQ(tod2,neckD2,inner) - curlink.qOffset);
00324 }
00325 
00326 
00327 /*! We'll compute the knee angle first, using:
00328  *  - length of the thigh ('thigh')
00329  *  - distance from knee (curlink) to Plink, projected to plane of curlink's rotation (cEffD)
00330  *  - distance from shoulder (previous link) to Pobj (ringObjD)
00331  *
00332  *  Two knee (curlink) configurations are supported: parallel to parLink (where parLink.d can be bundled as a z offset of the effector)
00333  *  or parallel to grandparent, with parLink.r==0.
00334  *  In the former case, the parent link's origin forms a ring about the grandparent's
00335  *  link of radius parLink.r.  It is the distance of the objective to the ring (or just the center point
00336  *  when parLink.r==0) that curlink must match the effector's distance.
00337  */
00338 void IKThreeLink::computeThirdLinkRevolute(KinematicJoint& curlink, const fmat::Column<3>& Pobj, KinematicJoint& endlink, const fmat::Column<3>& Plink, bool invert, bool& valid) const {
00339   // link point in current frame
00340   fmat::Column<3> cEff = endlink.getT(curlink)*Plink;
00341   if(std::abs(cEff[0])<EPSILON && std::abs(cEff[1])<EPSILON) {
00342     //Plink is along axis of rotation - nothing we do is going to move it, so don't move at all
00343     valid=true; //debatable
00344     return;
00345   }
00346   //cout << curlink.outputOffset << " cEff " << cEff << " dist " << cEff.norm() << " (in plane " << fmat::SubVector<2>(cEff).norm() << ")" << endl;
00347   
00348   KinematicJoint& parLink = *curlink.getParent();
00349   //std::cout << "par to cur dist " << curlink.getPosition().norm() << ' ' << curlink.getPosition() << std::endl;
00350   KinematicJoint& gparLink = *parLink.getParent();
00351   //std::cout << "check ring r " << parLink.r << ' ' << (parLink.getPosition() - fmat::pack(0,0,parLink.d)).norm() << std::endl;
00352   
00353   // object point in grandparent's frame
00354   fmat::Column<3> gpObj = gparLink.getFullInvT()*Pobj;
00355   gpObj[2]-=parLink.d; // subtract parLink height so now relative to plane of ring of motion
00356   //cout << "gpObj " << gpObj << endl;
00357   
00358   float thigh, thigh2; // distance from parent to current, projected to curlink's plane of rotation
00359   float ringObjD2; // distance from parent's ring of motion to objective, projected to curlink's plane of rotation
00360   
00361   // check condition α = ±90° (e.g. Aibo legs, knee is perpendicular to elevator)
00362   const bool KNEE_PERP = std::abs(std::abs((float)curlink.alpha) - (float)M_PI_2) < EPSILON;
00363   if(KNEE_PERP) {
00364     // math gets wonky if parLink.r≠0, effector's z component non-planar to objective radial, don't know how to recover
00365     ASSERT(parLink.r==0,"IKThreeLink can't handle perpendicular knees with non-zero hip radius");
00366     
00367     thigh2 = curlink.r*curlink.r + curlink.d*curlink.d;
00368     thigh = std::sqrt(thigh2);
00369     
00370     float lz = cEff[2]; // distance of link point from knee rotation plane
00371     float rd = std::sqrt(gpObj[0]*gpObj[0] + gpObj[1]*gpObj[1]) - std::abs((float)parLink.r); // distance from ring of motion in grandparent xy
00372     ringObjD2 = rd*rd + gpObj[2]*gpObj[2] - lz*lz;
00373     if(ringObjD2<0)
00374       ringObjD2=0;
00375     
00376   } else { // assume Chiara legs, knee is parallel to elevator
00377     //cout << "PARALLEL" << endl;
00378     thigh = std::abs((float)curlink.r);
00379     thigh2=thigh*thigh;
00380     
00381     float lz = cEff[2] - curlink.d; // distance of link point from knee rotation plane
00382     
00383     // now account for curlink.d and any pl.z, need to project objective distance onto curlink plane of rotation
00384     // grandparent origin, curlink origin, and objective form right triangle.  curlink-objective is parallel to grandparent xy
00385     float d2 = gpObj[0]*gpObj[0] + gpObj[1]*gpObj[1] - lz*lz;
00386     if(d2<0) 
00387       d2=0;
00388     float rd = std::sqrt(d2) - std::abs((float)parLink.r); // distance from ring of motion in grandparent xy
00389     //cout << "d2 " << d2 << " lz " << lz << " rd " << rd << endl;
00390     ringObjD2 = rd*rd + gpObj[2]*gpObj[2];
00391 
00392     /* Singularity issue: current implementation cannot reach points "behind" (x<0) the hip because we are
00393      setting oringd to be the closest point on the *ring*, but with hip rotation limits we actually have an *arc*.
00394      However this limitation prevents a singularity as the target point passes behind the hip, we would have to
00395      instantly snap 180° to the opposite side of the arc to continue tracking.  Instead what we do here is
00396      to allow the hip to hit its joint limit and just stay there on the same side, even though technically the
00397      point is within reach.  We could do something like add a flag to allow this solution in case the instantanious
00398      motion is acceptable. */
00399   }
00400   float ringObjD = std::sqrt(ringObjD2);
00401   
00402   // distance from curlink to pLink projected to curlink's xy plane:
00403   float ln2 = cEff[0]*cEff[0] + cEff[1]*cEff[1];
00404   float ln = std::sqrt(ln2);
00405   
00406   //cout << "thigh " << thigh << " oringd " << ringObjD << " link " << ln << endl;
00407   
00408   // test for reachability otherwise angle will be NaN
00409   float a;
00410   if( (ln + ringObjD <= thigh) || (ringObjD + thigh <= ln)) {
00411     //cout << "out of reach, interior" << endl;
00412     a = 0;
00413     
00414   } else if(ln + thigh <= ringObjD) {
00415     //cout << "out of reach, exterior" << endl;
00416     a = (float)M_PI;
00417     
00418   } else {
00419     // law of cosines to find angle between parent and solution point
00420     a = std::acos( (ln2 + thigh2 - ringObjD2) / (2*ln*thigh) );
00421   }
00422   float offset = -curlink.qOffset - std::atan2(cEff[1],cEff[0]);
00423   
00424   if(KNEE_PERP) {
00425     if(curlink.alpha<0)
00426       offset -= std::atan2((float)curlink.d,(float)curlink.r);
00427     else
00428       offset += std::atan2((float)curlink.d,(float)curlink.r);
00429     a = (float)M_PI-a;
00430   } else if(curlink.r>0) {
00431     a = (float)M_PI-a;
00432   }
00433 
00434   // solutions are offset ± a
00435   float q1,q2;
00436   if(invert) {
00437     q1 = offset-a;
00438     q2 = offset+a;
00439   } else {
00440     q1 = offset+a;
00441     q2 = offset-a;
00442   }
00443   //std::cout << "angle " << a << " (" << a/M_PI*180 << "°) offset " << offset << " q1 " << q1 << " (" << q1/M_PI*180 << "°) q2 " << q2 << " (" << q2/M_PI*180 << ")" << std::endl;
00444   
00445   valid = curlink.tryQ( normalize_angle(q1) );
00446   if(!valid) { // try other solution
00447     //cout << "trying inverse" << endl;
00448     valid = curlink.tryQ( normalize_angle(q2) );
00449     if(!valid) { // go back
00450       //cout << "reverting inverse" << endl;
00451       valid = curlink.tryQ( normalize_angle(q1) );
00452     }
00453   } else if(std::abs(q1-q2)>EPSILON && curlink.qmin<=q2 && q2<=curlink.qmax) {
00454     //cout << "has inverse" << endl;
00455     hasInverseSolution=true;
00456     inverseKnee=q2;
00457   }
00458 }
00459 
00460 /*! This version is intended mainly for solving to have a pan-tilt camera look at a point in space.
00461  *  The camera is the third link, and should be marked as prismatic.  There can be immobile
00462  *  frames between this and the tilt joint, as we often want a specific configuration of x and y axes
00463  *  to match image coordinates.  Z must point out of the camera, toward the scene.
00464  *
00465  *  @a Plink is ignored.  This only solves for the z axis, and that must intersect the pan axis.
00466  *  (The math gets really hairy if the camera is not aligned to a radial from the pan joint.)
00467  *  
00468  *  The approach is similar to computeThirdLinkRevolute(), using
00469  *  - length of the neck, i.e. distance from tilt to camera
00470  *  - angle at the camera (curlink) between neck link and z axis
00471  *  - distance from tilt's ring of motion to Pobj (ringObjD)
00472  */
00473 void IKThreeLink::computeThirdLinkPrismatic(KinematicJoint& curlink, const fmat::Column<3>& Pobj, KinematicJoint& endlink, const fmat::Column<3>&, bool invert, bool& valid) const {
00474   //cout << "PRISMATIC" << endl;
00475   
00476   KinematicJoint* tilt = curlink.getParent();
00477   while(!tilt->isMobile())
00478     tilt=tilt->getParent();
00479   KinematicJoint* pan = tilt->getParent();
00480   
00481   // object point in pan's frame
00482   fmat::Column<3> gpObj = pan->getFullInvT()*Pobj;
00483   gpObj[2]-=tilt->d; // subtract tilt height so now relative to plane of ring of motion
00484   //cout << "gpObj " << gpObj << endl;
00485   
00486   fmat::Transform curToTilt = curlink.getParent()->getT(*tilt) * curlink.getTo();
00487   
00488   const fmat::Column<3>& neck = curToTilt.translation();
00489   float neckD2 = neck.sumSq();
00490   
00491   const fmat::Column<3>& tcz = curToTilt.column(2);
00492   float inner = fmat::dotProduct(tcz,-neck); // a·b·cos(C), |tcz|=1 so actually neckD·cos(C) 
00493   //cout << "neck " << std::sqrt(neckD2) << " inner " << inner << endl;
00494   
00495   float d2 = gpObj[0]*gpObj[0] + gpObj[1]*gpObj[1];
00496   float rd = std::sqrt(d2) - std::abs((float)tilt->r); // distance from ring of motion in grandparent xy
00497   float ringObjD2 = rd*rd + gpObj[2]*gpObj[2];
00498   //cout << "d " << std::sqrt(d2) << " rd " << rd << " ringObjD " << std::sqrt(ringObjD2) << endl;
00499   
00500   valid = curlink.tryQ(computePrismaticQ(ringObjD2,neckD2,inner) - curlink.qOffset);
00501 }
00502 
00503 fmat::fmatReal IKThreeLink::computePrismaticQ(fmat::fmatReal objD2, fmat::fmatReal neckD2, fmat::fmatReal inner) {
00504   // law of cosines: c² = a² + b² - 2·a·b·cos(C)
00505   // where c = ringObjD, a = neckD, a·b·cos(C) = inner, solve for b:
00506   // b² - 2·a·b·cos(C) + a² - c² = 0
00507   // apply quadratic formula:
00508   // a'x² + b'x + c' = 0, x = (-b' ± √(b'² - 4·a'·c'))/(2a')
00509   // where a' = 1, b' = -2·neckD·cos(C), c' = neckD2 - ringObjD2, x=b
00510   float b = -2*inner;
00511   float c = neckD2 - objD2;
00512   float r = b*b - 4*c;
00513   if(r<0) {
00514     //cout << "NO SOLUTION" << endl;
00515     // closest solution is at right angle to objD extension, aka inner
00516     float q2 = neckD2 - inner*inner;
00517     if(q2<0) {
00518       // should only happen due to numerical roundoff, if ever
00519       cerr << "IKThreeLink::computePrismaticQ numeric error, q² < 0 : " << q2 << " from " << neckD2 << ' ' << inner << ' ' << objD2 << endl;
00520       return 0;
00521     } else {
00522       return std::sqrt(q2);
00523     }
00524   } else {
00525     return (-b + std::sqrt(r))/2; // only want largest/positive solution
00526   }
00527 }
00528 
00529 
00530 
00531 /*! @file
00532  * @brief 
00533  * @author Ethan Tira-Thompson (ejt) (Creator)
00534  */

Tekkotsu v5.1CVS
Generated Fri Mar 16 05:26:42 2012 by Doxygen 1.6.3