提要

四元素是游戏开发中经常使用的用于处理旋转的数学工具,以下就用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. 安装ipython notebook及基本命令(快捷键)

    转载自:http://121.42.47.99/yuenshome/wordpress/?p=2622 目前基本上是Pycharm和ipython notebook结合起来做东西,ipython no ...

  2. 什么 是JUnit?

    JUnit详解: http://www.cnblogs.com/eggbucket/archive/2012/02/02/2335697.html

  3. Pro ASP.NET Core MVC 第6版 第一章

    目录 第一章 ASP.NET Core MVC 的前世今生 ASP.NET Core MVC 是一个微软公司开发的Web应用程序开发框架,它结合了MVC架构的高效性和简洁性,敏捷开发的思想和技术和.N ...

  4. 26-Ubuntu-文件和目录命令-其他命令-管道

    管道 Linux允许将一个命令的输出可以通过管道作为另一个命令的输入. 可以理解为现实生活中的管子,管子的一头塞东西进去,另一头取出来,这里的 | 左右分为两端,左端塞东西(写),右端取东西(读). ...

  5. 16Oracle Database 系统权限和对象权限

    Oracle Database 系统权限和对象权限 Oracle中的系统权限和对象权限 DCL 数据控制语言 -- 查看对象的权限 grant / revoke 查看登录用户 Show user 查看 ...

  6. WebService接口数据传输加密

    1.加密流程 客服端--->加密文本------>服务端接收到加密文本,通过固定加密密文进行解密,然后做相应处理------------>返回结果 2.固定密文创建 密文创建有很多种 ...

  7. 洛谷——P2261 [CQOI2007]余数求和

    P2261 [CQOI2007]余数求和 关键在于化简公式,题目所求$\sum_{i=1}^{n}k\mod i$ 简化式子,也就是$\sum_{i=1}^{n}(k-\frac{k}{i}\time ...

  8. Gym - 101670E Forest Picture (CTU Open Contest 2017 模拟)

    题目: https://cn.vjudge.net/problem/1451310/origin 题意&思路: 纯粹模拟. 大体题意是这样的: 1.有人要在一个10-9<=x<=1 ...

  9. APUE 文件IO

    文件 IO 记录书中的重要知识和思考实践部分 Unix 每个文件都对应一个文件描述符(file descriptor),为一个非负整数,一个文件可以有多个fd, 后面所有与文件(设备,套接字等)有关操 ...

  10. 如何使用微信小程序video组件播放视频

    相信很多人都有在手机上看视频的习惯,比较看视频更真实更形象.那么我们在微信小程序中如何观看视频呢?这就需要video组件的帮忙了.今天我们就给大家演示一下,如何用微信小程序组件video播放视频.我们 ...