frame.cpp
Go to the documentation of this file.
00001 /****************************************************************************
00002 
00003  Copyright (C) 2002-2013 Gilles Debunne. All rights reserved.
00004 
00005  This file is part of the QGLViewer library version 2.4.0.
00006 
00007  http://www.libqglviewer.com - contact@libqglviewer.com
00008 
00009  This file may be used under the terms of the GNU General Public License 
00010  versions 2.0 or 3.0 as published by the Free Software Foundation and
00011  appearing in the LICENSE file included in the packaging of this file.
00012  In addition, as a special exception, Gilles Debunne gives you certain 
00013  additional rights, described in the file GPL_EXCEPTION in this package.
00014 
00015  libQGLViewer uses dual licensing. Commercial/proprietary software must
00016  purchase a libQGLViewer Commercial License.
00017 
00018  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
00019  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00020 
00021 *****************************************************************************/
00022 
00023 #include "domUtils.h"
00024 #include "frame.h"
00025 #include <math.h>
00026 
00027 using namespace qglviewer;
00028 using namespace std;
00029 
00030 
00035 Frame::Frame()
00036   : constraint_(NULL), referenceFrame_(NULL)
00037 {}
00038 
00045 Frame::Frame(const Vec& position, const Quaternion& orientation)
00046   : t_(position), q_(orientation), constraint_(NULL), referenceFrame_(NULL)
00047 {}
00048 
00054 Frame& Frame::operator=(const Frame& frame)
00055 {
00056   // Automatic compiler generated version would not emit the modified() signals as is done in
00057   // setTranslationAndRotation.
00058   setTranslationAndRotation(frame.translation(), frame.rotation());
00059   setConstraint(frame.constraint());
00060   setReferenceFrame(frame.referenceFrame());
00061   return *this;
00062 }
00063 
00068 Frame::Frame(const Frame& frame)
00069   : QObject()
00070 {
00071   (*this) = frame;
00072 }
00073 
00075 
00123 const GLdouble* Frame::matrix() const
00124 {
00125   static GLdouble m[4][4];
00126   getMatrix(m);
00127   return (const GLdouble*)(m);
00128 }
00129 
00131 void Frame::getMatrix(GLdouble m[4][4]) const
00132 {
00133   q_.getMatrix(m);
00134 
00135   m[3][0] = t_[0];
00136   m[3][1] = t_[1];
00137   m[3][2] = t_[2];
00138 }
00139 
00141 void Frame::getMatrix(GLdouble m[16]) const
00142 {
00143   q_.getMatrix(m);
00144 
00145   m[12] = t_[0];
00146   m[13] = t_[1];
00147   m[14] = t_[2];
00148 }
00149 
00164 Frame Frame::inverse() const
00165 {
00166   Frame fr(-(q_.inverseRotate(t_)), q_.inverse());
00167   fr.setReferenceFrame(referenceFrame());
00168   return fr;
00169 }
00170 
00196 const GLdouble* Frame::worldMatrix() const
00197 {
00198   // This test is done for efficiency reasons (creates lots of temp objects otherwise).
00199   if (referenceFrame())
00200   {
00201     static Frame fr;
00202     fr.setTranslation(position());
00203     fr.setRotation(orientation());
00204     return fr.matrix();
00205   }
00206   else
00207     return matrix();
00208 }
00209 
00211 void Frame::getWorldMatrix(GLdouble m[4][4]) const
00212 {
00213   const GLdouble* mat = worldMatrix();
00214   for (int i=0; i<4; ++i)
00215     for (int j=0; j<4; ++j)
00216       m[i][j] = mat[i*4+j];
00217 }
00218 
00220 void Frame::getWorldMatrix(GLdouble m[16]) const
00221 {
00222   const GLdouble* mat = worldMatrix();
00223   for (int i=0; i<16; ++i)
00224       m[i] = mat[i];
00225 }
00226 
00228 void Frame::setFromMatrix(const GLdouble m[4][4])
00229 {
00230   if (fabs(m[3][3]) < 1E-8)
00231     {
00232       qWarning("Frame::setFromMatrix: Null homogeneous coefficient");
00233       return;
00234     }
00235 
00236   double rot[3][3];
00237   for (int i=0; i<3; ++i)
00238     {
00239       t_[i] = m[3][i] / m[3][3];
00240       for (int j=0; j<3; ++j)
00241         // Beware of the transposition (OpenGL to European math)
00242         rot[i][j] = m[j][i] / m[3][3];
00243     }
00244   q_.setFromRotationMatrix(rot);
00245   Q_EMIT modified();
00246 }
00247 
00272 void Frame::setFromMatrix(const GLdouble m[16])
00273 {
00274   GLdouble mat[4][4];
00275   for (int i=0; i<4; ++i)
00276     for (int j=0; j<4; ++j)
00277       mat[i][j] = m[i*4+j];
00278   setFromMatrix(mat);
00279 }
00280 
00282 
00283 
00285 void Frame::setTranslation(float x, float y, float z)
00286 {
00287   setTranslation(Vec(x, y, z));
00288 }
00289 
00291 void Frame::getTranslation(float& x, float& y, float& z) const
00292 {
00293   const Vec t = translation();
00294   x = t[0];
00295   y = t[1];
00296   z = t[2];
00297 }
00298 
00300 void Frame::setRotation(double q0, double q1, double q2, double q3)
00301 {
00302   setRotation(Quaternion(q0, q1, q2, q3));
00303 }
00304 
00308 void Frame::getRotation(double& q0, double& q1, double& q2, double& q3) const
00309 {
00310   const Quaternion q = rotation();
00311   q0 = q[0];
00312   q1 = q[1];
00313   q2 = q[2];
00314   q3 = q[3];
00315 }
00316 
00318 
00327 void Frame::translate(const Vec& t)
00328 {
00329   Vec tbis = t;
00330   translate(tbis);
00331 }
00332 
00335 void Frame::translate(Vec& t)
00336 {
00337   if (constraint())
00338     constraint()->constrainTranslation(t, this);
00339   t_ += t;
00340   Q_EMIT modified();
00341 }
00342 
00344 void Frame::translate(float x, float y, float z)
00345 {
00346   Vec t(x,y,z);
00347   translate(t);
00348 }
00349 
00351 void Frame::translate(float& x, float& y, float& z)
00352 {
00353   Vec t(x,y,z);
00354   translate(t);
00355   x = t[0];
00356   y = t[1];
00357   z = t[2];
00358 }
00359 
00368 void Frame::rotate(const Quaternion& q)
00369 {
00370   Quaternion qbis = q;
00371   rotate(qbis);
00372 }
00373 
00376 void Frame::rotate(Quaternion& q)
00377 {
00378   if (constraint())
00379     constraint()->constrainRotation(q, this);
00380   q_ *= q;
00381   q_.normalize(); // Prevents numerical drift
00382   Q_EMIT modified();
00383 }
00384 
00386 void Frame::rotate(double& q0, double& q1, double& q2, double& q3)
00387 {
00388   Quaternion q(q0,q1,q2,q3);
00389   rotate(q);
00390   q0 = q[0];
00391   q1 = q[1];
00392   q2 = q[2];
00393   q3 = q[3];
00394 }
00395 
00397 void Frame::rotate(double q0, double q1, double q2, double q3)
00398 {
00399   Quaternion q(q0,q1,q2,q3);
00400   rotate(q);
00401 }
00402 
00414 void Frame::rotateAroundPoint(Quaternion& rotation, const Vec& point)
00415 {
00416   if (constraint())
00417     constraint()->constrainRotation(rotation, this);
00418   q_ *= rotation;
00419   q_.normalize(); // Prevents numerical drift
00420   Vec trans = point + Quaternion(inverseTransformOf(rotation.axis()), rotation.angle()).rotate(position()-point) - t_;
00421   if (constraint())
00422     constraint()->constrainTranslation(trans, this);
00423   t_ += trans;
00424   Q_EMIT modified();
00425 }
00426 
00429 void Frame::rotateAroundPoint(const Quaternion& rotation, const Vec& point)
00430 {
00431   Quaternion rot = rotation;
00432   rotateAroundPoint(rot, point);
00433 }
00434 
00436 
00443 void Frame::setPosition(const Vec& position)
00444 {
00445   if (referenceFrame())
00446     setTranslation(referenceFrame()->coordinatesOf(position));
00447   else
00448     setTranslation(position);
00449 }
00450 
00452 void Frame::setPosition(float x, float y, float z)
00453 {
00454   setPosition(Vec(x, y, z));
00455 }
00456 
00462 void Frame::setPositionAndOrientation(const Vec& position, const Quaternion& orientation)
00463 {
00464   if (referenceFrame())
00465     {
00466       t_ = referenceFrame()->coordinatesOf(position);
00467       q_ = referenceFrame()->orientation().inverse() * orientation;
00468     }
00469   else
00470     {
00471       t_ = position;
00472       q_ = orientation;
00473     }
00474   Q_EMIT modified();
00475 }
00476 
00477 
00483 void Frame::setTranslationAndRotation(const Vec& translation, const Quaternion& rotation)
00484 {
00485   t_ = translation;
00486   q_ = rotation;
00487   Q_EMIT modified();
00488 }
00489 
00490 
00492 void Frame::getPosition(float& x, float& y, float& z) const
00493 {
00494   Vec p = position();
00495   x = p.x;
00496   y = p.y;
00497   z = p.z;
00498 }
00499 
00505 void Frame::setOrientation(const Quaternion& orientation)
00506 {
00507   if (referenceFrame())
00508     setRotation(referenceFrame()->orientation().inverse() * orientation);
00509   else
00510     setRotation(orientation);
00511 }
00512 
00514 void Frame::setOrientation(double q0, double q1, double q2, double q3)
00515 {
00516   setOrientation(Quaternion(q0, q1, q2, q3));
00517 }
00518 
00526 void Frame::getOrientation(double& q0, double& q1, double& q2, double& q3) const
00527 {
00528   Quaternion o = orientation();
00529   q0 = o[0];
00530   q1 = o[1];
00531   q2 = o[2];
00532   q3 = o[3];
00533 }
00534 
00537 Quaternion Frame::orientation() const
00538 {
00539   Quaternion res = rotation();
00540   const Frame* fr = referenceFrame();
00541   while (fr != NULL)
00542     {
00543       res = fr->rotation() * res;
00544       fr  = fr->referenceFrame();
00545     }
00546   return res;
00547 }
00548 
00549 
00551 
00556 void Frame::setTranslationWithConstraint(Vec& translation)
00557 {
00558   Vec deltaT = translation - this->translation();
00559   if (constraint())
00560     constraint()->constrainTranslation(deltaT, this);
00561 
00562   setTranslation(this->translation() + deltaT);
00563   translation = this->translation();
00564 }
00565 
00570 void Frame::setRotationWithConstraint(Quaternion& rotation)
00571 {
00572   Quaternion deltaQ = this->rotation().inverse() * rotation;
00573   if (constraint())
00574     constraint()->constrainRotation(deltaQ, this);
00575 
00576   // Prevent numerical drift
00577   deltaQ.normalize();
00578 
00579   setRotation(this->rotation() * deltaQ);
00580   q_.normalize();
00581   rotation = this->rotation();
00582 }
00583 
00586 void Frame::setTranslationAndRotationWithConstraint(Vec& translation, Quaternion& rotation)
00587 {
00588   Vec deltaT = translation - this->translation();
00589   Quaternion deltaQ = this->rotation().inverse() * rotation;
00590 
00591   if (constraint())
00592     {
00593       constraint()->constrainTranslation(deltaT, this);
00594       constraint()->constrainRotation(deltaQ, this);
00595     }
00596 
00597   // Prevent numerical drift
00598   deltaQ.normalize();
00599 
00600   t_ += deltaT;
00601   q_ *= deltaQ;
00602   q_.normalize();
00603 
00604   translation = this->translation();
00605   rotation = this->rotation();
00606 
00607   Q_EMIT modified();
00608 }
00609 
00612 void Frame::setPositionWithConstraint(Vec& position)
00613 {
00614   if (referenceFrame())
00615     position = referenceFrame()->coordinatesOf(position);
00616 
00617   setTranslationWithConstraint(position);
00618 }
00619 
00622 void Frame::setOrientationWithConstraint(Quaternion& orientation)
00623 {
00624   if (referenceFrame())
00625     orientation = referenceFrame()->orientation().inverse() * orientation;
00626 
00627   setRotationWithConstraint(orientation);
00628 }
00629 
00632 void Frame::setPositionAndOrientationWithConstraint(Vec& position, Quaternion& orientation)
00633 {
00634   if (referenceFrame())
00635     {
00636       position = referenceFrame()->coordinatesOf(position);
00637       orientation = referenceFrame()->orientation().inverse() * orientation;
00638     }
00639   setTranslationAndRotationWithConstraint(position, orientation);
00640 }
00641 
00642 
00644 
00656 void Frame::setReferenceFrame(const Frame* const refFrame)
00657 {
00658   if (settingAsReferenceFrameWillCreateALoop(refFrame))
00659     qWarning("Frame::setReferenceFrame would create a loop in Frame hierarchy");
00660   else
00661     {
00662       bool identical = (referenceFrame_ == refFrame);
00663       referenceFrame_ = refFrame;
00664       if (!identical)
00665         Q_EMIT modified();
00666     }
00667 }
00668 
00671 bool Frame::settingAsReferenceFrameWillCreateALoop(const Frame* const frame)
00672 {
00673   const Frame* f = frame;
00674   while (f != NULL)
00675     {
00676       if (f == this)
00677         return true;
00678       f = f->referenceFrame();
00679     }
00680   return false;
00681 }
00682 
00684 
00693 Vec Frame::coordinatesOf(const Vec& src) const
00694 {
00695   if (referenceFrame())
00696     return localCoordinatesOf(referenceFrame()->coordinatesOf(src));
00697   else
00698     return localCoordinatesOf(src);
00699 }
00700 
00706 Vec Frame::inverseCoordinatesOf(const Vec& src) const
00707 {
00708   const Frame* fr = this;
00709   Vec res = src;
00710   while (fr != NULL)
00711     {
00712       res = fr->localInverseCoordinatesOf(res);
00713       fr  = fr->referenceFrame();
00714     }
00715   return res;
00716 }
00717 
00722 Vec Frame::localCoordinatesOf(const Vec& src) const
00723 {
00724   return rotation().inverseRotate(src - translation());
00725 }
00726 
00731 Vec Frame::localInverseCoordinatesOf(const Vec& src) const
00732 {
00733   return rotation().rotate(src) + translation();
00734 }
00735 
00740 Vec Frame::coordinatesOfFrom(const Vec& src, const Frame* const from) const
00741 {
00742   if (this == from)
00743     return src;
00744   else
00745     if (referenceFrame())
00746       return localCoordinatesOf(referenceFrame()->coordinatesOfFrom(src, from));
00747     else
00748       return localCoordinatesOf(from->inverseCoordinatesOf(src));
00749 }
00750 
00755 Vec Frame::coordinatesOfIn(const Vec& src, const Frame* const in) const
00756 {
00757   const Frame* fr = this;
00758   Vec res = src;
00759   while ((fr != NULL) && (fr != in))
00760     {
00761       res = fr->localInverseCoordinatesOf(res);
00762       fr  = fr->referenceFrame();
00763     }
00764 
00765   if (fr != in)
00766     // in was not found in the branch of this, res is now expressed in the world
00767     // coordinate system. Simply convert to in coordinate system.
00768     res = in->coordinatesOf(res);
00769 
00770   return res;
00771 }
00772 
00774 
00776 void Frame::getCoordinatesOf(const float src[3], float res[3]) const
00777 {
00778   const Vec r = coordinatesOf(Vec(src));
00779   for (int i=0; i<3 ; ++i)
00780     res[i] = r[i];
00781 }
00782 
00784 void Frame::getInverseCoordinatesOf(const float src[3], float res[3]) const
00785 {
00786   const Vec r = inverseCoordinatesOf(Vec(src));
00787   for (int i=0; i<3 ; ++i)
00788     res[i] = r[i];
00789 }
00790 
00792 void Frame::getLocalCoordinatesOf(const float src[3], float res[3]) const
00793 {
00794   const Vec r = localCoordinatesOf(Vec(src));
00795   for (int i=0; i<3 ; ++i)
00796     res[i] = r[i];
00797 }
00798 
00800 void Frame::getLocalInverseCoordinatesOf(const float src[3], float res[3]) const
00801 {
00802   const Vec r = localInverseCoordinatesOf(Vec(src));
00803   for (int i=0; i<3 ; ++i)
00804     res[i] = r[i];
00805 }
00806 
00808 void Frame::getCoordinatesOfIn(const float src[3], float res[3], const Frame* const in) const
00809 {
00810   const Vec r = coordinatesOfIn(Vec(src), in);
00811   for (int i=0; i<3 ; ++i)
00812     res[i] = r[i];
00813 }
00814 
00816 void Frame::getCoordinatesOfFrom(const float src[3], float res[3], const Frame* const from) const
00817 {
00818   const Vec r = coordinatesOfFrom(Vec(src), from);
00819   for (int i=0; i<3 ; ++i)
00820     res[i] = r[i];
00821 }
00822 
00823 
00825 
00834 Vec Frame::transformOf(const Vec& src) const
00835 {
00836   if (referenceFrame())
00837     return localTransformOf(referenceFrame()->transformOf(src));
00838   else
00839     return localTransformOf(src);
00840 }
00841 
00847 Vec Frame::inverseTransformOf(const Vec& src) const
00848 {
00849   const Frame* fr = this;
00850   Vec res = src;
00851   while (fr != NULL)
00852     {
00853       res = fr->localInverseTransformOf(res);
00854       fr  = fr->referenceFrame();
00855     }
00856   return res;
00857 }
00858 
00863 Vec Frame::localTransformOf(const Vec& src) const
00864 {
00865   return rotation().inverseRotate(src);
00866 }
00867 
00872 Vec Frame::localInverseTransformOf(const Vec& src) const
00873 {
00874   return rotation().rotate(src);
00875 }
00876 
00881 Vec Frame::transformOfFrom(const Vec& src, const Frame* const from) const
00882 {
00883   if (this == from)
00884     return src;
00885   else
00886     if (referenceFrame())
00887       return localTransformOf(referenceFrame()->transformOfFrom(src, from));
00888     else
00889       return localTransformOf(from->inverseTransformOf(src));
00890 }
00891 
00896 Vec Frame::transformOfIn(const Vec& src, const Frame* const in) const
00897 {
00898   const Frame* fr = this;
00899   Vec    res = src;
00900   while ((fr != NULL) && (fr != in))
00901     {
00902       res = fr->localInverseTransformOf(res);
00903       fr  = fr->referenceFrame();
00904     }
00905 
00906   if (fr != in)
00907     // in was not found in the branch of this, res is now expressed in the world
00908     // coordinate system. Simply convert to in coordinate system.
00909     res = in->transformOf(res);
00910 
00911   return res;
00912 }
00913 
00915 
00917 void Frame::getTransformOf(const float src[3], float res[3]) const
00918 {
00919   Vec r = transformOf(Vec(src));
00920   for (int i=0; i<3 ; ++i)
00921     res[i] = r[i];
00922 }
00923 
00925 void Frame::getInverseTransformOf(const float src[3], float res[3]) const
00926 {
00927   Vec r = inverseTransformOf(Vec(src));
00928   for (int i=0; i<3 ; ++i)
00929     res[i] = r[i];
00930 }
00931 
00933 void Frame::getLocalTransformOf(const float src[3], float res[3]) const
00934 {
00935   Vec r = localTransformOf(Vec(src));
00936   for (int i=0; i<3 ; ++i)
00937     res[i] = r[i];
00938 }
00939 
00941 void Frame::getLocalInverseTransformOf(const float src[3], float res[3]) const
00942 {
00943   Vec r = localInverseTransformOf(Vec(src));
00944   for (int i=0; i<3 ; ++i)
00945     res[i] = r[i];
00946 }
00947 
00949 void Frame::getTransformOfIn(const float src[3], float res[3], const Frame* const in) const
00950 {
00951   Vec r = transformOfIn(Vec(src), in);
00952   for (int i=0; i<3 ; ++i)
00953     res[i] = r[i];
00954 }
00955 
00957 void Frame::getTransformOfFrom(const float src[3], float res[3], const Frame* const from) const
00958 {
00959   Vec r = transformOfFrom(Vec(src), from);
00960   for (int i=0; i<3 ; ++i)
00961     res[i] = r[i];
00962 }
00963 
00965 
00985 QDomElement Frame::domElement(const QString& name, QDomDocument& document) const
00986 {
00987   // TODO: use translation and rotation instead when referenceFrame is coded...
00988   QDomElement e = document.createElement(name);
00989   e.appendChild(position().domElement("position", document));
00990   e.appendChild(orientation().domElement("orientation", document));
00991   return e;
00992 }
00993 
01002 void Frame::initFromDOMElement(const QDomElement& element)
01003 {
01004   // TODO: use translation and rotation instead when referenceFrame is coded...
01005 
01006   // Reset default values. Attention: destroys constraint.
01007   // *this = Frame();
01008   // This instead ? Better : what is not set is not changed.
01009   // setPositionAndOrientation(Vec(), Quaternion());
01010 
01011   QDomElement child=element.firstChild().toElement();
01012   while (!child.isNull())
01013     {
01014       if (child.tagName() == "position")
01015         setPosition(Vec(child));
01016       if (child.tagName() == "orientation")
01017         setOrientation(Quaternion(child).normalized());
01018 
01019       child = child.nextSibling().toElement();
01020     }
01021 }
01022 
01024 
01045 void Frame::alignWithFrame(const Frame* const frame, bool move, float threshold)
01046 {
01047   Vec directions[2][3];
01048   for (int d=0; d<3; ++d)
01049     {
01050       Vec dir((d==0)? 1.0 : 0.0, (d==1)? 1.0 : 0.0, (d==2)? 1.0 : 0.0);
01051       if (frame)
01052         directions[0][d] = frame->inverseTransformOf(dir);
01053       else
01054         directions[0][d] = dir;
01055       directions[1][d] = inverseTransformOf(dir);
01056     }
01057 
01058   float maxProj = 0.0f;
01059   float proj;
01060   unsigned short index[2];
01061   index[0] = index[1] = 0;
01062   for (int i=0; i<3; ++i)
01063     for (int j=0; j<3; ++j)
01064       if ( (proj=fabs(directions[0][i]*directions[1][j])) >= maxProj )
01065         {
01066           index[0] = i;
01067           index[1] = j;
01068           maxProj  = proj;
01069         }
01070 
01071   Frame old;
01072   old=*this;
01073 
01074   float coef = directions[0][index[0]] * directions[1][index[1]];
01075   if (fabs(coef) >= threshold)
01076     {
01077       const Vec axis = cross(directions[0][index[0]], directions[1][index[1]]);
01078       float angle = asin(axis.norm());
01079       if (coef >= 0.0)
01080         angle = -angle;
01081       // setOrientation(Quaternion(axis, angle) * orientation());
01082       rotate(rotation().inverse() * Quaternion(axis, angle) * orientation());
01083 
01084       // Try to align an other axis direction
01085       unsigned short d = (index[1]+1) % 3;
01086       Vec dir((d==0)? 1.0 : 0.0, (d==1)? 1.0 : 0.0, (d==2)? 1.0 : 0.0);
01087       dir = inverseTransformOf(dir);
01088 
01089       float max = 0.0f;
01090       for (int i=0; i<3; ++i)
01091         {
01092           float proj = fabs(directions[0][i]*dir);
01093           if (proj > max)
01094             {
01095               index[0] = i;
01096               max = proj;
01097             }
01098         }
01099 
01100       if (max >= threshold)
01101         {
01102           const Vec axis = cross(directions[0][index[0]], dir);
01103           float angle = asin(axis.norm());
01104           if (directions[0][index[0]] * dir >= 0.0)
01105             angle = -angle;
01106           // setOrientation(Quaternion(axis, angle) * orientation());
01107           rotate(rotation().inverse() * Quaternion(axis, angle) * orientation());
01108         }
01109     }
01110 
01111   if (move)
01112     {
01113       Vec center;
01114       if (frame)
01115         center = frame->position();
01116 
01117       // setPosition(center - orientation().rotate(old.coordinatesOf(center)));
01118       translate(center - orientation().rotate(old.coordinatesOf(center)) - translation());
01119     }
01120 }
01121 
01126 void Frame::projectOnLine(const Vec& origin, const Vec& direction)
01127 {
01128   // If you are trying to find a bug here, because of memory problems, you waste your time.
01129   // This is a bug in the gcc 3.3 compiler. Compile the library in debug mode and test.
01130   // Uncommenting this line also seems to solve the problem. Horrible.
01131   // cout << "position = " << position() << endl;
01132   // If you found a problem or are using a different compiler, please let me know.
01133   const Vec shift = origin - position();
01134   Vec proj = shift;
01135   proj.projectOnAxis(direction);
01136   translate(shift-proj);
01137 }


octovis
Author(s): Kai M. Wurm , Armin Hornung
autogenerated on Thu Jun 6 2019 17:31:58