用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 ...
随机推荐
- 推荐一些相见恨晚的 Python 库 「一」
扯淡 首先说明下,这篇文章篇幅过长并且大部分是链接,因此非常适合在电脑端打开访问. 本文内容摘自 Github 上有名的 Awesome Python.这是由 vinta 在 14 年发起并持续维护的 ...
- Android Bitmap转换WebP图片导致损坏的分析及解决方案
背景 作为移动领域所力推的图片格式,WebP图片在商业领域证明了其应有的价值.基于其他格式的横向对比,其在压缩性能表现,及还原度极为优秀,节省大量的带宽开销.基于可观的效益比,团队早前已开始磋商将当前 ...
- [转]MapReduce浅析
本文转自http://edisonchou.cnblogs.com/ 一.什么是MapReduce MapReduce是Google的一项重要技术,它首先是一个编程模型,用以进行大数据量的计算.对于大 ...
- iOS从手机相册选择一张照片并显示 Objective-C
要先给app设置访问相册的权限: 在项目的Info.plist文件里添加Privacy - Photo Library Usage Description权限 ViewController.h: #i ...
- vscode常好用的插件以及几个快捷操作
使用方法,可以在官网中搜索需要的插件或者在VsCode的“”扩展“”中搜索需要的插件添加方法使用Ctrl+P, 输入 ext install xxxx ,搜索要安装的插件,点击安装按钮即可(各取所需插 ...
- js正则表达式限制文本框只能输入数字,小数点,英文字母
1.文本框只能输入数字代码(小数点也不能输入)<input onkeyup="this.value=this.value.replace(/\D/g,'')" onafter ...
- SqlBulkCopy实现大批量数据导入
//自增列重新生成:SqlBulkCopy bc = new SqlBulkCopy(conn) //自增列保留原值:SqlBulkCopy bc = new SqlBulkCopy(conn,Sql ...
- 使用Unittest做单元测试,addTest()单个case的时候却执行全部的case
参考: http://tieba.baidu.com/p/6008699660 首先造成这个结果的原因是pycharm配置问题 问题验证: 测试代码: import unittest class Te ...
- Libreswan软件的密钥协商协议IKEv1主模式实现分析
Libreswan软件的密钥协商协议IKEv1主模式实现分析 1 协商过程 IKEv1(互联网密钥交换协议第一版)是IPsec VPN隧道协商使用的标准密钥协商协议,其协商过程如下图所示. 总共来回交 ...
- 【jenkins】UnicodeEncodeError: 'ascii' codec can't encode character
https://stackoverflow.com/questions/6076203/how-do-you-set-the-default-encoding-in-jenkins