/**************************************************************************\ * * This file is part of the Coin 3D visualization library. * Copyright (C) 1998-2007 by Systems in Motion. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * ("GPL") version 2 as published by the Free Software Foundation. * See the file LICENSE.GPL at the root directory of this source * distribution for additional information about the GNU GPL. * * For using Coin with software that can not be combined with the GNU * GPL, and for taking advantage of the additional benefits of our * support services, please contact Systems in Motion about acquiring * a Coin Professional Edition License. * * See http://www.coin3d.org/ for more information. * * Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY. * http://www.sim.no/ sales@sim.no coin-support@coin3d.org * \**************************************************************************/ /*! \class SbDPRotation SbLinear.h Inventor/SbLinear.h \brief The SbDPRotation class represents a rotation in 3D space. \ingroup base SbDPRotation is used extensively throughout the Coin library. An SbDPRotation is stored internally as a quaternion for speed and storage reasons, but inquiries can be done to get and set axis and angle values for convenience. \sa SbDPMatrix */ // FIXME: we should _really_ have double-precision classes compatible // with those in TGS' API, for several good reasons. So either rename // this, make a typedef (if that is sufficient), or write a "wrapper // class" around this with inline functions, using with TGS' name for // it. 20020225 mortene. #include #include #include #include #include #if COIN_DEBUG #include #endif // COIN_DEBUG /*! The default constructor just initializes a valid rotation. The actual value is unspecified, and you should not depend on it. */ SbDPRotation::SbDPRotation(void) // This translates to zero rotation around the positive Z axis. : quat(0.0f, 0.0f, 0.0f, 1.0f) { } /*! Construct a new SbDPRotation object initialized with the given axis-of-rotation and rotation angle. */ SbDPRotation::SbDPRotation(const SbVec3d & axis, const double radians) { #if COIN_DEBUG if (axis.length()==0.0f) SoDebugError::postWarning("SbDPRotation::SbDPRotation", "axis parameter has zero length => " "undefined axis."); #endif // COIN_DEBUG this->setValue(axis, radians); } /*! Construct a new SbDPRotation object initialized with the given quaternion components. The array must be ordered as follows: q[0] = x, q[1] = y, q[2] = z and q[3] = w, where the quaternion is specified by q=w+xi+yj+zk. */ SbDPRotation::SbDPRotation(const double q[4]) { this->setValue(q); } /*! Construct a new SbDPRotation object initialized with the given quaternion components. */ SbDPRotation::SbDPRotation(const double q0, const double q1, const double q2, const double q3) { this->setValue(q0, q1, q2, q3); } /*! Construct a new SbDPRotation object initialized with the given rotation matrix. */ SbDPRotation::SbDPRotation(const SbDPMatrix & m) { this->setValue(m); } /*! Construct a rotation which is the minimum rotation necessary to make vector \a rotateFrom point in the direction of vector \a rotateTo. */ SbDPRotation::SbDPRotation(const SbVec3d & rotateFrom, const SbVec3d & rotateTo) { // Parameters are checked in setValue(). this->setValue(rotateFrom, rotateTo); } /*! Return pointer to an array with the rotation expressed as four quaternion values. \sa setValue(). */ const double * SbDPRotation::getValue(void) const { return &this->quat[0]; } /*! Return the four quaternion components representing the rotation. \sa setValue(). */ void SbDPRotation::getValue(double & q0, double & q1, double & q2, double & q3) const { q0 = this->quat[0]; q1 = this->quat[1]; q2 = this->quat[2]; q3 = this->quat[3]; } /*! Set the rotation. \sa getValue(). */ SbDPRotation & SbDPRotation::setValue(const double q0, const double q1, const double q2, const double q3) { this->quat[0] = q0; this->quat[1] = q1; this->quat[2] = q2; this->quat[3] = q3; if (this->quat.normalize() == 0.0f) { #if COIN_DEBUG SoDebugError::postWarning("SbRotation::setValue", "Quarternion has zero length => " "undefined rotation."); #endif // COIN_DEBUG } return *this; } /*! Return the rotation in the form of an axis-of-rotation and a rotation angle. \sa setValue(). */ void SbDPRotation::getValue(SbVec3d & axis, double & radians) const { if((this->quat[3] >= -1.0f) && (this->quat[3] <= 1.0f)) { radians = double(acos(this->quat[3])) * 2.0f; double scale = (double)sin(radians / 2.0f); if(scale != 0.0f) { axis[0] = this->quat[0] / scale; axis[1] = this->quat[1] / scale; axis[2] = this->quat[2] / scale; // FIXME: why not just flip the sign on each component according // to "scale" and normalize the axis instead? 20010111 mortene. return; } } // Quaternion can't be converted to axis and rotation angle, so we just // set up values to indicate this. axis.setValue(0.0f, 0.0f, 1.0f); radians = 0.0f; } /*! Return this rotation in the form of a matrix. \sa setValue(). */ void SbDPRotation::getValue(SbDPMatrix & matrix) const { const double x = this->quat[0]; const double y = this->quat[1]; const double z = this->quat[2]; const double w = this->quat[3]; matrix[0][0] = w*w + x*x - y*y - z*z; matrix[0][1] = 2*x*y + 2*w*z; matrix[0][2] = 2*x*z - 2*w*y; matrix[0][3] = 0.0f; matrix[1][0] = 2*x*y-2*w*z; matrix[1][1] = w*w - x*x + y*y - z*z; matrix[1][2] = 2*y*z + 2*w*x; matrix[1][3] = 0.0f; matrix[2][0] = 2*x*z + 2*w*y; matrix[2][1] = 2*y*z - 2*w*x; matrix[2][2] = w*w - x*x - y*y + z*z; matrix[2][3] = 0.0f; matrix[3][0] = 0.0f; matrix[3][1] = 0.0f; matrix[3][2] = 0.0f; matrix[3][3] = w*w + x*x + y*y + z*z; } /*! Invert the rotation. Returns reference to self. \sa inverse() */ SbDPRotation & SbDPRotation::invert(void) { double length = this->quat.length(); #if COIN_DEBUG if (length==0.0f) SoDebugError::postWarning("SbDPRotation::invert", "Quarternion has zero length => " "undefined rotation."); #endif // COIN_DEBUG // Optimize by doing 1 div and 4 muls instead of 4 divs. double inv = 1.0f / length; this->quat[0] = -this->quat[0] * inv; this->quat[1] = -this->quat[1] * inv; this->quat[2] = -this->quat[2] * inv; this->quat[3] = this->quat[3] * inv; return *this; } /*! Non-destructively inverses the rotation and returns the result. \sa invert() */ SbDPRotation SbDPRotation::inverse(void) const { double length = this->quat.length(); #if COIN_DEBUG if (length==0.0f) SoDebugError::postWarning("SbDPRotation::inverse", "Quaternion has zero length => " "undefined rotation."); #endif // COIN_DEBUG // Optimize by doing 1 div and 4 muls instead of 4 divs. double inv = 1.0f / length; SbDPRotation rot; rot.quat[0] = -this->quat[0] * inv; rot.quat[1] = -this->quat[1] * inv; rot.quat[2] = -this->quat[2] * inv; rot.quat[3] = this->quat[3] * inv; return rot; } /*! Reset the rotation by the four quaternions in the array. \sa getValue(). */ SbDPRotation& SbDPRotation::setValue(const double q[4]) { this->quat[0] = q[0]; this->quat[1] = q[1]; this->quat[2] = q[2]; this->quat[3] = q[3]; if (this->quat.normalize() == 0.0f) { #if COIN_DEBUG SoDebugError::postWarning("SbRotation::setValue", "Quarternion has zero length => " "undefined rotation."); #endif // COIN_DEBUG } return *this; } /*! Set the rotation from the components of the given matrix. Returns reference to self. \sa getValue(). */ SbDPRotation & SbDPRotation::setValue(const SbDPMatrix & m) { double scalerow = m[0][0] + m[1][1] + m[2][2]; if (scalerow > 0.0f) { double s = (double)sqrt(scalerow + m[3][3]); this->quat[3] = s * 0.5f; s = 0.5f / s; this->quat[0] = (m[1][2] - m[2][1]) * s; this->quat[1] = (m[2][0] - m[0][2]) * s; this->quat[2] = (m[0][1] - m[1][0]) * s; } else { int i = 0; if (m[1][1] > m[0][0]) i = 1; if (m[2][2] > m[i][i]) i = 2; int j = (i+1)%3; int k = (j+1)%3; double s = (double)sqrt((m[i][i] - (m[j][j] + m[k][k])) + m[3][3]); this->quat[i] = s * 0.5f; s = 0.5f / s; this->quat[3] = (m[j][k] - m[k][j]) * s; this->quat[j] = (m[i][j] + m[j][i]) * s; this->quat[k] = (m[i][k] + m[k][i]) * s; } if (m[3][3] != 1.0f) this->operator*=(1.0f/(double)(sqrt(m[3][3]))); return *this; } /*! Reset rotation with the given axis-of-rotation and rotation angle. Returns reference to self. Make sure \a axis is not the null vector when calling this method. \sa getValue(). */ SbDPRotation & SbDPRotation::setValue(const SbVec3d & axis, const double radians) { #if COIN_DEBUG if (axis.length()==0.0f) SoDebugError::postWarning("SbDPRotation::setValue", "axis parameter has zero length."); #endif // COIN_DEBUG // From . this->quat[3] = (double)cos(radians/2); const double sineval = (double)sin(radians/2); SbVec3d a = axis; // we test for a null vector above (void) a.normalize(); this->quat[0] = a[0] * sineval; this->quat[1] = a[1] * sineval; this->quat[2] = a[2] * sineval; return *this; } /*! Construct a rotation which is the minimum rotation necessary to make vector \a rotateFrom point in the direction of vector \a rotateTo. Returns reference to self. \sa getValue(). */ SbDPRotation & SbDPRotation::setValue(const SbVec3d & rotateFrom, const SbVec3d & rotateTo) { #if COIN_DEBUG // Check if the vectors are valid. if (rotateFrom.length()==0.0f) { SoDebugError::postWarning("SbDPRotation::setValue", "rotateFrom argument has zero length."); } if (rotateTo.length()==0.0f) { SoDebugError::postWarning("SbDPRotation::setValue", "rotateTo argument has zero length."); } #endif // COIN_DEBUG SbVec3d from(rotateFrom); // we test for a null vector above (void) from.normalize(); SbVec3d to(rotateTo); // we test for a null vector above (void) to.normalize(); const double dot = from.dot(to); SbVec3d crossvec = from.cross(to); const double crosslen = crossvec.normalize(); if (crosslen == 0.0f) { // Parallel vectors // Check if they are pointing in the same direction. if (dot > 0.0) { this->setValue(0.0, 0.0, 0.0, 1.0); } // Ok, so they are parallel and pointing in the opposite direction // of each other. else { // Try crossing with x axis. SbVec3d t = from.cross(SbVec3d(1.0f, 0.0f, 0.0f)); // If not ok, cross with y axis. if (t.normalize() == 0.0) { t = from.cross(SbVec3d(0.0f, 1.0f, 0.0f)); (void) t.normalize(); } this->setValue(t[0], t[1], t[2], 0.0f); } } else { // Vectors are not parallel // The fabs() wrapping is to avoid problems when `dot' "overflows" // a tiny wee bit, which can lead to sqrt() returning NaN. crossvec *= (double)sqrt(0.5f * fabs(1.0f - dot)); // The fabs() wrapping is to avoid problems when `dot' "underflows" // a tiny wee bit, which can lead to sqrt() returning NaN. this->setValue(crossvec[0], crossvec[1], crossvec[2], (double)sqrt(0.5 * fabs(1.0 + dot))); } return *this; } /*! Multiplies the quaternions. Note that order is important when combining quaternions with the multiplication operator. */ SbDPRotation & SbDPRotation::operator*=(const SbDPRotation & q) { // Formula from double tx, ty, tz, tw; this->getValue(tx, ty, tz, tw); double qx, qy, qz, qw; q.getValue(qx, qy, qz, qw); this->setValue(qw*tx + qx*tw + qy*tz - qz*ty, qw*ty - qx*tz + qy*tw + qz*tx, qw*tz + qx*ty - qy*tx + qz*tw, qw*tw - qx*tx - qy*ty - qz*tz); return *this; } /*! Multiplies components of quaternion with scalar value \a s. Returns reference to self. */ SbDPRotation & SbDPRotation::operator*=(const double s) { this->quat *= s; return *this; } /*! \relates SbDPRotation Check if the two rotations are equal. \sa equals(). */ int operator==(const SbDPRotation & q1, const SbDPRotation & q2) { return (q1.quat == q2.quat); } /*! \relates SbDPRotation Check if the two rotations are unequal. \sa equals(). */ int operator!=(const SbDPRotation & q1, const SbDPRotation & q2) { return !(q1 == q2); } /*! Check the internal quaternion representation vectors for equality within the given tolerance. */ SbBool SbDPRotation::equals(const SbDPRotation & r, const double tolerance) const { return this->quat.equals(r.quat, tolerance); } /*! \relates SbDPRotation Multiplies the two rotations and returns the result. Note that order is important when combining quaternions with the multiplication operator. */ SbDPRotation operator*(const SbDPRotation & q1, const SbDPRotation & q2) { SbDPRotation q(q1); q *= q2; return q; } /*! Rotate the \a src vector and put the result in \a dst. */ void SbDPRotation::multVec(const SbVec3d & src, SbVec3d & dst) const { // FIXME: this looks amazingly ineffective. Should // optimize. 20010907 mortene. SbDPMatrix mat; mat.setRotate(*this); mat.multVecMatrix(src, dst); } /*! Scale the angle of rotation by \a scaleFactor. */ void SbDPRotation::scaleAngle(const double scaleFactor) { SbVec3d axis; double rad; this->getValue(axis, rad); this->setValue(axis, rad * scaleFactor); } /*! \relates SbDPRotation Interpolates along the shortest path between the two rotation positions (from \a rot0 to \a rot1). Returns the SbDPRotation which will rotate \a rot0 the given part \a t of the spherical distance towards \a rot1, where \a t=0 will yield \a rot0 and \a t=1 will yield \a rot1. \a t should be in the interval [0, 1]. */ SbDPRotation SbDPRotation::slerp(const SbDPRotation & rot0, const SbDPRotation & rot1, double t) { #if COIN_DEBUG if (t<0.0f || t>1.0f) { SoDebugError::postWarning("SbDPRotation::slerp", "The t parameter (%f) is out of bounds [0,1]. " "Clamping to bounds.", t); if (t<0.0f) t=0.0f; else if (t>1.0f) t=1.0f; } #endif // COIN_DEBUG SbDPRotation from = rot0; SbDPRotation to = rot1; double dot = from.quat.dot(to.quat); // Find the correct direction of the interpolation. if(dot < 0.0f) { dot = -dot; to.quat.negate(); } // fallback to linear interpolation, in case we run out of floating // point precision double scale0 = 1.0 - t; double scale1 = t; if ((1.0f - dot) > FLT_EPSILON) { double angle = (double)acos(dot); double sinangle = (double)sin(angle); if (sinangle > FLT_EPSILON) { // calculate spherical interpolation scale0 = double(sin((1.0 - t) * angle)) / sinangle; scale1 = double(sin(t * angle)) / sinangle; } } SbVec4d vec = (scale0 * from.quat) + (scale1 * to.quat); return SbDPRotation(vec[0], vec[1], vec[2], vec[3]); } /*! Returns an identity rotation. */ SbDPRotation SbDPRotation::identity(void) { return SbDPRotation(0.0f, 0.0f, 0.0f, 1.0f); } /*! Dump the state of this object to the \a file stream. Only works in debug version of library, method does nothing in an optimized compile. */ void SbDPRotation::print(FILE * fp) const { #if COIN_DEBUG this->quat.print(fp); #endif // COIN_DEBUG }