198 lines
4.6 KiB
C++
198 lines
4.6 KiB
C++
#ifndef QUATERNION_H
|
|
#define QUATERNION_H
|
|
|
|
#include "Matrix3.h"
|
|
|
|
class Quaternion {
|
|
|
|
public:
|
|
|
|
float w;
|
|
float x, y, z;
|
|
|
|
/** empty ctor */
|
|
Quaternion() : w(1.0f), x(0.0f), y(0.0f), z(0.0f) {
|
|
;
|
|
}
|
|
|
|
/** valued ctor */
|
|
Quaternion(float w, float x, float y, float z) : w(w), x(x), y(y), z(z) {
|
|
;
|
|
}
|
|
|
|
/** construct from Euler angles in radians */
|
|
static Quaternion fromEuler(float x, float y, float z) {
|
|
Quaternion q;
|
|
q.setEuler(x,y,z);
|
|
return q;
|
|
}
|
|
|
|
static Quaternion fromAxisAngle(float angle, float x, float y, float z) {
|
|
const float qx = x * std::sin(angle/2);
|
|
const float qy = y * std::sin(angle/2);
|
|
const float qz = z * std::sin(angle/2);
|
|
const float qw = std::cos(angle/2);
|
|
return Quaternion(qw, qx,qy,qz);
|
|
}
|
|
|
|
|
|
void normalize() {
|
|
*this *= 1.0 / std::sqrt( x*x + y*y + z*z + w*w );
|
|
}
|
|
|
|
inline float dotProduct(const Quaternion& q2) const {
|
|
return (x * q2.x) + (y * q2.y) + (z * q2.z) + (w * q2.w);
|
|
}
|
|
|
|
|
|
/** linear interpolation between q1 and q2 */
|
|
static Quaternion lerp( Quaternion q1, Quaternion q2, float time) {
|
|
const float scale = 1.0f - time;
|
|
return (q1*scale) + (q2*time);
|
|
}
|
|
|
|
/** spherical interpolation between q1 and q2 */
|
|
static Quaternion slerp( Quaternion q1, Quaternion q2, float time, float threshold = 0.05) {
|
|
|
|
float angle = q1.dotProduct(q2);
|
|
|
|
// make sure we use the short rotation
|
|
if (angle < 0.0f) {
|
|
q1 *= -1.0f;
|
|
angle *= -1.0f;
|
|
}
|
|
|
|
if (angle <= (1-threshold)) {
|
|
// spherical interpolation
|
|
const float theta = acosf(angle);
|
|
const float invsintheta = 1.0/(sinf(theta));
|
|
const float scale = sinf(theta * (1.0f-time)) * invsintheta;
|
|
const float invscale = sinf(theta * time) * invsintheta;
|
|
return (q1*scale) + (q2*invscale);
|
|
} else {
|
|
// linear interpolation
|
|
return Quaternion::lerp(q1,q2,time);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/** multiply by scalar */
|
|
Quaternion& operator *= (float val) {
|
|
x *= val;
|
|
y *= val;
|
|
z *= val;
|
|
w *= val;
|
|
return *this;
|
|
}
|
|
|
|
/** multiply by scalar */
|
|
Quaternion operator * (float val) const {
|
|
return Quaternion(w*val, x*val, y*val, z*val);
|
|
}
|
|
|
|
/** multiply by other quaterion */
|
|
Quaternion& operator *= (const Quaternion& other) {
|
|
return (*this = other * (*this));
|
|
}
|
|
|
|
/** multiply by other quaterion */
|
|
Quaternion operator * (const Quaternion& other) const {
|
|
Quaternion tmp;
|
|
tmp.w = (other.w * w) - (other.x * x) - (other.y * y) - (other.z * z);
|
|
tmp.x = (other.w * x) + (other.x * w) + (other.y * z) - (other.z * y);
|
|
tmp.y = (other.w * y) + (other.y * w) + (other.z * x) - (other.x * z);
|
|
tmp.z = (other.w * z) + (other.z * w) + (other.x * y) - (other.y * x);
|
|
return tmp;
|
|
}
|
|
|
|
/** add two quaternions */
|
|
Quaternion operator + (const Quaternion& b) const {
|
|
return Quaternion(w+b.w, x+b.x, y+b.y, z+b.z);
|
|
}
|
|
|
|
|
|
/** set from euler angles (in radians) */
|
|
void setEuler(float x, float y, float z) {
|
|
|
|
double angle;
|
|
|
|
angle = x * 0.5;
|
|
const double sr = std::sin(angle);
|
|
const double cr = std::cos(angle);
|
|
|
|
angle = y * 0.5;
|
|
const double sp = std::sin(angle);
|
|
const double cp = std::cos(angle);
|
|
|
|
angle = z * 0.5;
|
|
const double sy = std::sin(angle);
|
|
const double cy = std::cos(angle);
|
|
|
|
const double cpcy = cp * cy;
|
|
const double spcy = sp * cy;
|
|
const double cpsy = cp * sy;
|
|
const double spsy = sp * sy;
|
|
|
|
this->x = (float)(sr * cpcy - cr * spsy);
|
|
this->y = (float)(cr * spcy + sr * cpsy);
|
|
this->z = (float)(cr * cpsy - sr * spcy);
|
|
this->w = (float)(cr * cpcy + sr * spsy);
|
|
normalize();
|
|
|
|
}
|
|
|
|
/** convert to euler angles */
|
|
Vector3 toEuler() const {
|
|
|
|
Vector3 res;
|
|
|
|
const double sqw = w*w;
|
|
const double sqx = x*x;
|
|
const double sqy = y*y;
|
|
const double sqz = z*z;
|
|
const double test = 2.0 * (y*w - x*z);
|
|
|
|
if (near(test, 1.0)) {
|
|
// heading = rotation about z-axis
|
|
res.z = (float) (-2.0*atan2(x, w));
|
|
// bank = rotation about x-axis
|
|
res.x = 0;
|
|
// attitude = rotation about y-axis
|
|
res.y = (float) (M_PI/2.0);
|
|
} else if (near(test, -1.0)) {
|
|
// heading = rotation about z-axis
|
|
res.z = (float) (2.0*std::atan2(x, w));
|
|
// bank = rotation about x-axis
|
|
res.x = 0;
|
|
// attitude = rotation about y-axis
|
|
res.y = (float) (M_PI/-2.0);
|
|
} else {
|
|
// heading = rotation about z-axis
|
|
res.z = (float) std::atan2(2.0 * (x*y + z*w),(sqx - sqy - sqz + sqw));
|
|
// bank = rotation about x-axis
|
|
res.x = (float) std::atan2(2.0 * (y*z + x*w),(-sqx - sqy + sqz + sqw));
|
|
// attitude = rotation about y-axis
|
|
res.y = (float) std::asin( clamp(test, -1.0, 1.0) );
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
static inline bool near(double d1, double d2) {
|
|
return std::abs(d1-d2) < 0.00001;
|
|
}
|
|
|
|
static inline double clamp(double val, double min, double max) {
|
|
if (val < min) {return min;}
|
|
if (val > max) {return max;}
|
|
return val;
|
|
}
|
|
|
|
};
|
|
|
|
#endif // QUATERNION_H
|