在2.2矩阵章节讲到可以将坐标空间的基向量使用矩阵来表示,从而可以用3x3的矩阵来表达物体的方位,并且使用矩阵表示方位也是图形API使用的形式。但3x3的旋转矩阵需要存储9个数据,相比较欧拉角和四元数,内存占用会比较大,并且表达并不直观。
欧拉角可以使用3个数来表达方位,欧拉角使用Roll-Pitch-Yaw 或 Heading-Pitch-Bank的命名约定可以用来表示物体绕各个坐标轴的旋转。在我们实现中,并不打算写一个欧拉角的类,而是直接使用Vector3来存储欧拉角绕x、y、z的旋转。欧拉角在开发中对于程序调试非方便,但欧拉角本身也存在一定的缺点,首先欧拉角有万向锁的问题,即我们先绕y轴进行水平方向的旋转,再绕x轴进行垂直方向的旋转,而这时旋转了90度,则当绕z轴旋转时,旋转的方位仍然是在水平方向,在这种情况丢失了一个旋转维度。另外一点就是欧拉角表示方位的方式不唯一,即绕x轴旋转theta度和这个角度加上n * 360度表示的是同一个方位,这可能会对我们方向的插值造成困扰。
四元数使用4个数来表示绕轴v旋转theta度的方位,在使用四元数表示方位时,类似于向量表示方向的单位向量, 我们使用的是单位四元数,即四元数||q|| = 1,因此四元数中并不是直接存储了旋转角度theta和旋转轴,而是以
q = [cos(θ/2) sin(θ/2)nx sin(θ/2)ny sin(θ/2)nz]
这种形式来存储数据,使用单位四元数时,他的四元数的逆等于四元数的共轭:
q-1 = q*/||q|| = q* = [w -v]
这样就可以使用共轭来表示相反的方位角。四元数主要包含了下面的操作:
1 template<typename T>
2 class Quaternion
3 {
4 public:
5 Quaternion();
6 Quaternion(T w, T x, T y, T z);
7
8 void Identity();
9 void Normalize();
10
11 Vector3<T> ToEulerAngle();
12 Quaternion &FromEulerAngle(T x, T y, T z);
13
14 Quaternion &MakeRotateByX(T theta);
15 Quaternion &MakeRotateByY(T theta);
16 Quaternion &MakeRotateByZ(T theta);
17 Quaternion &MakeRotateByAxis(const Vector3<T> &axis, T theta);
18 T GetRotationAngle() const;
19 Vector3<T> GetRotationAxis() const;
20
21 Quaternion operator*(const Quaternion &q) const;
22 Vector3<T> operator*(const Vector3<T> &v) const;
23
24 static Quaternion Slerp(const Quaternion &q0, const Quaternion &q1, float t);
25
26 template <typename T1>
27 friend ostream &operator<<(ostream &out, const Quaternion<T1> &q);
28 private:
29 T w, x, y, z;
30 };
第11行~12行声明了四元数与欧拉角之间的转换,第14~17行则是以四元数的形式声明绕各轴旋转,第21~22行声明了四元数和乘法操作,四元数之前的乘法与矩阵乘法类似,可以将多个旋转操作连接为一个四元数,四元数与向量相乘则是对用来旋转向量的。
第24行的Slerp操作是对四元数的球面插值。上面代码的具体实现代码如下:
1 template <typename T>
2 Quaternion<T>::Quaternion()
3 :w(1), x(0), y(0), z(0)
4 {
5 }
6
7 template <typename T>
8 Quaternion<T>::Quaternion(T w, T x, T y, T z)
9 :w(w), x(x), y(y), z(z)
10 {
11 }
12
13 template <typename T>
14 void Quaternion<T>::Identity()
15 {
16 w = static_cast<T>(1);
17 x = y = z = static_cast<T>(0);
18 }
19
20 template <typename T>
21 void Quaternion<T>::Normalize()
22 {
23 T mag = (T)sqrt(w * w + x * x + y * y + z * z);
24
25 if (mag > 0)
26 {
27 T oneOverMag = (T)1 / mag;
28 w *= oneOverMag;
29 x *= oneOverMag;
30 y *= oneOverMag;
31 z *= oneOverMag;
32 }
33 else
34 {
35 assert(false);
36 Identity();
37 }
38 }
39
40 template <typename T>
41 Vector3<T> Quaternion<T>::ToEulerAngle()
42 {
43 Vector3<T> euler;
44 Quaternion &q = *this;
45 T sp = (T)-2 * (q.y * q.z - q.w * q.x);
46 if (fabs(sp) > (T)0.9999)
47 {
48 euler.y = PI_DIV_2 * sp;
49 euler.x = atan2(-q.x + q.z + q.w * q.y, (T)0.5 - q.y * q.y - q.z * q.z);
50 euler.z = 0;
51 }
52 else
53 {
54 euler.y = asin(sp);
55 euler.x = atan2(q.x * q.z + q.w * q.y, (T)0.5 - q.x * q.x - q.y * q.y);
56 euler.z = atan2(q.x * q.y + q.w * q.z, (T)0.5 - q.x * q.x - q.z * q.z);
57 }
58
59 return euler;
60 }
61
62 template <typename T>
63 Quaternion<T> &Quaternion<T>::FromEulerAngle(T x, T y, T z)
64 {
65 T hOver2 = (T)0.5 * y;
66 T pOver2 = (T)0.5 * x;
67 T bOver2 = (T)0.5 * z;
68
69 T sh, sp, sb;
70 T ch, cp, cb;
71
72 sh = sin(hOver2);
73 ch = cos(hOver2);
74 sp = sin(pOver2);
75 cp = cos(pOver2);
76 sb = sin(bOver2);
77 cb = cos(bOver2);
78
79 this->w = ch * cp * cb + sh * sp * sb;
80 this->x = ch * sp * cb + sh * cp * sb;
81 this->y = -ch * sp * sb + sh * cp * cb;
82 this->z = -sh * sp * cb + ch * cp * sb;
83
84 return *this;
85 }
86
87 template <typename T>
88 Quaternion<T> Quaternion<T>::operator*(const Quaternion<T> &q) const
89 {
90 //右乘
91 return Quaternion(w * q.w - x * q.x - y * q.y - z * q.z,
92 w * q.x + x * q.w + z * q.y - y * q.z,
93 w * q.y + y * q.w + x * q.z - z * q.x,
94 w * q.z + z * q.w + y * q.x - x * q.y);
95 }
96
97 template <typename T>
98 Vector3<T> Quaternion<T>::operator*(const Vector3<T> &v) const
99 {
100 Quaternion vq(0, v.x, v.y, v.z);
101 Quaternion invQ(w, -x, -y, -z);
102 Quaternion result = invQ * vq * *this;
103
104 return Vector3<T>(result.x, result.y, result.z);
105 }
106
107 template <typename T>
108 Quaternion<T> &Quaternion<T>::MakeRotateByX(T theta)
109 {
110 T thetaOver2 = theta * (T)0.5;
111
112 w = cos(thetaOver2);
113 x = sin(thetaOver2);
114 y = 0;
115 z = 0;
116 return *this;
117 }
118
119 template <typename T>
120 Quaternion<T> &Quaternion<T>::MakeRotateByY(T theta)
121 {
122 T thetaOver2 = theta * (T)0.5;
123
124 w = cos(thetaOver2);
125 x = 0;
126 y = sin(thetaOver2);
127 z = 0;
128 return *this;
129 }
130 template <typename T>
131 Quaternion<T> &Quaternion<T>::MakeRotateByZ(T theta)
132 {
133 T thetaOver2 = theta * (T)0.5;
134
135 w = cos(thetaOver2);
136 x = 0;
137 y = 0;
138 z = sin(thetaOver2);
139 return *this;
140 }
141 template <typename T>
142 Quaternion<T> &Quaternion<T>::MakeRotateByAxis(const Vector3<T> &axis, T theta)
143 {
144 Vector3<T> axisNormallize = axis;
145 axisNormallize.Normalize();
146
147 T thetaOver2 = theta * (T)0.5;
148 T sinThetaOver2 = sin(thetaOver2);
149
150 w = cos(thetaOver2);
151 x = axisNormallize.x * sinThetaOver2;
152 y = axisNormallize.y * sinThetaOver2;
153 z = axisNormallize.z * sinThetaOver2;
154 return *this;
155 }
156
157 template <typename T>
158 T Quaternion<T>::GetRotationAngle() const
159 {
160 T thetaOver2;
161 if (w <= (T)-1)
162 thetaOver2 = PI;
163 else if (w >= (T)1)
164 thetaOver2 = 0;
165 else
166 thetaOver2 = acos(w);
167 return thetaOver2 * (T)2;
168 }
169
170 template <typename T>
171 Vector3<T> Quaternion<T>::GetRotationAxis() const
172 {
173 T sinThetaOver2Sq = (T)1 - w * w;
174 if (sinThetaOver2Sq <= 0)
175 {
176 return Vector3<T>(1, 0, 0);
177 }
178
179 T oneOverSinThetaOver2 = (T)1 / sqrt(sinThetaOver2Sq);
180 return Vector3<T>(x * oneOverSinThetaOver2, y * oneOverSinThetaOver2, z * oneOverSinThetaOver2);
181 }
182
183 template <typename T>
184 Quaternion<T> Quaternion<T>::Slerp(const Quaternion &q0, const Quaternion &q1, float t)
185 {
186 if (t <= 0) return q0;
187 if (t >= (T)1) return q1;
188
189 T cosOmega = q0.w * q1.w + q0.x * q1.x + q0.y * q1.y + q0.z * q1.z;
190 T q1w = q1.w;
191 T q1x = q1.x;
192 T q1y = q1.y;
193 T q1z = q1.z;
194 if (cosOmega < 0)
195 {
196 q1w = -q1w;
197 q1x = -q1x;
198 q1y = -q1y;
199 q1z = -q1z;
200 cosOmega = -cosOmega;
201 }
202
203 assert(cosOmega < 1);
204
205 T k0, k1;
206 if (cosOmega > (T)0.9999)
207 {
208 k0 = (T)1 - t;
209 k1 = t;
210 }
211 else
212 {
213 T sinOmega = sqrt((T)1 - cosOmega * cosOmega);
214 T omega = atan2(sinOmega, cosOmega);
215 T oneOverSinOmega = (T)1 / sinOmega;
216 k0 = sin(((T)(1) - t) * omega) * oneOverSinOmega;
217 k1 = sin(t * omega) * oneOverSinOmega;
218 }
219
220 return Quaternion(
221 k0 * q0.w + k1 * q1w,
222 k0 * q0.x + k1 * q1x,
223 k0 * q0.y + k1 * q1y,
224 k0 * q0.z + k1 * q1z);
225 }
226
227 template <typename T>
228 ostream &operator<<(ostream &out, const Quaternion<T> &q)
229 {
230 out << q.w << "," << q.x << "," << q.y << "," << q.z;
231 return out;
232 }
具体实现都比较简单,这里重点讲一下Slerp操作,先以向量为类,如下图所示:

向量vt = k0v0 + k1v1,这里只需要求出k0和k1即可,由以k1v1为斜边的直角三角形可知:
sin(theta) = sin(t * theta) / k1,
则:
k1 = sin(t * theta) / sin(theta),
同理:
k0 = sin((1-t) * theta) / sin(theta),
将向量的Slerp操作扩展到四元数即得到了四元数的球面插值操作:
Slerp(q0, q1, t) = k1q0 + k1q1
参考文献:
3D数学基础:图形与游戏开发
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl