提要

四元素是游戏开发中经常使用的用于处理旋转的数学工具,以下就用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. Java反射机制实战——字段篇

    首先,我们来认识几个类. Class(java.lang.Class) Class对象是一个特殊对象,每一个类都有一个Class对象,用来创建该类的“常规”对象.可以通过对象的getClass()方法 ...

  2. oracle dos命令

    1.无账户密码登录数据库:sqlplus/nolog 后面不能加分号,否则不能识别 2.登录数据库:sqlplus 3.在sql下测试连接性:conn oracle_name/oracle_passw ...

  3. jstree的基本应用----记录

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  4. DropDownList 递归绑定分子公司信息

    /// <summary> /// 绑定下拉框 /// </summary> /// <param name="ddl">绑定控件名称</ ...

  5. Java基础(五)--内部类

    内部类简单来说就是把一个类的定义放到另一个类的定义内部 内部类分为:成员内部类.局部内部类.匿名内部类.静态内部类 成员内部类:最常见的内部类 public class Outter { privat ...

  6. Oracle 把一个用户所有表的读权限授予另一个用户

    create user <USER_NAME> identified by <PASSWORD>; grant create session TO <USER_NAME& ...

  7. JS数组——冒泡、插入、快速排序

    前言:因为要对后端返回来的数据进行处理,之前之后冒泡,不够用,去看了插入跟快速,写下这篇笔记. 使用背景: 1.冒泡排序 数据比较少,小于1000 2.插入排序 数据比较少,大于1000不推荐 3.快 ...

  8. post请求重定向到get请求问题

    springMVC默认重定向是get请求,我在方法注解中没有指定method是post还是get请求,这样就可以接收到post重定向来的请求,也可以接收到页面传来的get请求,如果要传参,可以使用mo ...

  9. scrapy 按顺序抓取text内容

    需求:获得如下li.clearfix 下的所有text,并且按顺序输出 1. x.css('div.reply-doc h4 a::text').extract(); 2.  x.css('div.r ...

  10. Jmeter使用笔记之断言

    前言 Jmeter的断言方式有很多种,由于在工作中经常做的是API接口测试,所以这篇文章主要介绍如何对接口的字段进行解析,如何对解析出来的字段的值断言 了解API接口 Restful API 规范 协 ...