用C++实现一个Quaternion类
提要
四元素是游戏开发中经常使用的用于处理旋转的数学工具,以下就用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类的更多相关文章
- 【Unity】6.8 Quaternion类(四元数)
分类:Unity.C#.VS2015 创建日期:2016-04-20 一.四元数的概念 四元数包含一个标量分量和-个三维向量分量,四元数Q可以记作: Q=[w,(x,y,z)] 在3D数学中使用单位四 ...
- Unity3D - 详解Quaternion类(二)
OK,不做引子了,接上篇Unity3D - 详解Quaternion类(一)走起! 四.Quaternion类静态方法 Quaternion中的静态方法有9个即:Angle方法.Dot方法.Euler ...
- Unity3D - 详解Quaternion类(一)
一.简介 Quaternion又称四元数,由x,y,z和w这四个分量组成,是由爱尔兰数学家威廉·卢云·哈密顿在1843年发现的数学概念.四元数的乘法不符合交换律.从明确地角度而言,四元数是复数的不可交 ...
- PHP用单例模式实现一个数据库类
使用单例模式的出发点: 1.php的应用主要在于数据库应用, 所以一个应用中会存在大量的数据库操作, 使用单例模式, 则可以避免大量的new 操作消耗的资源. 2.如果系统中需要有一个类来全局控制某些 ...
- 使用代码向一个普通的类注入Spring的实例
转载请在页首注明作者与原文地址 一:应用场景 什么是普通的类,就是没有@Controller,@Service,@Repository,@Component等注解修饰的类,同时xml文件中,也没有相应 ...
- 一个Java文件至多包含一个公共类
编写一个java源文件时,该源文件又称为编译单元.一个java文件可以包含多个类,但至多包含一个公共类,作为编译时该java文件的公用接口,公共类的名字和源文件的名字要相同,源文件名字的格式为[公共类 ...
- 一个java源文件中为什么只能有一个public类。
我们都遇到过一个源文件中有多个java类,但当第一个类使用public修饰时,如果下面还有类使用public修饰,会报错.也就是是说一个java源文件最多只能有一个public类. 当有一个publi ...
- 很久以前写的一个 ShareRestrictedSD 类
代码中一开始的 几个 USES 单元,可能是多余的. unit ShareRestrictedSD; interface uses Windows, Messages, SysUtils, Class ...
- 22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表。然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法showB输出大写的英文字母表。最后编写主类C,在主类的main方法 中测试类A与类B。
22.编写一个类A,该类创建的对象可以调用方法showA输出小写的英文字母表.然后再编写一个A类的子类B,子类B创建的对象不仅可以调用方法showA输出小写的英文字母表,而且可以调用子类新增的方法sh ...
随机推荐
- java操作zip文件
思路: 1).读取zip中的文件并将除了重名文件之外的文件转存到中转zip文件中. 2).往中转文件中插入txt文件. 3).删除原zip文件. 4).将中转zip文件重命名为原zip文件. 前提,t ...
- HDU_2112_最短路
题目链接:http://acm.hdu.edu.cn/status.php?user=l1526789512&pid=2112&status=5 HDU Today Time Limi ...
- SpringBoot+Mybatis 自动创建数据表(适用mysql)
Mybatis用了快两年了,在我手上的发展史大概是这样的 第一个阶段 利用Mybatis-Generator自动生成实体类.DAO接口和Mapping映射文件.那时候觉得这个特别好用,大概的过程是这样 ...
- vue项目国际化实现 vue-i18n使用详细教程
1.安装vue-i18n: npm i vue-i18n -S 当然你也可以这样: <script src="https://unpkg.com/vue/dist/vue.js&quo ...
- c++类简介
C++类(Class)总结 一.C++类的定义 C++中使用关键字 class 来定义类, 其基本形式如下:class 类名{ public: //行为或属性 protected: // ...
- 脚手架工具搭建VUE应用
首先需要安装node.js,然后安装CLI工具. vue init webpack vue-lesson2 使用element组件的话,需要用到如下命令: cd vue-lesson2 vue add ...
- vue组件---动态组件之多标签页面
首先看下效果图 代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> & ...
- 02Struts2 环境搭建
Struts2 环境搭建 1.下载 Apache Struts2 类库 2.建立web工程 3.配置web.xml <?xml version="1.0" encoding= ...
- Python学习之LeetCode刷题之路——简单题【1、7、9】
1.两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个 ...
- 关于DOS-BOX的使用方法
将MASM文件夹里的全部文件拷贝到一个目录下,比如E:\masm下,然后将这个目录挂着为DOSBox的一个盘符下,挂载命令为 Mount c e:\masm 切换到E盘 然后编译,运行