OpenSceneGraph/src/osg/Quat.cpp

266 lines
7.4 KiB
C++
Raw Normal View History

/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* 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
* OpenSceneGraph Public License for more details.
*/
#include <stdio.h>
#include <osg/Quat>
#include <osg/Vec4>
#include <osg/Vec3>
#include <osg/Matrixf>
#include <osg/Matrixd>
2001-01-11 00:32:10 +08:00
#include <math.h>
/// Good introductions to Quaternions at:
/// http://www.gamasutra.com/features/programming/19980703/quaternions_01.htm
/// http://mathworld.wolfram.com/Quaternion.html
using namespace osg;
void Quat::set(const Matrixf& matrix)
{
matrix.get(*this);
}
void Quat::set(const Matrixd& matrix)
{
matrix.get(*this);
}
void Quat::get(Matrixf& matrix) const
{
matrix.set(*this);
}
void Quat::get(Matrixd& matrix) const
{
matrix.set(*this);
}
2001-01-11 00:32:10 +08:00
/// Set the elements of the Quat to represent a rotation of angle
/// (radians) around the axis (x,y,z)
void Quat::makeRotate( float angle,
float x,
float y,
float z )
2001-01-11 00:32:10 +08:00
{
float inversenorm = 1.0/sqrt( x*x + y*y + z*z );
float coshalfangle = cos( 0.5*angle );
float sinhalfangle = sin( 0.5*angle );
2001-01-11 00:32:10 +08:00
_fv[0] = x * sinhalfangle * inversenorm;
_fv[1] = y * sinhalfangle * inversenorm;
_fv[2] = z * sinhalfangle * inversenorm;
_fv[3] = coshalfangle;
}
void Quat::makeRotate( float angle, const Vec3& vec )
2001-01-11 00:32:10 +08:00
{
makeRotate( angle, vec[0], vec[1], vec[2] );
2001-01-11 00:32:10 +08:00
}
void Quat::makeRotate ( float angle1, const Vec3& axis1,
float angle2, const Vec3& axis2,
float angle3, const Vec3& axis3)
{
Quat q1; q1.makeRotate(angle1,axis1);
Quat q2; q2.makeRotate(angle2,axis2);
Quat q3; q3.makeRotate(angle3,axis3);
*this = q1*q2*q3;
}
2001-01-11 00:32:10 +08:00
// Make a rotation Quat which will rotate vec1 to vec2
// Generally take adot product to get the angle between these
// and then use a cross product to get the rotation axis
// Watch out for the two special cases of when the vectors
// are co-incident or opposite in direction.
void Quat::makeRotate( const Vec3& from, const Vec3& to )
2001-01-11 00:32:10 +08:00
{
const float epsilon = 0.00001f;
float length1 = from.length();
float length2 = to.length();
// dot product vec1*vec2
float cosangle = from*to/(length1*length2);
2001-01-11 00:32:10 +08:00
if ( fabs(cosangle - 1) < epsilon )
{
// cosangle is close to 1, so the vectors are close to being coincident
// Need to generate an angle of zero with any vector we like
// We'll choose (1,0,0)
makeRotate( 0.0, 1.0, 0.0, 0.0 );
2001-01-11 00:32:10 +08:00
}
else
if ( fabs(cosangle + 1.0) < epsilon )
2001-01-11 00:32:10 +08:00
{
// vectors are close to being opposite, so will need to find a
// vector orthongonal to from to rotate about.
osg::Vec3 tmp;
if (fabs(from.x())<fabs(from.y()))
if (fabs(from.x())<fabs(from.z())) tmp.set(1.0,0.0,0.0); // use x axis.
else tmp.set(0.0,0.0,1.0);
else if (fabs(from.y())<fabs(from.z())) tmp.set(0.0,1.0,0.0);
else tmp.set(0.0,0.0,1.0);
// find orthogonal axis.
Vec3 axis(from^tmp);
axis.normalize();
_fv[0] = axis[0]; // sin of half angle of PI is 1.0.
_fv[1] = axis[1]; // sin of half angle of PI is 1.0.
_fv[2] = axis[2]; // sin of half angle of PI is 1.0.
_fv[3] = 0; // cos of half angle of PI is zero.
2001-01-11 00:32:10 +08:00
}
else
{
// This is the usual situation - take a cross-product of vec1 and vec2
// and that is the axis around which to rotate.
Vec3 axis(from^to);
float angle = acos( cosangle );
makeRotate( angle, axis );
2001-01-11 00:32:10 +08:00
}
}
void Quat::getRotate( float& angle, Vec3& vec ) const
2001-01-11 00:32:10 +08:00
{
getRotate(angle,vec[0],vec[1],vec[2]);
2001-01-11 00:32:10 +08:00
}
// Get the angle of rotation and axis of this Quat object.
// Won't give very meaningful results if the Quat is not associated
// with a rotation!
void Quat::getRotate( float& angle, float& x, float& y, float& z ) const
2001-01-11 00:32:10 +08:00
{
float sinhalfangle = sqrt( _fv[0]*_fv[0] + _fv[1]*_fv[1] + _fv[2]*_fv[2] );
angle = 2 * atan2( sinhalfangle, _fv[3] );
if(sinhalfangle)
{
x = _fv[0] / sinhalfangle;
y = _fv[1] / sinhalfangle;
z = _fv[2] / sinhalfangle;
}
else
{
x = 0.0f;
y = 0.0f;
z = 1.0f;
}
2001-01-11 00:32:10 +08:00
}
/// Spherical Linear Interpolation
/// As t goes from 0 to 1, the Quat object goes from "from" to "to"
/// Reference: Shoemake at SIGGRAPH 89
/// See also
/// http://www.gamasutra.com/features/programming/19980703/quaternions_01.htm
void Quat::slerp( float t, const Quat& from, const Quat& to )
2001-01-11 00:32:10 +08:00
{
const double epsilon = 0.00001;
double omega, cosomega, sinomega, scale_from, scale_to ;
osg::Quat quatTo(to);
// this is a dot product
cosomega = from.asVec4() * to.asVec4();
if ( cosomega <0.0 )
{
cosomega = -cosomega;
quatTo.set(-to._fv);
}
2001-01-11 00:32:10 +08:00
if( (1.0 - cosomega) > epsilon )
{
omega= acos(cosomega) ; // 0 <= omega <= Pi (see man acos)
sinomega = sin(omega) ; // this sinomega should always be +ve so
// could try sinomega=sqrt(1-cosomega*cosomega) to avoid a sin()?
scale_from = sin((1.0-t)*omega)/sinomega ;
scale_to = sin(t*omega)/sinomega ;
2001-01-11 00:32:10 +08:00
}
else
{
/* --------------------------------------------------
The ends of the vectors are very close
we can use simple linear interpolation - no need
to worry about the "spherical" interpolation
-------------------------------------------------- */
scale_from = 1.0 - t ;
scale_to = t ;
2001-01-11 00:32:10 +08:00
}
// use Vec4 arithmetic
_fv = (from._fv*scale_from) + (quatTo._fv*scale_to);
// so that we get a Vec4
2001-01-11 00:32:10 +08:00
}
#define QX _fv[0]
#define QY _fv[1]
#define QZ _fv[2]
#define QW _fv[3]
#ifdef OSG_USE_UNIT_TESTS
void test_Quat_Eueler(float heading,float pitch,float roll)
{
osg::Quat q;
q.makeRotate(heading,pitch,roll);
osg::Matrix q_m;
q.get(q_m);
osg::Vec3 xAxis(1,0,0);
osg::Vec3 yAxis(0,1,0);
osg::Vec3 zAxis(0,0,1);
cout << "heading = "<<heading<<" pitch = "<<pitch<<" roll = "<<roll<<endl;
cout <<"q_m = "<<q_m;
cout <<"xAxis*q_m = "<<xAxis*q_m << endl;
cout <<"yAxis*q_m = "<<yAxis*q_m << endl;
cout <<"zAxis*q_m = "<<zAxis*q_m << endl;
osg::Matrix r_m = osg::Matrix::rotate(roll,0.0,1.0,0.0)*
osg::Matrix::rotate(pitch,1.0,0.0,0.0)*
osg::Matrix::rotate(-heading,0.0,0.0,1.0);
cout << "r_m = "<<r_m;
cout <<"xAxis*r_m = "<<xAxis*r_m << endl;
cout <<"yAxis*r_m = "<<yAxis*r_m << endl;
cout <<"zAxis*r_m = "<<zAxis*r_m << endl;
cout << endl<<"*****************************************" << endl<< endl;
}
void test_Quat()
{
test_Quat_Eueler(osg::DegreesToRadians(20),0,0);
test_Quat_Eueler(0,osg::DegreesToRadians(20),0);
test_Quat_Eueler(0,0,osg::DegreesToRadians(20));
test_Quat_Eueler(osg::DegreesToRadians(20),osg::DegreesToRadians(20),osg::DegreesToRadians(20));
return 0;
}
#endif