提要

四元素是游戏开发中经常使用的用于处理旋转的数学工具,以下就用C++来实现一个四元素类。參考Unity中四元素的接口。

假设没有看之前的 彻底搞懂四元数。 建议先看一下。

代码清单

Quaternion.h

#pragma once
#include "Vector3.h"
#include "Mathf.h"
class Quaternion
{
public:
Quaternion(float x, float y, float z, float w);
Quaternion(float yaw, float pitch, float roll);
~Quaternion();
static Quaternion identity;
static float Dot(const Quaternion &lhs, const Quaternion &rhs);
static Quaternion Lerp(const Quaternion &a, const Quaternion &b, float t);
static Quaternion Slerp(const Quaternion &a, const Quaternion &b, float t);
static float Angle(const Quaternion &lhs, const Quaternion &rhs);
void SetEulerAngle(float yaw, float pitch, float roll);
void Set(float _x, float _y, float _z, float _w); Quaternion Conjugate() const;
Quaternion Inverse() const;
Vector3 EulerAngle() const; void operator+(const Quaternion &q);
void operator*(float s);
void operator-(const Quaternion &q);
friend Quaternion operator * (const Quaternion& lhs, const Quaternion& rhs);
friend Vector3 operator *(const Quaternion& rotation, const Vector3& point); float x;
float y;
float z;
float w; private: Vector3 eulerAngles;
};

Quaternion.cpp

#include "Quaternion.h"

Quaternion Quaternion::identity(0, 0, 0, 1);

Quaternion::Quaternion(float _x, float _y, float _z, float _w)
{
float mag = _x *_x + _y*_y + _z *_z + _w*_w;
x = _x / mag;
y = _y / mag;
z = _z / mag;
w = _w / mag;
} Quaternion::Quaternion(float yaw, float pitch, float roll)
{
this->SetEulerAngle(yaw, pitch, roll);
} Quaternion::~Quaternion()
{ } //Cos theta of two quaternion
float Quaternion::Dot(const Quaternion &lhs, const Quaternion &rhs)
{
return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z + lhs.w * rhs.w;
} Quaternion Quaternion::Slerp(const Quaternion &a, const Quaternion &b, float t)
{
float cos_theta = Dot(a, b); // if B is on opposite hemisphere from A, use -B instead
float sign;
if (cos_theta < 0.f)
{
cos_theta = -cos_theta;
sign = -1.f;
}
else sign = 1.f; float c1, c2;
if (cos_theta > 1.f - Mathf::EPSILON)
{
// if q2 is (within precision limits) the same as q1,
// just linear interpolate between A and B. c2 = t;
c1 = 1.f - t;
}
else
{
//float theta = gFloat::ArcCosTable(cos_theta);
// faster than table-based :
//const float theta = myacos(cos_theta);
float theta = acos(cos_theta);
float sin_theta = sin(theta);
float t_theta = t*theta;
float inv_sin_theta = 1.f / sin_theta;
c2 = sin(t_theta) * inv_sin_theta;
c1 = sin(theta - t_theta) * inv_sin_theta;
} c2 *= sign; // or c1 *= sign
// just affects the overrall sign of the output // interpolate
return Quaternion(a.x * c1 + b.x * c2, a.y * c1 + b.y * c2, a.z * c1 + b.z * c2, a.w * c1 + b.w * c2);
} Quaternion Quaternion::Lerp(const Quaternion &a, const Quaternion &b, float t)
{
return Quaternion((1 - t) * a.x + t * b.x,
(1 - t) * a.y + t * b.y,
(1 - t) * a.z + t * b.z,
(1 - t) * a.w + t * b.w);
} float Quaternion::Angle(const Quaternion &lhs, const Quaternion &rhs)
{
float cos_theta = Dot(lhs, rhs); // if B is on opposite hemisphere from A, use -B instead
if (cos_theta < 0.f)
{
cos_theta = -cos_theta;
}
float theta = acos(cos_theta);
return 2 * Mathf::Rad2Deg * theta;
} void Quaternion::Set(float _x, float _y, float _z, float _w)
{
x = _x;
y = _y;
z = _z;
w = _w;
} void Quaternion::SetEulerAngle(float yaw, float pitch, float roll)
{
float angle;
float sinRoll, sinPitch, sinYaw, cosRoll, cosPitch, cosYaw; angle = yaw * 0.5f;
sinYaw = sin(angle);
cosYaw = cos(angle); angle = pitch * 0.5f;
sinPitch = sin(angle);
cosPitch = cos(angle); angle = roll * 0.5f;
sinRoll = sin(angle);
cosRoll = cos(angle); float _y = cosRoll*sinPitch*cosYaw + sinRoll*cosPitch*sinYaw;
float _x = cosRoll*cosPitch*sinYaw - sinRoll*sinPitch*cosYaw;
float _z = sinRoll*cosPitch*cosYaw - cosRoll*sinPitch*sinYaw;
float _w = cosRoll*cosPitch*cosYaw + sinRoll*sinPitch*sinYaw; float mag = _x *_x + _y*_y + _z *_z + _w*_w;
x = _x / mag;
y = _y / mag;
z = _z / mag;
w = _w / mag;
} void Quaternion::operator+(const Quaternion &q)
{
x += q.x;
y += q.y;
z += q.z;
w += q.w;
} void Quaternion::operator-(const Quaternion &q)
{
x -= q.x;
y -= q.y;
z -= q.z;
w -= q.w;
} void Quaternion::operator*(float s)
{
x *= s;
y *= s;
z *= s;
w *= s;
} Quaternion Quaternion::Conjugate() const
{
return Quaternion(-x, -y, -z, w);
} Quaternion Quaternion::Inverse() const
{
return Quaternion(-x, -y, -z, w);
} Quaternion operator * (const Quaternion& lhs, const Quaternion& rhs)
{
float w1 = lhs.w;
float w2 = rhs.w;
Vector3 v1(lhs.x, lhs.y, lhs.z);
Vector3 v2(rhs.x, rhs.y, rhs.z);
float w3 = w1 * w2 - Vector3::Dot(v1, v2);
Vector3 v3 = Vector3::Cross(v1, v2) + w1 * v2 + w2 * v1;
return Quaternion(v3.x, v3.y, v3.z, w3);
} Vector3 operator *(const Quaternion& q, const Vector3& v)
{ /*
Quaternion tmp(v.x, v.y, v.z, 0); //This will normalise the quaternion. this will case error.
Quaternion result = q * tmp * q.Conjugate();
return Vector3(result.x, result.y, result.z);*/ // Extract the vector part of the quaternion
Vector3 u(q.x, q.y, q.z); // Extract the scalar part of the quaternion
float s = q.w; // Do the math
return 2.0f * Vector3::Dot(u, v) * u
+ (s*s - Vector3::Dot(u, u)) * v
+ 2.0f * s * Vector3::Cross(u, v);
} Vector3 Quaternion::EulerAngle() const
{
float yaw = atan2(2 * (w * x + z * y), 1 - 2 * (x * x + y * y));
float pitch = asin(Mathf::Clamp(2 * (w * y - x * z), -1.0f, 1.0f));
float roll = atan2(2 * (w * z + x * y), 1 - 2 * (z * z + y * y));
return Vector3(Mathf::Rad2Deg * yaw, Mathf::Rad2Deg * pitch, Mathf::Rad2Deg * roll);
}

測试

void QuaternionTest()
{
qDebug() << Mathf::Rad2Deg * Mathf::Pi * 0.25f;// 45
qDebug() << Mathf::Deg2Rad * 45;// 0.785398163397 Quaternion a = Quaternion::identity;
Quaternion b(Mathf::Pi * 0.5, 0, 0); qDebug() << "a" << a << a.EulerAngle();
qDebug() << "b" << b << b.EulerAngle();
qDebug() << Quaternion::Angle(a, b);
Quaternion c = Quaternion::Slerp(a, b, 0.5f);
qDebug() <<"c" <<c << c.EulerAngle();
qDebug() << Quaternion::Angle(a, c);
Quaternion d = b * c;
qDebug() << "d" << d << d.EulerAngle();
Vector3 pos(1, 2, 3);
qDebug() << "c * (1, 2, 3) "<<c * pos;
}

执行结果

关于四元素和Vector3乘法的优化

四元素和向量相乘最easy用到的应该是骨骼动画。计算量是很大的,所以对四元素和向量相乘的算法能够设法优化一下。

对于向量 v 和 四元素 q。最原始的乘法是这种。

1) Create a pure quaternion p out of v. This simply means adding a fourth coordinate of 0:

2) Pre-multiply it with q and post-multiply it with the conjugate q*:

3) This will result in another pure quaternion which can be turned back to a vector:

This vector v' is v rotated by q.

一个优化的算法是这种

We can also describe q as the combination of a 3-dimensional vector u and a scalar s:

By the rules of quaternion multiplication, and as the conjugate of a unit length quaternion is simply it's inverse, we get:

this is very long to re-type

The scalar part (ellipses) results in zero, as detailed here. What's interesting is the vector part, AKA our rotated vector v'. It can be simplified using some basic vector identities:

that too

This is now much more optimal; two dot products, a cross product and a few extras: around half the operations.

代码能够写成这样

void rotate_vector_by_quaternion(const Vector3& v, const Quaternion& q, Vector3& vprime)
{
// Extract the vector part of the quaternion
Vector3 u(q.x, q.y, q.z); // Extract the scalar part of the quaternion
float s = q.w; // Do the math
vprime = 2.0f * dot(u, v) * u
+ (s*s - dot(u, u)) * v
+ 2.0f * s * cross(u, v);
}

这样写不仅很清晰。并且有个哥们用sse优化之后,居然有35% faster。

參考

Understanding Quaternions - http://blog.csdn.net/silangquan/article/details/39008903

A faster quaternion-vector multiplication - http://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/

Rotating vector3 by a quaternion - http://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion

用C++实现一个Quaternion类的更多相关文章

  1. 【Unity】6.8 Quaternion类(四元数)

    分类:Unity.C#.VS2015 创建日期:2016-04-20 一.四元数的概念 四元数包含一个标量分量和-个三维向量分量,四元数Q可以记作: Q=[w,(x,y,z)] 在3D数学中使用单位四 ...

  2. Unity3D - 详解Quaternion类(二)

    OK,不做引子了,接上篇Unity3D - 详解Quaternion类(一)走起! 四.Quaternion类静态方法 Quaternion中的静态方法有9个即:Angle方法.Dot方法.Euler ...

  3. Unity3D - 详解Quaternion类(一)

    一.简介 Quaternion又称四元数,由x,y,z和w这四个分量组成,是由爱尔兰数学家威廉·卢云·哈密顿在1843年发现的数学概念.四元数的乘法不符合交换律.从明确地角度而言,四元数是复数的不可交 ...

  4. PHP用单例模式实现一个数据库类

    使用单例模式的出发点: 1.php的应用主要在于数据库应用, 所以一个应用中会存在大量的数据库操作, 使用单例模式, 则可以避免大量的new 操作消耗的资源. 2.如果系统中需要有一个类来全局控制某些 ...

  5. 使用代码向一个普通的类注入Spring的实例

    转载请在页首注明作者与原文地址 一:应用场景 什么是普通的类,就是没有@Controller,@Service,@Repository,@Component等注解修饰的类,同时xml文件中,也没有相应 ...

  6. 一个Java文件至多包含一个公共类

    编写一个java源文件时,该源文件又称为编译单元.一个java文件可以包含多个类,但至多包含一个公共类,作为编译时该java文件的公用接口,公共类的名字和源文件的名字要相同,源文件名字的格式为[公共类 ...

  7. 一个java源文件中为什么只能有一个public类。

    我们都遇到过一个源文件中有多个java类,但当第一个类使用public修饰时,如果下面还有类使用public修饰,会报错.也就是是说一个java源文件最多只能有一个public类. 当有一个publi ...

  8. 很久以前写的一个 ShareRestrictedSD 类

    代码中一开始的 几个 USES 单元,可能是多余的. unit ShareRestrictedSD; interface uses Windows, Messages, SysUtils, Class ...

  9. 22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表。然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法showB输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。

    22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表.然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法sh ...

随机推荐

  1. Hue - Error loading MySQLdb module: libmysqlclient.so.20: cannot open shared object file: No such file or

    解决下面两点异常 >> 1. Hue页面 点击DB 查询时弹出: Error loading MySQLdb module: libmysqlclient.so.20: cannot op ...

  2. MAGENTO 插件

    导航放到右侧:magento-community/RicoNeitzel_VertNav 后台图片管理显示图片:magento-community/TBT_Enhancedgrid magento-c ...

  3. js类型识别

    typeof总结: 可以识别标准类型(Null除外) 不能识别具体的对象类型(Function除外) Object.prototype.toString总结: 可以识别标准类型和内置对象类型 不能识别 ...

  4. GAN生成图像论文总结

    GAN Theory Modifyingthe Optimization of GAN 题目 内容 GAN   DCGAN   WGAN   Least-square GAN   Loss Sensi ...

  5. day21-1 类的继承

    目录 类的继承 什么是继承 为什么用继承 对象的继承 继承与抽象 继承的应用 对象属性查找顺序 类的继承 什么是继承 继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承) ...

  6. CAD动态绘制样条线(网页版)

    在CAD设计时,需要绘制样条线,用户可以设置样条线线重及颜色等属性. 主要用到函数说明: _DMxDrawX::SendStringToExecuteFun 把命令当着函数执行,可以传参数.详细说明如 ...

  7. Commons_IO_FileUtils的使用

    commos_io.jar包下载地址:http://commons.apache.org/proper/commons-io/download_io.cgi 官方文档地址:http://commons ...

  8. iOS APP EuclidStudy Service Support

    Hi,If you have any questions, you can send a message here or send them to us. We will answer you as ...

  9. Java基础——二分法

    BinarySearch 二分法查找,顾名思义就是要将数据每次都分成两份然后再去找到你想要的数据,我们可以这样去想,二分法查找很类似与我们平时玩的猜价格游戏,当你报出一个价格时裁判会告诉你价格相对于真 ...

  10. share——Alpha版(内部测试版)发布

    我们产品的下载二维码: 使用说明: 后期会进行更新,文件下载位置