1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <QtTest/QtTest>
30#include <QtCore/qmath.h>
31#include <QtGui/qquaternion.h>
32
33// This is a more tolerant version of qFuzzyCompare that also handles the case
34// where one or more of the values being compare are close to zero
35static inline bool myFuzzyCompare(float p1, float p2)
36{
37 if (qFuzzyIsNull(f: p1) && qFuzzyIsNull(f: p2))
38 return true;
39 return qAbs(t: qAbs(t: p1) - qAbs(t: p2)) <= 0.00003f;
40}
41
42static inline bool myFuzzyCompare(const QVector3D &v1, const QVector3D &v2)
43{
44 return myFuzzyCompare(p1: v1.x(), p2: v2.x())
45 && myFuzzyCompare(p1: v1.y(), p2: v2.y())
46 && myFuzzyCompare(p1: v1.z(), p2: v2.z());
47}
48
49static inline bool myFuzzyCompare(const QQuaternion &q1, const QQuaternion &q2)
50{
51 const float d = QQuaternion::dotProduct(q1, q2);
52 return myFuzzyCompare(p1: d * d, p2: 1.0f);
53}
54
55static inline bool myFuzzyCompareRadians(float p1, float p2)
56{
57 static const float fPI = float(M_PI);
58 if (p1 < -fPI)
59 p1 += 2.0f * fPI;
60 else if (p1 > fPI)
61 p1 -= 2.0f * fPI;
62
63 if (p2 < -fPI)
64 p2 += 2.0f * fPI;
65 else if (p2 > fPI)
66 p2 -= 2.0f * fPI;
67
68 return qAbs(t: qAbs(t: p1) - qAbs(t: p2)) <= qDegreesToRadians(degrees: 0.05f);
69}
70
71static inline bool myFuzzyCompareDegrees(float p1, float p2)
72{
73 p1 = qDegreesToRadians(degrees: p1);
74 p2 = qDegreesToRadians(degrees: p2);
75 return myFuzzyCompareRadians(p1, p2);
76}
77
78
79class tst_QQuaternion : public QObject
80{
81 Q_OBJECT
82public:
83 tst_QQuaternion() {}
84 ~tst_QQuaternion() {}
85
86private slots:
87 void create();
88
89 void dotProduct_data();
90 void dotProduct();
91
92 void length_data();
93 void length();
94
95 void normalized_data();
96 void normalized();
97
98 void normalize_data();
99 void normalize();
100
101 void inverted_data();
102 void inverted();
103
104 void compare();
105
106 void add_data();
107 void add();
108
109 void subtract_data();
110 void subtract();
111
112 void multiply_data();
113 void multiply();
114
115 void multiplyFactor_data();
116 void multiplyFactor();
117
118 void divide_data();
119 void divide();
120
121 void negate_data();
122 void negate();
123
124 void conjugate_data();
125 void conjugate();
126
127 void fromAxisAndAngle_data();
128 void fromAxisAndAngle();
129
130 void fromRotationMatrix_data();
131 void fromRotationMatrix();
132
133 void fromAxes_data();
134 void fromAxes();
135
136 void rotationTo_data();
137 void rotationTo();
138
139 void fromDirection_data();
140 void fromDirection();
141
142 void fromEulerAngles_data();
143 void fromEulerAngles();
144
145 void slerp_data();
146 void slerp();
147
148 void nlerp_data();
149 void nlerp();
150
151 void properties();
152 void metaTypes();
153};
154
155// Test the creation of QQuaternion objects in various ways:
156// construct, copy, and modify.
157void tst_QQuaternion::create()
158{
159 QQuaternion identity;
160 QCOMPARE(identity.x(), 0.0f);
161 QCOMPARE(identity.y(), 0.0f);
162 QCOMPARE(identity.z(), 0.0f);
163 QCOMPARE(identity.scalar(), 1.0f);
164 QVERIFY(identity.isIdentity());
165
166 QQuaternion negativeZeroIdentity(1.0f, -0.0f, -0.0f, -0.0f);
167 QCOMPARE(negativeZeroIdentity.x(), -0.0f);
168 QCOMPARE(negativeZeroIdentity.y(), -0.0f);
169 QCOMPARE(negativeZeroIdentity.z(), -0.0f);
170 QCOMPARE(negativeZeroIdentity.scalar(), 1.0f);
171 QVERIFY(negativeZeroIdentity.isIdentity());
172
173 QQuaternion v1(34.0f, 1.0f, 2.5f, -89.25f);
174 QCOMPARE(v1.x(), 1.0f);
175 QCOMPARE(v1.y(), 2.5f);
176 QCOMPARE(v1.z(), -89.25f);
177 QCOMPARE(v1.scalar(), 34.0f);
178 QVERIFY(!v1.isNull());
179
180 QQuaternion v1i(34, 1, 2, -89);
181 QCOMPARE(v1i.x(), 1.0f);
182 QCOMPARE(v1i.y(), 2.0f);
183 QCOMPARE(v1i.z(), -89.0f);
184 QCOMPARE(v1i.scalar(), 34.0f);
185 QVERIFY(!v1i.isNull());
186
187 QQuaternion v2(v1);
188 QCOMPARE(v2.x(), 1.0f);
189 QCOMPARE(v2.y(), 2.5f);
190 QCOMPARE(v2.z(), -89.25f);
191 QCOMPARE(v2.scalar(), 34.0f);
192 QVERIFY(!v2.isNull());
193
194 QQuaternion v4;
195 QCOMPARE(v4.x(), 0.0f);
196 QCOMPARE(v4.y(), 0.0f);
197 QCOMPARE(v4.z(), 0.0f);
198 QCOMPARE(v4.scalar(), 1.0f);
199 QVERIFY(v4.isIdentity());
200 v4 = v1;
201 QCOMPARE(v4.x(), 1.0f);
202 QCOMPARE(v4.y(), 2.5f);
203 QCOMPARE(v4.z(), -89.25f);
204 QCOMPARE(v4.scalar(), 34.0f);
205 QVERIFY(!v4.isNull());
206
207 QQuaternion v9(34, QVector3D(1.0f, 2.5f, -89.25f));
208 QCOMPARE(v9.x(), 1.0f);
209 QCOMPARE(v9.y(), 2.5f);
210 QCOMPARE(v9.z(), -89.25f);
211 QCOMPARE(v9.scalar(), 34.0f);
212 QVERIFY(!v9.isNull());
213
214 v1.setX(3.0f);
215 QCOMPARE(v1.x(), 3.0f);
216 QCOMPARE(v1.y(), 2.5f);
217 QCOMPARE(v1.z(), -89.25f);
218 QCOMPARE(v1.scalar(), 34.0f);
219 QVERIFY(!v1.isNull());
220
221 v1.setY(10.5f);
222 QCOMPARE(v1.x(), 3.0f);
223 QCOMPARE(v1.y(), 10.5f);
224 QCOMPARE(v1.z(), -89.25f);
225 QCOMPARE(v1.scalar(), 34.0f);
226 QVERIFY(!v1.isNull());
227
228 v1.setZ(15.5f);
229 QCOMPARE(v1.x(), 3.0f);
230 QCOMPARE(v1.y(), 10.5f);
231 QCOMPARE(v1.z(), 15.5f);
232 QCOMPARE(v1.scalar(), 34.0f);
233 QVERIFY(!v1.isNull());
234
235 v1.setScalar(6.0f);
236 QCOMPARE(v1.x(), 3.0f);
237 QCOMPARE(v1.y(), 10.5f);
238 QCOMPARE(v1.z(), 15.5f);
239 QCOMPARE(v1.scalar(), 6.0f);
240 QVERIFY(!v1.isNull());
241
242 v1.setVector(aX: 2.0f, aY: 6.5f, aZ: -1.25f);
243 QCOMPARE(v1.x(), 2.0f);
244 QCOMPARE(v1.y(), 6.5f);
245 QCOMPARE(v1.z(), -1.25f);
246 QCOMPARE(v1.scalar(), 6.0f);
247 QVERIFY(!v1.isNull());
248 QVERIFY(v1.vector() == QVector3D(2.0f, 6.5f, -1.25f));
249
250 v1.setVector(QVector3D(-2.0f, -6.5f, 1.25f));
251 QCOMPARE(v1.x(), -2.0f);
252 QCOMPARE(v1.y(), -6.5f);
253 QCOMPARE(v1.z(), 1.25f);
254 QCOMPARE(v1.scalar(), 6.0f);
255 QVERIFY(!v1.isNull());
256 QVERIFY(v1.vector() == QVector3D(-2.0f, -6.5f, 1.25f));
257
258 v1.setX(0.0f);
259 v1.setY(0.0f);
260 v1.setZ(0.0f);
261 v1.setScalar(0.0f);
262 QCOMPARE(v1.x(), 0.0f);
263 QCOMPARE(v1.y(), 0.0f);
264 QCOMPARE(v1.z(), 0.0f);
265 QCOMPARE(v1.scalar(), 0.0f);
266 QVERIFY(v1.isNull());
267
268 QVector4D v10 = v9.toVector4D();
269 QCOMPARE(v10.x(), 1.0f);
270 QCOMPARE(v10.y(), 2.5f);
271 QCOMPARE(v10.z(), -89.25f);
272 QCOMPARE(v10.w(), 34.0f);
273}
274
275// Test the computation of dot product.
276void tst_QQuaternion::dotProduct_data()
277{
278 QTest::addColumn<float>(name: "x1");
279 QTest::addColumn<float>(name: "y1");
280 QTest::addColumn<float>(name: "z1");
281 QTest::addColumn<float>(name: "scalar1");
282 QTest::addColumn<float>(name: "x2");
283 QTest::addColumn<float>(name: "y2");
284 QTest::addColumn<float>(name: "z2");
285 QTest::addColumn<float>(name: "scalar2");
286 QTest::addColumn<float>(name: "dot");
287
288 QTest::newRow(dataTag: "null")
289 << 0.0f << 0.0f << 0.0f << 0.0f
290 << 0.0f << 0.0f << 0.0f << 0.0f
291 << 0.0f;
292
293 QTest::newRow(dataTag: "identity")
294 << 0.0f << 0.0f << 0.0f << 1.0f
295 << 0.0f << 0.0f << 0.0f << 1.0f
296 << 1.0f;
297
298 QTest::newRow(dataTag: "unitvec")
299 << 1.0f << 0.0f << 0.0f << 0.0f
300 << 0.0f << 1.0f << 0.0f << 0.0f
301 << 0.0f;
302
303 QTest::newRow(dataTag: "complex")
304 << 1.0f << 2.0f << 3.0f << 4.0f
305 << 4.0f << 5.0f << 6.0f << 7.0f
306 << 60.0f;
307}
308void tst_QQuaternion::dotProduct()
309{
310 QFETCH(float, x1);
311 QFETCH(float, y1);
312 QFETCH(float, z1);
313 QFETCH(float, scalar1);
314 QFETCH(float, x2);
315 QFETCH(float, y2);
316 QFETCH(float, z2);
317 QFETCH(float, scalar2);
318 QFETCH(float, dot);
319
320 QQuaternion q1(scalar1, x1, y1, z1);
321 QQuaternion q2(scalar2, x2, y2, z2);
322
323 QCOMPARE(QQuaternion::dotProduct(q1, q2), dot);
324 QCOMPARE(QQuaternion::dotProduct(q2, q1), dot);
325}
326
327// Test length computation for quaternions.
328void tst_QQuaternion::length_data()
329{
330 QTest::addColumn<float>(name: "x");
331 QTest::addColumn<float>(name: "y");
332 QTest::addColumn<float>(name: "z");
333 QTest::addColumn<float>(name: "w");
334 QTest::addColumn<float>(name: "len");
335
336 QTest::newRow(dataTag: "null") << 0.0f << 0.0f << 0.0f << 0.0f << 0.0f;
337 QTest::newRow(dataTag: "1x") << 1.0f << 0.0f << 0.0f << 0.0f << 1.0f;
338 QTest::newRow(dataTag: "1y") << 0.0f << 1.0f << 0.0f << 0.0f << 1.0f;
339 QTest::newRow(dataTag: "1z") << 0.0f << 0.0f << 1.0f << 0.0f << 1.0f;
340 QTest::newRow(dataTag: "1w") << 0.0f << 0.0f << 0.0f << 1.0f << 1.0f;
341 QTest::newRow(dataTag: "-1x") << -1.0f << 0.0f << 0.0f << 0.0f << 1.0f;
342 QTest::newRow(dataTag: "-1y") << 0.0f << -1.0f << 0.0f << 0.0f << 1.0f;
343 QTest::newRow(dataTag: "-1z") << 0.0f << 0.0f << -1.0f << 0.0f << 1.0f;
344 QTest::newRow(dataTag: "-1w") << 0.0f << 0.0f << 0.0f << -1.0f << 1.0f;
345 QTest::newRow(dataTag: "two") << 2.0f << -2.0f << 2.0f << 2.0f << std::sqrt(x: 16.0f);
346}
347void tst_QQuaternion::length()
348{
349 QFETCH(float, x);
350 QFETCH(float, y);
351 QFETCH(float, z);
352 QFETCH(float, w);
353 QFETCH(float, len);
354
355 QQuaternion v(w, x, y, z);
356 QCOMPARE(v.length(), len);
357 QCOMPARE(v.lengthSquared(), x * x + y * y + z * z + w * w);
358}
359
360// Test the unit vector conversion for quaternions.
361void tst_QQuaternion::normalized_data()
362{
363 // Use the same test data as the length test.
364 length_data();
365}
366void tst_QQuaternion::normalized()
367{
368 QFETCH(float, x);
369 QFETCH(float, y);
370 QFETCH(float, z);
371 QFETCH(float, w);
372 QFETCH(float, len);
373
374 QQuaternion v(w, x, y, z);
375 QQuaternion u = v.normalized();
376 if (v.isNull())
377 QVERIFY(u.isNull());
378 else
379 QCOMPARE(u.length(), 1.0f);
380 QCOMPARE(u.x() * len, v.x());
381 QCOMPARE(u.y() * len, v.y());
382 QCOMPARE(u.z() * len, v.z());
383 QCOMPARE(u.scalar() * len, v.scalar());
384}
385
386// Test the unit vector conversion for quaternions.
387void tst_QQuaternion::normalize_data()
388{
389 // Use the same test data as the length test.
390 length_data();
391}
392void tst_QQuaternion::normalize()
393{
394 QFETCH(float, x);
395 QFETCH(float, y);
396 QFETCH(float, z);
397 QFETCH(float, w);
398
399 QQuaternion v(w, x, y, z);
400 bool isNull = v.isNull();
401 v.normalize();
402 if (isNull)
403 QVERIFY(v.isNull());
404 else
405 QCOMPARE(v.length(), 1.0f);
406}
407
408void tst_QQuaternion::inverted_data()
409{
410 // Use the same test data as the length test.
411 length_data();
412}
413void tst_QQuaternion::inverted()
414{
415 QFETCH(float, x);
416 QFETCH(float, y);
417 QFETCH(float, z);
418 QFETCH(float, w);
419 QFETCH(float, len);
420
421 QQuaternion v(w, x, y, z);
422 QQuaternion u = v.inverted();
423 if (v.isNull()) {
424 QVERIFY(u.isNull());
425 } else {
426 len *= len;
427 QCOMPARE(-u.x() * len, v.x());
428 QCOMPARE(-u.y() * len, v.y());
429 QCOMPARE(-u.z() * len, v.z());
430 QCOMPARE(u.scalar() * len, v.scalar());
431 }
432}
433
434// Test the comparison operators for quaternions.
435void tst_QQuaternion::compare()
436{
437 QQuaternion v1(8, 1, 2, 4);
438 QQuaternion v2(8, 1, 2, 4);
439 QQuaternion v3(8, 3, 2, 4);
440 QQuaternion v4(8, 1, 3, 4);
441 QQuaternion v5(8, 1, 2, 3);
442 QQuaternion v6(3, 1, 2, 4);
443
444 QCOMPARE(v1, v2);
445 QVERIFY(v1 != v3);
446 QVERIFY(v1 != v4);
447 QVERIFY(v1 != v5);
448 QVERIFY(v1 != v6);
449}
450
451// Test addition for quaternions.
452void tst_QQuaternion::add_data()
453{
454 QTest::addColumn<float>(name: "x1");
455 QTest::addColumn<float>(name: "y1");
456 QTest::addColumn<float>(name: "z1");
457 QTest::addColumn<float>(name: "w1");
458 QTest::addColumn<float>(name: "x2");
459 QTest::addColumn<float>(name: "y2");
460 QTest::addColumn<float>(name: "z2");
461 QTest::addColumn<float>(name: "w2");
462 QTest::addColumn<float>(name: "x3");
463 QTest::addColumn<float>(name: "y3");
464 QTest::addColumn<float>(name: "z3");
465 QTest::addColumn<float>(name: "w3");
466
467 QTest::newRow(dataTag: "null")
468 << 0.0f << 0.0f << 0.0f << 0.0f
469 << 0.0f << 0.0f << 0.0f << 0.0f
470 << 0.0f << 0.0f << 0.0f << 0.0f;
471
472 QTest::newRow(dataTag: "xonly")
473 << 1.0f << 0.0f << 0.0f << 0.0f
474 << 2.0f << 0.0f << 0.0f << 0.0f
475 << 3.0f << 0.0f << 0.0f << 0.0f;
476
477 QTest::newRow(dataTag: "yonly")
478 << 0.0f << 1.0f << 0.0f << 0.0f
479 << 0.0f << 2.0f << 0.0f << 0.0f
480 << 0.0f << 3.0f << 0.0f << 0.0f;
481
482 QTest::newRow(dataTag: "zonly")
483 << 0.0f << 0.0f << 1.0f << 0.0f
484 << 0.0f << 0.0f << 2.0f << 0.0f
485 << 0.0f << 0.0f << 3.0f << 0.0f;
486
487 QTest::newRow(dataTag: "wonly")
488 << 0.0f << 0.0f << 0.0f << 1.0f
489 << 0.0f << 0.0f << 0.0f << 2.0f
490 << 0.0f << 0.0f << 0.0f << 3.0f;
491
492 QTest::newRow(dataTag: "all")
493 << 1.0f << 2.0f << 3.0f << 8.0f
494 << 4.0f << 5.0f << -6.0f << 9.0f
495 << 5.0f << 7.0f << -3.0f << 17.0f;
496}
497void tst_QQuaternion::add()
498{
499 QFETCH(float, x1);
500 QFETCH(float, y1);
501 QFETCH(float, z1);
502 QFETCH(float, w1);
503 QFETCH(float, x2);
504 QFETCH(float, y2);
505 QFETCH(float, z2);
506 QFETCH(float, w2);
507 QFETCH(float, x3);
508 QFETCH(float, y3);
509 QFETCH(float, z3);
510 QFETCH(float, w3);
511
512 QQuaternion v1(w1, x1, y1, z1);
513 QQuaternion v2(w2, x2, y2, z2);
514 QQuaternion v3(w3, x3, y3, z3);
515
516 QVERIFY((v1 + v2) == v3);
517
518 QQuaternion v4(v1);
519 v4 += v2;
520 QCOMPARE(v4, v3);
521
522 QCOMPARE(v4.x(), v1.x() + v2.x());
523 QCOMPARE(v4.y(), v1.y() + v2.y());
524 QCOMPARE(v4.z(), v1.z() + v2.z());
525 QCOMPARE(v4.scalar(), v1.scalar() + v2.scalar());
526}
527
528// Test subtraction for quaternions.
529void tst_QQuaternion::subtract_data()
530{
531 // Use the same test data as the add test.
532 add_data();
533}
534void tst_QQuaternion::subtract()
535{
536 QFETCH(float, x1);
537 QFETCH(float, y1);
538 QFETCH(float, z1);
539 QFETCH(float, w1);
540 QFETCH(float, x2);
541 QFETCH(float, y2);
542 QFETCH(float, z2);
543 QFETCH(float, w2);
544 QFETCH(float, x3);
545 QFETCH(float, y3);
546 QFETCH(float, z3);
547 QFETCH(float, w3);
548
549 QQuaternion v1(w1, x1, y1, z1);
550 QQuaternion v2(w2, x2, y2, z2);
551 QQuaternion v3(w3, x3, y3, z3);
552
553 QVERIFY((v3 - v1) == v2);
554 QVERIFY((v3 - v2) == v1);
555
556 QQuaternion v4(v3);
557 v4 -= v1;
558 QCOMPARE(v4, v2);
559
560 QCOMPARE(v4.x(), v3.x() - v1.x());
561 QCOMPARE(v4.y(), v3.y() - v1.y());
562 QCOMPARE(v4.z(), v3.z() - v1.z());
563 QCOMPARE(v4.scalar(), v3.scalar() - v1.scalar());
564
565 QQuaternion v5(v3);
566 v5 -= v2;
567 QCOMPARE(v5, v1);
568
569 QCOMPARE(v5.x(), v3.x() - v2.x());
570 QCOMPARE(v5.y(), v3.y() - v2.y());
571 QCOMPARE(v5.z(), v3.z() - v2.z());
572 QCOMPARE(v5.scalar(), v3.scalar() - v2.scalar());
573}
574
575// Test quaternion multiplication.
576void tst_QQuaternion::multiply_data()
577{
578 QTest::addColumn<float>(name: "x1");
579 QTest::addColumn<float>(name: "y1");
580 QTest::addColumn<float>(name: "z1");
581 QTest::addColumn<float>(name: "w1");
582 QTest::addColumn<float>(name: "x2");
583 QTest::addColumn<float>(name: "y2");
584 QTest::addColumn<float>(name: "z2");
585 QTest::addColumn<float>(name: "w2");
586
587 QTest::newRow(dataTag: "null")
588 << 0.0f << 0.0f << 0.0f << 0.0f
589 << 0.0f << 0.0f << 0.0f << 0.0f;
590
591 QTest::newRow(dataTag: "unitvec")
592 << 1.0f << 0.0f << 0.0f << 1.0f
593 << 0.0f << 1.0f << 0.0f << 1.0f;
594
595 QTest::newRow(dataTag: "complex")
596 << 1.0f << 2.0f << 3.0f << 7.0f
597 << 4.0f << 5.0f << 6.0f << 8.0f;
598
599 for (float w = -1.0f; w <= 1.0f; w += 0.5f)
600 for (float x = -1.0f; x <= 1.0f; x += 0.5f)
601 for (float y = -1.0f; y <= 1.0f; y += 0.5f)
602 for (float z = -1.0f; z <= 1.0f; z += 0.5f) {
603 QTest::newRow(dataTag: "exhaustive")
604 << x << y << z << w
605 << z << w << y << x;
606 }
607}
608void tst_QQuaternion::multiply()
609{
610 QFETCH(float, x1);
611 QFETCH(float, y1);
612 QFETCH(float, z1);
613 QFETCH(float, w1);
614 QFETCH(float, x2);
615 QFETCH(float, y2);
616 QFETCH(float, z2);
617 QFETCH(float, w2);
618
619 QQuaternion q1(w1, x1, y1, z1);
620 QQuaternion q2(w2, x2, y2, z2);
621
622 // Use the simple algorithm at:
623 // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q53
624 // to calculate the answer we expect to get.
625 QVector3D v1(x1, y1, z1);
626 QVector3D v2(x2, y2, z2);
627 float scalar = w1 * w2 - QVector3D::dotProduct(v1, v2);
628 QVector3D vector = w1 * v2 + w2 * v1 + QVector3D::crossProduct(v1, v2);
629 QQuaternion result(scalar, vector);
630
631 QVERIFY((q1 * q2) == result);
632}
633
634// Test multiplication by a factor for quaternions.
635void tst_QQuaternion::multiplyFactor_data()
636{
637 QTest::addColumn<float>(name: "x1");
638 QTest::addColumn<float>(name: "y1");
639 QTest::addColumn<float>(name: "z1");
640 QTest::addColumn<float>(name: "w1");
641 QTest::addColumn<float>(name: "factor");
642 QTest::addColumn<float>(name: "x2");
643 QTest::addColumn<float>(name: "y2");
644 QTest::addColumn<float>(name: "z2");
645 QTest::addColumn<float>(name: "w2");
646
647 QTest::newRow(dataTag: "null")
648 << 0.0f << 0.0f << 0.0f << 0.0f
649 << 100.0f
650 << 0.0f << 0.0f << 0.0f << 0.0f;
651
652 QTest::newRow(dataTag: "xonly")
653 << 1.0f << 0.0f << 0.0f << 0.0f
654 << 2.0f
655 << 2.0f << 0.0f << 0.0f << 0.0f;
656
657 QTest::newRow(dataTag: "yonly")
658 << 0.0f << 1.0f << 0.0f << 0.0f
659 << 2.0f
660 << 0.0f << 2.0f << 0.0f << 0.0f;
661
662 QTest::newRow(dataTag: "zonly")
663 << 0.0f << 0.0f << 1.0f << 0.0f
664 << 2.0f
665 << 0.0f << 0.0f << 2.0f << 0.0f;
666
667 QTest::newRow(dataTag: "wonly")
668 << 0.0f << 0.0f << 0.0f << 1.0f
669 << 2.0f
670 << 0.0f << 0.0f << 0.0f << 2.0f;
671
672 QTest::newRow(dataTag: "all")
673 << 1.0f << 2.0f << -3.0f << 4.0f
674 << 2.0f
675 << 2.0f << 4.0f << -6.0f << 8.0f;
676
677 QTest::newRow(dataTag: "allzero")
678 << 1.0f << 2.0f << -3.0f << 4.0f
679 << 0.0f
680 << 0.0f << 0.0f << 0.0f << 0.0f;
681}
682void tst_QQuaternion::multiplyFactor()
683{
684 QFETCH(float, x1);
685 QFETCH(float, y1);
686 QFETCH(float, z1);
687 QFETCH(float, w1);
688 QFETCH(float, factor);
689 QFETCH(float, x2);
690 QFETCH(float, y2);
691 QFETCH(float, z2);
692 QFETCH(float, w2);
693
694 QQuaternion v1(w1, x1, y1, z1);
695 QQuaternion v2(w2, x2, y2, z2);
696
697 QVERIFY((v1 * factor) == v2);
698 QVERIFY((factor * v1) == v2);
699
700 QQuaternion v3(v1);
701 v3 *= factor;
702 QCOMPARE(v3, v2);
703
704 QCOMPARE(v3.x(), v1.x() * factor);
705 QCOMPARE(v3.y(), v1.y() * factor);
706 QCOMPARE(v3.z(), v1.z() * factor);
707 QCOMPARE(v3.scalar(), v1.scalar() * factor);
708}
709
710// Test division by a factor for quaternions.
711void tst_QQuaternion::divide_data()
712{
713 // Use the same test data as the multiply test.
714 multiplyFactor_data();
715}
716void tst_QQuaternion::divide()
717{
718 QFETCH(float, x1);
719 QFETCH(float, y1);
720 QFETCH(float, z1);
721 QFETCH(float, w1);
722 QFETCH(float, factor);
723 QFETCH(float, x2);
724 QFETCH(float, y2);
725 QFETCH(float, z2);
726 QFETCH(float, w2);
727
728 QQuaternion v1(w1, x1, y1, z1);
729 QQuaternion v2(w2, x2, y2, z2);
730
731 if (factor == 0.0f)
732 return;
733
734 QVERIFY((v2 / factor) == v1);
735
736 QQuaternion v3(v2);
737 v3 /= factor;
738 QCOMPARE(v3, v1);
739
740 QCOMPARE(v3.x(), v2.x() / factor);
741 QCOMPARE(v3.y(), v2.y() / factor);
742 QCOMPARE(v3.z(), v2.z() / factor);
743 QCOMPARE(v3.scalar(), v2.scalar() / factor);
744}
745
746// Test negation for quaternions.
747void tst_QQuaternion::negate_data()
748{
749 // Use the same test data as the add test.
750 add_data();
751}
752void tst_QQuaternion::negate()
753{
754 QFETCH(float, x1);
755 QFETCH(float, y1);
756 QFETCH(float, z1);
757 QFETCH(float, w1);
758
759 QQuaternion v1(w1, x1, y1, z1);
760 QQuaternion v2(-w1, -x1, -y1, -z1);
761
762 QCOMPARE(-v1, v2);
763}
764
765// Test quaternion conjugate calculations.
766void tst_QQuaternion::conjugate_data()
767{
768 // Use the same test data as the add test.
769 add_data();
770}
771void tst_QQuaternion::conjugate()
772{
773 QFETCH(float, x1);
774 QFETCH(float, y1);
775 QFETCH(float, z1);
776 QFETCH(float, w1);
777
778 QQuaternion v1(w1, x1, y1, z1);
779 QQuaternion v2(w1, -x1, -y1, -z1);
780
781#if QT_DEPRECATED_SINCE(5, 5)
782 QCOMPARE(v1.conjugate(), v2);
783#endif
784 QCOMPARE(v1.conjugated(), v2);
785}
786
787// Test quaternion creation from an axis and an angle.
788void tst_QQuaternion::fromAxisAndAngle_data()
789{
790 QTest::addColumn<float>(name: "x1");
791 QTest::addColumn<float>(name: "y1");
792 QTest::addColumn<float>(name: "z1");
793 QTest::addColumn<float>(name: "angle");
794
795 QTest::newRow(dataTag: "null")
796 << 0.0f << 0.0f << 0.0f << 0.0f;
797
798 QTest::newRow(dataTag: "xonly")
799 << 1.0f << 0.0f << 0.0f << 90.0f;
800
801 QTest::newRow(dataTag: "yonly")
802 << 0.0f << 1.0f << 0.0f << 180.0f;
803
804 QTest::newRow(dataTag: "zonly")
805 << 0.0f << 0.0f << 1.0f << 270.0f;
806
807 QTest::newRow(dataTag: "complex")
808 << 1.0f << 2.0f << -3.0f << 45.0f;
809}
810void tst_QQuaternion::fromAxisAndAngle()
811{
812 QFETCH(float, x1);
813 QFETCH(float, y1);
814 QFETCH(float, z1);
815 QFETCH(float, angle);
816
817 // Use a straight-forward implementation of the algorithm at:
818 // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q56
819 // to calculate the answer we expect to get.
820 QVector3D vector = QVector3D(x1, y1, z1).normalized();
821 const float a = qDegreesToRadians(degrees: angle) / 2.0;
822 const float sin_a = std::sin(x: a);
823 const float cos_a = std::cos(x: a);
824 QQuaternion result(cos_a,
825 (vector.x() * sin_a),
826 (vector.y() * sin_a),
827 (vector.z() * sin_a));
828 result = result.normalized();
829
830 QQuaternion answer = QQuaternion::fromAxisAndAngle(axis: QVector3D(x1, y1, z1), angle);
831 QVERIFY(qFuzzyCompare(answer.x(), result.x()));
832 QVERIFY(qFuzzyCompare(answer.y(), result.y()));
833 QVERIFY(qFuzzyCompare(answer.z(), result.z()));
834 QVERIFY(qFuzzyCompare(answer.scalar(), result.scalar()));
835
836 {
837 QVector3D answerAxis;
838 float answerAngle;
839 answer.getAxisAndAngle(axis: &answerAxis, angle: &answerAngle);
840 QVERIFY(qFuzzyCompare(answerAxis.x(), vector.x()));
841 QVERIFY(qFuzzyCompare(answerAxis.y(), vector.y()));
842 QVERIFY(qFuzzyCompare(answerAxis.z(), vector.z()));
843 QVERIFY(qFuzzyCompare(answerAngle, angle));
844 }
845
846 answer = QQuaternion::fromAxisAndAngle(x: x1, y: y1, z: z1, angle);
847 QVERIFY(qFuzzyCompare(answer.x(), result.x()));
848 QVERIFY(qFuzzyCompare(answer.y(), result.y()));
849 QVERIFY(qFuzzyCompare(answer.z(), result.z()));
850 QVERIFY(qFuzzyCompare(answer.scalar(), result.scalar()));
851
852 {
853 float answerAxisX, answerAxisY, answerAxisZ;
854 float answerAngle;
855 answer.getAxisAndAngle(x: &answerAxisX, y: &answerAxisY, z: &answerAxisZ, angle: &answerAngle);
856 QVERIFY(qFuzzyCompare(answerAxisX, vector.x()));
857 QVERIFY(qFuzzyCompare(answerAxisY, vector.y()));
858 QVERIFY(qFuzzyCompare(answerAxisZ, vector.z()));
859 QVERIFY(qFuzzyCompare(answerAngle, angle));
860 }
861}
862
863// Test quaternion convertion to and from rotation matrix.
864void tst_QQuaternion::fromRotationMatrix_data()
865{
866 fromAxisAndAngle_data();
867}
868void tst_QQuaternion::fromRotationMatrix()
869{
870 QFETCH(float, x1);
871 QFETCH(float, y1);
872 QFETCH(float, z1);
873 QFETCH(float, angle);
874
875 QQuaternion result = QQuaternion::fromAxisAndAngle(axis: QVector3D(x1, y1, z1), angle);
876 QMatrix3x3 rot3x3 = result.toRotationMatrix();
877 QQuaternion answer = QQuaternion::fromRotationMatrix(rot3x3);
878
879 QVERIFY(qFuzzyCompare(answer, result) || qFuzzyCompare(-answer, result));
880}
881
882// Test quaternion convertion to and from orthonormal axes.
883void tst_QQuaternion::fromAxes_data()
884{
885 QTest::addColumn<float>(name: "x1");
886 QTest::addColumn<float>(name: "y1");
887 QTest::addColumn<float>(name: "z1");
888 QTest::addColumn<float>(name: "angle");
889 QTest::addColumn<QVector3D>(name: "xAxis");
890 QTest::addColumn<QVector3D>(name: "yAxis");
891 QTest::addColumn<QVector3D>(name: "zAxis");
892
893 QTest::newRow(dataTag: "null")
894 << 0.0f << 0.0f << 0.0f << 0.0f
895 << QVector3D(1, 0, 0) << QVector3D(0, 1, 0) << QVector3D(0, 0, 1);
896
897 QTest::newRow(dataTag: "xonly")
898 << 1.0f << 0.0f << 0.0f << 90.0f
899 << QVector3D(1, 0, 0) << QVector3D(0, 0, 1) << QVector3D(0, -1, 0);
900
901 QTest::newRow(dataTag: "yonly")
902 << 0.0f << 1.0f << 0.0f << 180.0f
903 << QVector3D(-1, 0, 0) << QVector3D(0, 1, 0) << QVector3D(0, 0, -1);
904
905 QTest::newRow(dataTag: "zonly")
906 << 0.0f << 0.0f << 1.0f << 270.0f
907 << QVector3D(0, -1, 0) << QVector3D(1, 0, 0) << QVector3D(0, 0, 1);
908
909 QTest::newRow(dataTag: "complex")
910 << 1.0f << 2.0f << -3.0f << 45.0f
911 << QVector3D(0.728028, -0.525105, -0.440727) << QVector3D(0.608789, 0.790791, 0.0634566) << QVector3D(0.315202, -0.314508, 0.895395);
912}
913void tst_QQuaternion::fromAxes()
914{
915 QFETCH(float, x1);
916 QFETCH(float, y1);
917 QFETCH(float, z1);
918 QFETCH(float, angle);
919 QFETCH(QVector3D, xAxis);
920 QFETCH(QVector3D, yAxis);
921 QFETCH(QVector3D, zAxis);
922
923 QQuaternion result = QQuaternion::fromAxisAndAngle(axis: QVector3D(x1, y1, z1), angle);
924
925 QVector3D axes[3];
926 result.getAxes(xAxis: &axes[0], yAxis: &axes[1], zAxis: &axes[2]);
927 QVERIFY(myFuzzyCompare(axes[0], xAxis));
928 QVERIFY(myFuzzyCompare(axes[1], yAxis));
929 QVERIFY(myFuzzyCompare(axes[2], zAxis));
930
931 QQuaternion answer = QQuaternion::fromAxes(xAxis: axes[0], yAxis: axes[1], zAxis: axes[2]);
932
933 QVERIFY(qFuzzyCompare(answer, result) || qFuzzyCompare(-answer, result));
934}
935
936// Test shortest arc quaternion.
937void tst_QQuaternion::rotationTo_data()
938{
939 QTest::addColumn<QVector3D>(name: "from");
940 QTest::addColumn<QVector3D>(name: "to");
941
942 // same
943 QTest::newRow(dataTag: "+X -> +X") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(10.0f, 0.0f, 0.0f);
944 QTest::newRow(dataTag: "-X -> -X") << QVector3D(-10.0f, 0.0f, 0.0f) << QVector3D(-10.0f, 0.0f, 0.0f);
945 QTest::newRow(dataTag: "+Y -> +Y") << QVector3D(0.0f, 10.0f, 0.0f) << QVector3D(0.0f, 10.0f, 0.0f);
946 QTest::newRow(dataTag: "-Y -> -Y") << QVector3D(0.0f, -10.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f);
947 QTest::newRow(dataTag: "+Z -> +Z") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 0.0f, 10.0f);
948 QTest::newRow(dataTag: "-Z -> -Z") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(0.0f, 0.0f, -10.0f);
949 QTest::newRow(dataTag: "+X+Y+Z -> +X+Y+Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(10.0f, 10.0f, 10.0f);
950 QTest::newRow(dataTag: "-X-Y-Z -> -X-Y-Z") << QVector3D(-10.0f, -10.0f, -10.0f) << QVector3D(-10.0f, -10.0f, -10.0f);
951
952 // arbitrary
953 QTest::newRow(dataTag: "+Z -> +X") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(10.0f, 0.0f, 0.0f);
954 QTest::newRow(dataTag: "+Z -> -X") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(-10.0f, 0.0f, 0.0f);
955 QTest::newRow(dataTag: "+Z -> +Y") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 10.0f, 0.0f);
956 QTest::newRow(dataTag: "+Z -> -Y") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, -10.0f, 0.0f);
957 QTest::newRow(dataTag: "-Z -> +X") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(10.0f, 0.0f, 0.0f);
958 QTest::newRow(dataTag: "-Z -> -X") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(-10.0f, 0.0f, 0.0f);
959 QTest::newRow(dataTag: "-Z -> +Y") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(0.0f, 10.0f, 0.0f);
960 QTest::newRow(dataTag: "-Z -> -Y") << QVector3D(0.0f, 0.0f, -10.0f) << QVector3D(0.0f, -10.0f, 0.0f);
961 QTest::newRow(dataTag: "+X -> +Y") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(0.0f, 10.0f, 0.0f);
962 QTest::newRow(dataTag: "+X -> -Y") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f);
963 QTest::newRow(dataTag: "-X -> +Y") << QVector3D(-10.0f, 0.0f, 0.0f) << QVector3D(0.0f, 10.0f, 0.0f);
964 QTest::newRow(dataTag: "-X -> -Y") << QVector3D(-10.0f, 0.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f);
965 QTest::newRow(dataTag: "+X+Y+Z -> +X-Y-Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(10.0f, -10.0f, -10.0f);
966 QTest::newRow(dataTag: "-X-Y+Z -> -X+Y-Z") << QVector3D(-10.0f, -10.0f, 10.0f) << QVector3D(-10.0f, 10.0f, -10.0f);
967 QTest::newRow(dataTag: "+X+Y+Z -> +Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(0.0f, 0.0f, 10.0f);
968
969 // collinear
970 QTest::newRow(dataTag: "+X -> -X") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(-10.0f, 0.0f, 0.0f);
971 QTest::newRow(dataTag: "+Y -> -Y") << QVector3D(0.0f, 10.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f);
972 QTest::newRow(dataTag: "+Z -> -Z") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 0.0f, -10.0f);
973 QTest::newRow(dataTag: "+X+Y+Z -> -X-Y-Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(-10.0f, -10.0f, -10.0f);
974}
975void tst_QQuaternion::rotationTo()
976{
977 QFETCH(QVector3D, from);
978 QFETCH(QVector3D, to);
979
980 QQuaternion q1 = QQuaternion::rotationTo(from, to);
981 QVERIFY(myFuzzyCompare(q1, q1.normalized()));
982 QVector3D vec1(q1 * from);
983 vec1 *= (to.length() / from.length()); // discard rotated length
984 QVERIFY(myFuzzyCompare(vec1, to));
985
986 QQuaternion q2 = QQuaternion::rotationTo(from: to, to: from);
987 QVERIFY(myFuzzyCompare(q2, q2.normalized()));
988 QVector3D vec2(q2 * to);
989 vec2 *= (from.length() / to.length()); // discard rotated length
990 QVERIFY(myFuzzyCompare(vec2, from));
991}
992
993static QByteArray testnameForAxis(const QVector3D &axis)
994{
995 QByteArray testname;
996 if (axis == QVector3D()) {
997 testname = "null";
998 } else {
999 if (axis.x()) {
1000 testname += axis.x() < 0 ? '-' : '+';
1001 testname += 'X';
1002 }
1003 if (axis.y()) {
1004 testname += axis.y() < 0 ? '-' : '+';
1005 testname += 'Y';
1006 }
1007 if (axis.z()) {
1008 testname += axis.z() < 0 ? '-' : '+';
1009 testname += 'Z';
1010 }
1011 }
1012 return testname;
1013}
1014
1015// Test quaternion convertion to and from orthonormal axes.
1016void tst_QQuaternion::fromDirection_data()
1017{
1018 QTest::addColumn<QVector3D>(name: "direction");
1019 QTest::addColumn<QVector3D>(name: "up");
1020
1021 QList<QQuaternion> orientations;
1022 orientations << QQuaternion();
1023 for (int angle = 45; angle <= 360; angle += 45) {
1024 orientations << QQuaternion::fromAxisAndAngle(axis: QVector3D(1, 0, 0), angle)
1025 << QQuaternion::fromAxisAndAngle(axis: QVector3D(0, 1, 0), angle)
1026 << QQuaternion::fromAxisAndAngle(axis: QVector3D(0, 0, 1), angle)
1027 << QQuaternion::fromAxisAndAngle(axis: QVector3D(1, 0, 0), angle)
1028 * QQuaternion::fromAxisAndAngle(axis: QVector3D(0, 1, 0), angle)
1029 * QQuaternion::fromAxisAndAngle(axis: QVector3D(0, 0, 1), angle);
1030 }
1031
1032 // othonormal up and dir
1033 foreach (const QQuaternion &q, orientations) {
1034 QVector3D xAxis, yAxis, zAxis;
1035 q.getAxes(xAxis: &xAxis, yAxis: &yAxis, zAxis: &zAxis);
1036
1037 QTest::newRow(dataTag: "dir: " + testnameForAxis(axis: zAxis) + ", up: " + testnameForAxis(axis: yAxis))
1038 << zAxis * 10.0f << yAxis * 10.0f;
1039 }
1040
1041 // collinear up and dir
1042 QTest::newRow(dataTag: "dir: +X, up: +X") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(10.0f, 0.0f, 0.0f);
1043 QTest::newRow(dataTag: "dir: +X, up: -X") << QVector3D(10.0f, 0.0f, 0.0f) << QVector3D(-10.0f, 0.0f, 0.0f);
1044 QTest::newRow(dataTag: "dir: +Y, up: +Y") << QVector3D(0.0f, 10.0f, 0.0f) << QVector3D(0.0f, 10.0f, 0.0f);
1045 QTest::newRow(dataTag: "dir: +Y, up: -Y") << QVector3D(0.0f, 10.0f, 0.0f) << QVector3D(0.0f, -10.0f, 0.0f);
1046 QTest::newRow(dataTag: "dir: +Z, up: +Z") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 0.0f, 10.0f);
1047 QTest::newRow(dataTag: "dir: +Z, up: -Z") << QVector3D(0.0f, 0.0f, 10.0f) << QVector3D(0.0f, 0.0f, -10.0f);
1048 QTest::newRow(dataTag: "dir: +X+Y+Z, up: +X+Y+Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(10.0f, 10.0f, 10.0f);
1049 QTest::newRow(dataTag: "dir: +X+Y+Z, up: -X-Y-Z") << QVector3D(10.0f, 10.0f, 10.0f) << QVector3D(-10.0f, -10.0f, -10.0f);
1050
1051 // invalid up
1052 foreach (const QQuaternion &q, orientations) {
1053 QVector3D xAxis, yAxis, zAxis;
1054 q.getAxes(xAxis: &xAxis, yAxis: &yAxis, zAxis: &zAxis);
1055
1056 QTest::newRow(dataTag: "dir: " + testnameForAxis(axis: zAxis) + ", up: null")
1057 << zAxis * 10.0f << QVector3D();
1058 }
1059}
1060void tst_QQuaternion::fromDirection()
1061{
1062 QFETCH(QVector3D, direction);
1063 QFETCH(QVector3D, up);
1064
1065 QVector3D expextedZ(direction != QVector3D() ? direction.normalized() : QVector3D(0, 0, 1));
1066 QVector3D expextedY(up.normalized());
1067
1068 QQuaternion result = QQuaternion::fromDirection(direction, up);
1069 QVERIFY(myFuzzyCompare(result, result.normalized()));
1070
1071 QVector3D xAxis, yAxis, zAxis;
1072 result.getAxes(xAxis: &xAxis, yAxis: &yAxis, zAxis: &zAxis);
1073
1074 QVERIFY(myFuzzyCompare(zAxis, expextedZ));
1075
1076 if (!qFuzzyIsNull(f: QVector3D::crossProduct(v1: expextedZ, v2: expextedY).lengthSquared())) {
1077 QVector3D expextedX(QVector3D::crossProduct(v1: expextedY, v2: expextedZ));
1078
1079 QVERIFY(myFuzzyCompare(yAxis, expextedY));
1080 QVERIFY(myFuzzyCompare(xAxis, expextedX));
1081 }
1082}
1083
1084// Test quaternion creation from an axis and an angle.
1085void tst_QQuaternion::fromEulerAngles_data()
1086{
1087 QTest::addColumn<float>(name: "pitch");
1088 QTest::addColumn<float>(name: "yaw");
1089 QTest::addColumn<float>(name: "roll");
1090
1091 QTest::addColumn<QQuaternion>(name: "quaternion");
1092
1093 QTest::newRow(dataTag: "null")
1094 << 0.0f << 0.0f << 0.0f << QQuaternion(1.0f, 0.0f, 0.0f, 0.0f);
1095
1096 QTest::newRow(dataTag: "xonly")
1097 << 90.0f << 0.0f << 0.0f << QQuaternion(0.707107f, 0.707107f, 0.0f, 0.0f);
1098
1099 QTest::newRow(dataTag: "yonly")
1100 << 0.0f << 180.0f << 0.0f << QQuaternion(0.0f, 0.0f, 1.0f, 0.0f);
1101
1102 QTest::newRow(dataTag: "zonly")
1103 << 0.0f << 0.0f << 270.0f << QQuaternion(-0.707107f, 0.0f, 0.0f, 0.707107f);
1104
1105 QTest::newRow(dataTag: "x+z")
1106 << 30.0f << 0.0f << 45.0f << QQuaternion(0.892399f, 0.239118f, -0.099046f, 0.369644f);
1107
1108 QTest::newRow(dataTag: "x+y")
1109 << 30.0f << 90.0f << 0.0f << QQuaternion(0.683013f, 0.183013f, 0.683013f, -0.183013f);
1110
1111 QTest::newRow(dataTag: "y+z")
1112 << 0.0f << 45.0f << 30.0f << QQuaternion(0.892399f, 0.099046f, 0.369644f, 0.239118f);
1113
1114 QTest::newRow(dataTag: "complex")
1115 << 30.0f << 240.0f << -45.0f << QQuaternion(-0.531976f, -0.43968f, 0.723317f, -0.02226f);
1116}
1117void tst_QQuaternion::fromEulerAngles()
1118{
1119 QFETCH(float, pitch);
1120 QFETCH(float, yaw);
1121 QFETCH(float, roll);
1122 QFETCH(QQuaternion, quaternion);
1123
1124 // Use a straight-forward implementation of the algorithm at:
1125 // http://www.j3d.org/matrix_faq/matrfaq_latest.html#Q60
1126 // to calculate the answer we expect to get.
1127 QQuaternion qx = QQuaternion::fromAxisAndAngle(axis: QVector3D(1, 0, 0), angle: pitch);
1128 QQuaternion qy = QQuaternion::fromAxisAndAngle(axis: QVector3D(0, 1, 0), angle: yaw);
1129 QQuaternion qz = QQuaternion::fromAxisAndAngle(axis: QVector3D(0, 0, 1), angle: roll);
1130 QQuaternion result = qy * (qx * qz);
1131 QQuaternion answer = QQuaternion::fromEulerAngles(eulerAngles: QVector3D(pitch, yaw, roll));
1132
1133 QVERIFY(myFuzzyCompare(answer.x(), result.x()));
1134 QVERIFY(myFuzzyCompare(answer.y(), result.y()));
1135 QVERIFY(myFuzzyCompare(answer.z(), result.z()));
1136 QVERIFY(myFuzzyCompare(answer.scalar(), result.scalar()));
1137
1138 // quaternion should be the same as the result
1139 QVERIFY(myFuzzyCompare(answer.x(), quaternion.x()));
1140 QVERIFY(myFuzzyCompare(answer.y(), quaternion.y()));
1141 QVERIFY(myFuzzyCompare(answer.z(), quaternion.z()));
1142 QVERIFY(myFuzzyCompare(answer.scalar(), quaternion.scalar()));
1143
1144 {
1145 QVector3D answerEulerAngles = answer.toEulerAngles();
1146 QVERIFY(myFuzzyCompareDegrees(answerEulerAngles.x(), pitch));
1147 QVERIFY(myFuzzyCompareDegrees(answerEulerAngles.y(), yaw));
1148 QVERIFY(myFuzzyCompareDegrees(answerEulerAngles.z(), roll));
1149
1150 QVector3D quaternionEulerAngles = quaternion.toEulerAngles();
1151 QVERIFY(myFuzzyCompareDegrees(quaternionEulerAngles.x(), pitch));
1152 QVERIFY(myFuzzyCompareDegrees(quaternionEulerAngles.y(), yaw));
1153 QVERIFY(myFuzzyCompareDegrees(quaternionEulerAngles.z(), roll));
1154 }
1155
1156 answer = QQuaternion::fromEulerAngles(pitch, yaw, roll);
1157 QVERIFY(myFuzzyCompare(answer.x(), result.x()));
1158 QVERIFY(myFuzzyCompare(answer.y(), result.y()));
1159 QVERIFY(myFuzzyCompare(answer.z(), result.z()));
1160 QVERIFY(myFuzzyCompare(answer.scalar(), result.scalar()));
1161
1162 {
1163 float answerPitch, answerYaw, answerRoll;
1164 answer.getEulerAngles(pitch: &answerPitch, yaw: &answerYaw, roll: &answerRoll);
1165 QVERIFY(myFuzzyCompareDegrees(answerPitch, pitch));
1166 QVERIFY(myFuzzyCompareDegrees(answerYaw, yaw));
1167 QVERIFY(myFuzzyCompareDegrees(answerRoll, roll));
1168
1169 float quaternionPitch, quaternionYaw, quaternionRoll;
1170 quaternion.getEulerAngles(pitch: &quaternionPitch, yaw: &quaternionYaw, roll: &quaternionRoll);
1171 QVERIFY(myFuzzyCompareDegrees(quaternionPitch, pitch));
1172 QVERIFY(myFuzzyCompareDegrees(quaternionYaw, yaw));
1173 QVERIFY(myFuzzyCompareDegrees(quaternionRoll, roll));
1174 }
1175}
1176
1177// Test spherical interpolation of quaternions.
1178void tst_QQuaternion::slerp_data()
1179{
1180 QTest::addColumn<float>(name: "x1");
1181 QTest::addColumn<float>(name: "y1");
1182 QTest::addColumn<float>(name: "z1");
1183 QTest::addColumn<float>(name: "angle1");
1184 QTest::addColumn<float>(name: "x2");
1185 QTest::addColumn<float>(name: "y2");
1186 QTest::addColumn<float>(name: "z2");
1187 QTest::addColumn<float>(name: "angle2");
1188 QTest::addColumn<float>(name: "t");
1189 QTest::addColumn<float>(name: "x3");
1190 QTest::addColumn<float>(name: "y3");
1191 QTest::addColumn<float>(name: "z3");
1192 QTest::addColumn<float>(name: "angle3");
1193
1194 QTest::newRow(dataTag: "first")
1195 << 1.0f << 2.0f << -3.0f << 90.0f
1196 << 1.0f << 2.0f << -3.0f << 180.0f
1197 << 0.0f
1198 << 1.0f << 2.0f << -3.0f << 90.0f;
1199 QTest::newRow(dataTag: "first2")
1200 << 1.0f << 2.0f << -3.0f << 90.0f
1201 << 1.0f << 2.0f << -3.0f << 180.0f
1202 << -0.5f
1203 << 1.0f << 2.0f << -3.0f << 90.0f;
1204 QTest::newRow(dataTag: "second")
1205 << 1.0f << 2.0f << -3.0f << 90.0f
1206 << 1.0f << 2.0f << -3.0f << 180.0f
1207 << 1.0f
1208 << 1.0f << 2.0f << -3.0f << 180.0f;
1209 QTest::newRow(dataTag: "second2")
1210 << 1.0f << 2.0f << -3.0f << 90.0f
1211 << 1.0f << 2.0f << -3.0f << 180.0f
1212 << 1.5f
1213 << 1.0f << 2.0f << -3.0f << 180.0f;
1214 QTest::newRow(dataTag: "middle")
1215 << 1.0f << 2.0f << -3.0f << 90.0f
1216 << 1.0f << 2.0f << -3.0f << 180.0f
1217 << 0.5f
1218 << 1.0f << 2.0f << -3.0f << 135.0f;
1219 QTest::newRow(dataTag: "wide angle")
1220 << 1.0f << 2.0f << -3.0f << 0.0f
1221 << 1.0f << 2.0f << -3.0f << 270.0f
1222 << 0.5f
1223 << 1.0f << 2.0f << -3.0f << -45.0f;
1224}
1225void tst_QQuaternion::slerp()
1226{
1227 QFETCH(float, x1);
1228 QFETCH(float, y1);
1229 QFETCH(float, z1);
1230 QFETCH(float, angle1);
1231 QFETCH(float, x2);
1232 QFETCH(float, y2);
1233 QFETCH(float, z2);
1234 QFETCH(float, angle2);
1235 QFETCH(float, t);
1236 QFETCH(float, x3);
1237 QFETCH(float, y3);
1238 QFETCH(float, z3);
1239 QFETCH(float, angle3);
1240
1241 QQuaternion q1 = QQuaternion::fromAxisAndAngle(x: x1, y: y1, z: z1, angle: angle1);
1242 QQuaternion q2 = QQuaternion::fromAxisAndAngle(x: x2, y: y2, z: z2, angle: angle2);
1243 QQuaternion q3 = QQuaternion::fromAxisAndAngle(x: x3, y: y3, z: z3, angle: angle3);
1244
1245 QQuaternion result = QQuaternion::slerp(q1, q2, t);
1246
1247 QVERIFY(qFuzzyCompare(result.x(), q3.x()));
1248 QVERIFY(qFuzzyCompare(result.y(), q3.y()));
1249 QVERIFY(qFuzzyCompare(result.z(), q3.z()));
1250 QVERIFY(qFuzzyCompare(result.scalar(), q3.scalar()));
1251}
1252
1253// Test normalized linear interpolation of quaternions.
1254void tst_QQuaternion::nlerp_data()
1255{
1256 slerp_data();
1257}
1258void tst_QQuaternion::nlerp()
1259{
1260 QFETCH(float, x1);
1261 QFETCH(float, y1);
1262 QFETCH(float, z1);
1263 QFETCH(float, angle1);
1264 QFETCH(float, x2);
1265 QFETCH(float, y2);
1266 QFETCH(float, z2);
1267 QFETCH(float, angle2);
1268 QFETCH(float, t);
1269
1270 QQuaternion q1 = QQuaternion::fromAxisAndAngle(x: x1, y: y1, z: z1, angle: angle1);
1271 QQuaternion q2 = QQuaternion::fromAxisAndAngle(x: x2, y: y2, z: z2, angle: angle2);
1272
1273 QQuaternion result = QQuaternion::nlerp(q1, q2, t);
1274
1275 float resultx, resulty, resultz, resultscalar;
1276 if (t <= 0.0f) {
1277 resultx = q1.x();
1278 resulty = q1.y();
1279 resultz = q1.z();
1280 resultscalar = q1.scalar();
1281 } else if (t >= 1.0f) {
1282 resultx = q2.x();
1283 resulty = q2.y();
1284 resultz = q2.z();
1285 resultscalar = q2.scalar();
1286 } else if (qAbs(t: angle1 - angle2) <= 180.f) {
1287 resultx = q1.x() * (1 - t) + q2.x() * t;
1288 resulty = q1.y() * (1 - t) + q2.y() * t;
1289 resultz = q1.z() * (1 - t) + q2.z() * t;
1290 resultscalar = q1.scalar() * (1 - t) + q2.scalar() * t;
1291 } else {
1292 // Angle greater than 180 degrees: negate q2.
1293 resultx = q1.x() * (1 - t) - q2.x() * t;
1294 resulty = q1.y() * (1 - t) - q2.y() * t;
1295 resultz = q1.z() * (1 - t) - q2.z() * t;
1296 resultscalar = q1.scalar() * (1 - t) - q2.scalar() * t;
1297 }
1298
1299 QQuaternion q3 = QQuaternion(resultscalar, resultx, resulty, resultz).normalized();
1300
1301 QVERIFY(qFuzzyCompare(result.x(), q3.x()));
1302 QVERIFY(qFuzzyCompare(result.y(), q3.y()));
1303 QVERIFY(qFuzzyCompare(result.z(), q3.z()));
1304 QVERIFY(qFuzzyCompare(result.scalar(), q3.scalar()));
1305}
1306
1307class tst_QQuaternionProperties : public QObject
1308{
1309 Q_OBJECT
1310 Q_PROPERTY(QQuaternion quaternion READ quaternion WRITE setQuaternion)
1311public:
1312 tst_QQuaternionProperties(QObject *parent = 0) : QObject(parent) {}
1313
1314 QQuaternion quaternion() const { return q; }
1315 void setQuaternion(const QQuaternion& value) { q = value; }
1316
1317private:
1318 QQuaternion q;
1319};
1320
1321// Test getting and setting quaternion properties via the metaobject system.
1322void tst_QQuaternion::properties()
1323{
1324 tst_QQuaternionProperties obj;
1325
1326 obj.setQuaternion(QQuaternion(6.0f, 7.0f, 8.0f, 9.0f));
1327
1328 QQuaternion q = qvariant_cast<QQuaternion>(v: obj.property(name: "quaternion"));
1329 QCOMPARE(q.scalar(), 6.0f);
1330 QCOMPARE(q.x(), 7.0f);
1331 QCOMPARE(q.y(), 8.0f);
1332 QCOMPARE(q.z(), 9.0f);
1333
1334 obj.setProperty(name: "quaternion",
1335 value: QVariant::fromValue(value: QQuaternion(-6.0f, -7.0f, -8.0f, -9.0f)));
1336
1337 q = qvariant_cast<QQuaternion>(v: obj.property(name: "quaternion"));
1338 QCOMPARE(q.scalar(), -6.0f);
1339 QCOMPARE(q.x(), -7.0f);
1340 QCOMPARE(q.y(), -8.0f);
1341 QCOMPARE(q.z(), -9.0f);
1342}
1343
1344void tst_QQuaternion::metaTypes()
1345{
1346 QCOMPARE(QMetaType::type("QQuaternion"), int(QMetaType::QQuaternion));
1347
1348 QCOMPARE(QByteArray(QMetaType::typeName(QMetaType::QQuaternion)),
1349 QByteArray("QQuaternion"));
1350
1351 QVERIFY(QMetaType::isRegistered(QMetaType::QQuaternion));
1352
1353 QCOMPARE(qMetaTypeId<QQuaternion>(), int(QMetaType::QQuaternion));
1354}
1355
1356QTEST_APPLESS_MAIN(tst_QQuaternion)
1357
1358#include "tst_qquaternion.moc"
1359

source code of qtbase/tests/auto/gui/math3d/qquaternion/tst_qquaternion.cpp