Tekkotsu Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

IKThreeLink.cc

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

Tekkotsu v5.1CVS
Generated Mon May 9 04:58:42 2016 by Doxygen 1.6.3