1 | // Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qtransform.h" |
5 | #include "qtransform_p.h" |
6 | |
7 | #include <Qt3DCore/private/qmath3d_p.h> |
8 | |
9 | QT_BEGIN_NAMESPACE |
10 | |
11 | namespace Qt3DCore { |
12 | |
13 | QTransformPrivate::QTransformPrivate() |
14 | : QComponentPrivate() |
15 | , m_rotation() |
16 | , m_scale(1.0f, 1.0f, 1.0f) |
17 | , m_translation() |
18 | , m_eulerRotationAngles() |
19 | , m_matrixDirty(false) |
20 | { |
21 | m_shareable = false; |
22 | } |
23 | |
24 | QTransformPrivate::~QTransformPrivate() |
25 | { |
26 | } |
27 | |
28 | /*! |
29 | \qmltype Transform |
30 | \inqmlmodule Qt3D.Core |
31 | \inherits Component3D |
32 | \instantiates Qt3DCore::QTransform |
33 | \since 5.6 |
34 | \brief Used to perform transforms on meshes. |
35 | |
36 | The Transform component is not shareable between multiple Entity's. |
37 | The transformation is held as vector3d scale, quaternion rotation and |
38 | vector3d translation components. The transformations are applied to the |
39 | mesh in that order. When Transform::matrix property is set, it is decomposed |
40 | to these transform components and corresponding transform signals are emitted. |
41 | |
42 | Several helper functions are provided to set up the Transform; |
43 | fromAxisAndAngle and fromAxesAndAngles can be used to set the rotation around |
44 | specific axes, fromEulerAngles can be used to set the rotation based on euler |
45 | angles and rotateAround can be used to rotate the object around specific point |
46 | relative to local origin. |
47 | */ |
48 | |
49 | /*! |
50 | \qmlproperty matrix4x4 Transform::matrix |
51 | |
52 | Holds the matrix4x4 of the transform. |
53 | \note When the matrix property is set, it is decomposed to translation, rotation and scale components. |
54 | */ |
55 | |
56 | /*! |
57 | \qmlproperty real Transform::rotationX |
58 | |
59 | Holds the x rotation of the transform as Euler angle. |
60 | */ |
61 | |
62 | /*! |
63 | \qmlproperty real Transform::rotationY |
64 | |
65 | Holds the y rotation of the transform as Euler angle. |
66 | */ |
67 | |
68 | /*! |
69 | \qmlproperty real Transform::rotationZ |
70 | |
71 | Holds the z rotation of the transform as Euler angle. |
72 | */ |
73 | |
74 | /*! |
75 | \qmlproperty vector3d Transform::scale3D |
76 | |
77 | Holds the scale of the transform as vector3d. |
78 | */ |
79 | |
80 | /*! |
81 | \qmlproperty real Transform::scale |
82 | |
83 | Holds the uniform scale of the transform. If the scale has been set with scale3D, holds |
84 | the x value only. |
85 | */ |
86 | |
87 | /*! |
88 | \qmlproperty quaternion Transform::rotation |
89 | |
90 | Holds the rotation of the transform as quaternion. |
91 | */ |
92 | |
93 | /*! |
94 | \qmlproperty vector3d Transform::translation |
95 | |
96 | Holds the translation of the transform as vector3d. |
97 | */ |
98 | |
99 | /*! |
100 | \qmlproperty matrix4x4 QTransform::worldMatrix |
101 | |
102 | Holds the world transformation matrix for the transform. This assumes the |
103 | Transform component is being referenced by an Entity. This makes it more |
104 | convenient to identify when an Entity part of a subtree has been |
105 | transformed in the world even though its local transformation might not |
106 | have changed. |
107 | |
108 | \since 5.14 |
109 | */ |
110 | |
111 | /*! |
112 | \qmlmethod quaternion Transform::fromAxisAndAngle(vector3d axis, real angle) |
113 | Creates a quaternion from \a axis and \a angle. |
114 | Returns the resulting quaternion. |
115 | */ |
116 | |
117 | /*! |
118 | \qmlmethod quaternion Transform::fromAxisAndAngle(real x, real y, real z, real angle) |
119 | Creates a quaternion from \a x, \a y, \a z, and \a angle. |
120 | Returns the resulting quaternion. |
121 | */ |
122 | |
123 | /*! |
124 | \qmlmethod quaternion Transform::fromAxesAndAngles(vector3d axis1, real angle1, |
125 | vector3d axis2, real angle2) |
126 | Creates a quaternion from \a axis1, \a angle1, \a axis2, and \a angle2. |
127 | Returns the resulting quaternion. |
128 | */ |
129 | |
130 | /*! |
131 | \qmlmethod quaternion Transform::fromAxesAndAngles(vector3d axis1, real angle1, |
132 | vector3d axis2, real angle2, |
133 | vector3d axis3, real angle3) |
134 | Creates a quaternion from \a axis1, \a angle1, \a axis2, \a angle2, \a axis3, and \a angle3. |
135 | Returns the resulting quaternion. |
136 | */ |
137 | |
138 | /*! |
139 | \qmlmethod quaternion Transform::fromEulerAngles(vector3d eulerAngles) |
140 | Creates a quaternion from \a eulerAngles. |
141 | Returns the resulting quaternion. |
142 | */ |
143 | |
144 | /*! |
145 | \qmlmethod quaternion Transform::fromEulerAngles(real pitch, real yaw, real roll) |
146 | Creates a quaternion from \a pitch, \a yaw, and \a roll. |
147 | Returns the resulting quaternion. |
148 | */ |
149 | |
150 | /*! |
151 | \qmlmethod matrix4x4 Transform::rotateAround(vector3d point, real angle, vector3d axis) |
152 | Creates a rotation matrix from \a axis and \a angle around \a point relative to local origin. |
153 | Returns the resulting matrix4x4. |
154 | */ |
155 | |
156 | /*! |
157 | \class Qt3DCore::QTransform |
158 | \inmodule Qt3DCore |
159 | \inherits Qt3DCore::QComponent |
160 | \since 5.6 |
161 | \brief Used to perform transforms on meshes. |
162 | |
163 | The QTransform component is not shareable between multiple QEntity's. |
164 | The transformation is held as QVector3D scale, QQuaternion rotation and |
165 | QVector3D translation components. The transformations are applied to the |
166 | mesh in that order. When QTransform::matrix property is set, it is decomposed |
167 | to these transform components and corresponding signals are emitted. |
168 | |
169 | Several helper functions are provided to set up the QTransform; |
170 | fromAxisAndAngle and fromAxesAndAngles can be used to set the rotation around |
171 | specific axes, fromEulerAngles can be used to set the rotation based on euler |
172 | angles and rotateAround can be used to rotate the object around specific point |
173 | relative to local origin. |
174 | */ |
175 | |
176 | /*! |
177 | Constructs a new QTransform with \a parent. |
178 | */ |
179 | QTransform::QTransform(QNode *parent) |
180 | : QComponent(*new QTransformPrivate, parent) |
181 | { |
182 | } |
183 | |
184 | /*! |
185 | \internal |
186 | */ |
187 | QTransform::~QTransform() |
188 | { |
189 | } |
190 | |
191 | /*! |
192 | \internal |
193 | */ |
194 | QTransform::QTransform(QTransformPrivate &dd, QNode *parent) |
195 | : QComponent(dd, parent) |
196 | { |
197 | } |
198 | |
199 | void QTransformPrivate::setWorldMatrix(const QMatrix4x4 &worldMatrix) |
200 | { |
201 | Q_Q(QTransform); |
202 | if (m_worldMatrix == worldMatrix) |
203 | return; |
204 | const bool blocked = q->blockNotifications(block: true); |
205 | m_worldMatrix = worldMatrix; |
206 | emit q->worldMatrixChanged(worldMatrix); |
207 | q->blockNotifications(block: blocked); |
208 | } |
209 | |
210 | void QTransformPrivate::update() |
211 | { |
212 | if (!m_blockNotifications) |
213 | m_dirty = true; |
214 | markDirty(changes: QScene::TransformDirty); |
215 | QNodePrivate::update(); |
216 | } |
217 | |
218 | void QTransform::setMatrix(const QMatrix4x4 &m) |
219 | { |
220 | Q_D(QTransform); |
221 | if (m != matrix()) { |
222 | d->m_matrix = m; |
223 | d->m_matrixDirty = false; |
224 | |
225 | QVector3D s; |
226 | QVector3D t; |
227 | QQuaternion r; |
228 | decomposeQMatrix4x4(m, position&: t, orientation&: r, scale&: s); |
229 | d->m_scale = s; |
230 | d->m_rotation = r; |
231 | d->m_translation = t; |
232 | d->m_eulerRotationAngles = d->m_rotation.toEulerAngles(); |
233 | emit scale3DChanged(scale: s); |
234 | emit rotationChanged(rotation: r); |
235 | emit translationChanged(translation: t); |
236 | const bool wasBlocked = blockNotifications(block: true); |
237 | emit matrixChanged(); |
238 | emit scaleChanged(scale: d->m_scale.x()); |
239 | emit rotationXChanged(rotationX: d->m_eulerRotationAngles.x()); |
240 | emit rotationYChanged(rotationY: d->m_eulerRotationAngles.y()); |
241 | emit rotationZChanged(rotationZ: d->m_eulerRotationAngles.z()); |
242 | blockNotifications(block: wasBlocked); |
243 | } |
244 | } |
245 | |
246 | void QTransform::setRotationX(float rotationX) |
247 | { |
248 | Q_D(QTransform); |
249 | |
250 | if (d->m_eulerRotationAngles.x() == rotationX) |
251 | return; |
252 | |
253 | d->m_eulerRotationAngles.setX(rotationX); |
254 | QQuaternion rotation = QQuaternion::fromEulerAngles(eulerAngles: d->m_eulerRotationAngles); |
255 | if (rotation != d->m_rotation) { |
256 | d->m_rotation = rotation; |
257 | d->m_matrixDirty = true; |
258 | emit rotationChanged(rotation); |
259 | } |
260 | |
261 | const bool wasBlocked = blockNotifications(block: true); |
262 | emit rotationXChanged(rotationX); |
263 | emit matrixChanged(); |
264 | blockNotifications(block: wasBlocked); |
265 | } |
266 | |
267 | void QTransform::setRotationY(float rotationY) |
268 | { |
269 | Q_D(QTransform); |
270 | |
271 | if (d->m_eulerRotationAngles.y() == rotationY) |
272 | return; |
273 | |
274 | d->m_eulerRotationAngles.setY(rotationY); |
275 | QQuaternion rotation = QQuaternion::fromEulerAngles(eulerAngles: d->m_eulerRotationAngles); |
276 | if (rotation != d->m_rotation) { |
277 | d->m_rotation = rotation; |
278 | d->m_matrixDirty = true; |
279 | emit rotationChanged(rotation); |
280 | } |
281 | |
282 | const bool wasBlocked = blockNotifications(block: true); |
283 | emit rotationYChanged(rotationY); |
284 | emit matrixChanged(); |
285 | blockNotifications(block: wasBlocked); |
286 | } |
287 | |
288 | void QTransform::setRotationZ(float rotationZ) |
289 | { |
290 | Q_D(QTransform); |
291 | if (d->m_eulerRotationAngles.z() == rotationZ) |
292 | return; |
293 | |
294 | d->m_eulerRotationAngles.setZ(rotationZ); |
295 | QQuaternion rotation = QQuaternion::fromEulerAngles(eulerAngles: d->m_eulerRotationAngles); |
296 | if (rotation != d->m_rotation) { |
297 | d->m_rotation = rotation; |
298 | d->m_matrixDirty = true; |
299 | emit rotationChanged(rotation); |
300 | } |
301 | |
302 | const bool wasBlocked = blockNotifications(block: true); |
303 | emit rotationZChanged(rotationZ); |
304 | emit matrixChanged(); |
305 | blockNotifications(block: wasBlocked); |
306 | } |
307 | |
308 | /*! |
309 | \property Qt3DCore::QTransform::matrix |
310 | |
311 | Holds the QMatrix4x4 of the transform. |
312 | \note When the matrix property is set, it is decomposed to translation, rotation and scale components. |
313 | */ |
314 | QMatrix4x4 QTransform::matrix() const |
315 | { |
316 | Q_D(const QTransform); |
317 | if (d->m_matrixDirty) { |
318 | composeQMatrix4x4(position: d->m_translation, orientation: d->m_rotation, scale: d->m_scale, m&: d->m_matrix); |
319 | d->m_matrixDirty = false; |
320 | } |
321 | return d->m_matrix; |
322 | } |
323 | |
324 | /*! |
325 | \property QTransform::worldMatrix |
326 | |
327 | Holds the world transformation matrix for the transform. This assumes the |
328 | QTransform component is being referenced by a QEntity. This makes it more |
329 | convenient to identify when a QEntity part of a subtree has been |
330 | transformed in the world even though its local transformation might not |
331 | have changed. |
332 | |
333 | \since 5.14 |
334 | */ |
335 | |
336 | /*! |
337 | Returns the world transformation matrix associated to the QTransform when |
338 | referenced by a QEntity which may be part of a QEntity hierarchy. |
339 | |
340 | \since 5.14 |
341 | */ |
342 | QMatrix4x4 QTransform::worldMatrix() const |
343 | { |
344 | Q_D(const QTransform); |
345 | return d->m_worldMatrix; |
346 | } |
347 | |
348 | /*! |
349 | \property Qt3DCore::QTransform::rotationX |
350 | |
351 | Holds the x rotation of the transform as Euler angle. |
352 | */ |
353 | float QTransform::rotationX() const |
354 | { |
355 | Q_D(const QTransform); |
356 | return d->m_eulerRotationAngles.x(); |
357 | } |
358 | |
359 | /*! |
360 | \property Qt3DCore::QTransform::rotationY |
361 | |
362 | Holds the y rotation of the transform as Euler angle. |
363 | */ |
364 | float QTransform::rotationY() const |
365 | { |
366 | Q_D(const QTransform); |
367 | return d->m_eulerRotationAngles.y(); |
368 | } |
369 | |
370 | /*! |
371 | \property Qt3DCore::QTransform::rotationZ |
372 | |
373 | Holds the z rotation of the transform as Euler angle. |
374 | */ |
375 | float QTransform::rotationZ() const |
376 | { |
377 | Q_D(const QTransform); |
378 | return d->m_eulerRotationAngles.z(); |
379 | } |
380 | |
381 | void QTransform::setScale3D(const QVector3D &scale) |
382 | { |
383 | Q_D(QTransform); |
384 | if (scale != d->m_scale) { |
385 | d->m_scale = scale; |
386 | d->m_matrixDirty = true; |
387 | emit scale3DChanged(scale); |
388 | |
389 | const bool wasBlocked = blockNotifications(block: true); |
390 | emit matrixChanged(); |
391 | blockNotifications(block: wasBlocked); |
392 | } |
393 | } |
394 | |
395 | /*! |
396 | \property Qt3DCore::QTransform::scale3D |
397 | |
398 | Holds the scale of the transform as QVector3D. |
399 | */ |
400 | QVector3D QTransform::scale3D() const |
401 | { |
402 | Q_D(const QTransform); |
403 | return d->m_scale; |
404 | } |
405 | |
406 | void QTransform::setScale(float scale) |
407 | { |
408 | Q_D(QTransform); |
409 | if (scale != d->m_scale.x()) { |
410 | setScale3D(QVector3D(scale, scale, scale)); |
411 | |
412 | const bool wasBlocked = blockNotifications(block: true); |
413 | emit scaleChanged(scale); |
414 | blockNotifications(block: wasBlocked); |
415 | } |
416 | } |
417 | |
418 | /*! |
419 | \property Qt3DCore::QTransform::scale |
420 | |
421 | Holds the uniform scale of the transform. If the scale has been set with setScale3D, holds |
422 | the x value only. |
423 | */ |
424 | float QTransform::scale() const |
425 | { |
426 | Q_D(const QTransform); |
427 | return d->m_scale.x(); |
428 | } |
429 | |
430 | void QTransform::setRotation(const QQuaternion &rotation) |
431 | { |
432 | Q_D(QTransform); |
433 | if (rotation != d->m_rotation) { |
434 | d->m_rotation = rotation; |
435 | const QVector3D oldRotation = d->m_eulerRotationAngles; |
436 | d->m_eulerRotationAngles = d->m_rotation.toEulerAngles(); |
437 | d->m_matrixDirty = true; |
438 | emit rotationChanged(rotation); |
439 | |
440 | const bool wasBlocked = blockNotifications(block: true); |
441 | emit matrixChanged(); |
442 | if (d->m_eulerRotationAngles.x() != oldRotation.x()) |
443 | emit rotationXChanged(rotationX: d->m_eulerRotationAngles.x()); |
444 | if (d->m_eulerRotationAngles.y() != oldRotation.y()) |
445 | emit rotationYChanged(rotationY: d->m_eulerRotationAngles.y()); |
446 | if (d->m_eulerRotationAngles.z() != oldRotation.z()) |
447 | emit rotationZChanged(rotationZ: d->m_eulerRotationAngles.z()); |
448 | blockNotifications(block: wasBlocked); |
449 | } |
450 | } |
451 | |
452 | /*! |
453 | \property Qt3DCore::QTransform::rotation |
454 | |
455 | Holds the rotation of the transform as QQuaternion. |
456 | */ |
457 | QQuaternion QTransform::rotation() const |
458 | { |
459 | Q_D(const QTransform); |
460 | return d->m_rotation; |
461 | } |
462 | |
463 | void QTransform::setTranslation(const QVector3D &translation) |
464 | { |
465 | Q_D(QTransform); |
466 | if (translation != d->m_translation) { |
467 | d->m_translation = translation; |
468 | d->m_matrixDirty = true; |
469 | emit translationChanged(translation); |
470 | |
471 | const bool wasBlocked = blockNotifications(block: true); |
472 | emit matrixChanged(); |
473 | blockNotifications(block: wasBlocked); |
474 | } |
475 | } |
476 | |
477 | /*! |
478 | \property Qt3DCore::QTransform::translation |
479 | |
480 | Holds the translation of the transform as QVector3D. |
481 | */ |
482 | QVector3D QTransform::translation() const |
483 | { |
484 | Q_D(const QTransform); |
485 | return d->m_translation; |
486 | } |
487 | |
488 | /*! |
489 | Creates a QQuaternion from \a axis and \a angle. |
490 | Returns the resulting QQuaternion. |
491 | */ |
492 | QQuaternion QTransform::fromAxisAndAngle(const QVector3D &axis, float angle) |
493 | { |
494 | return QQuaternion::fromAxisAndAngle(axis, angle); |
495 | } |
496 | |
497 | /*! |
498 | Creates a QQuaternion from \a x, \a y, \a z, and \a angle. |
499 | Returns the resulting QQuaternion. |
500 | */ |
501 | QQuaternion QTransform::fromAxisAndAngle(float x, float y, float z, float angle) |
502 | { |
503 | return QQuaternion::fromAxisAndAngle(x, y, z, angle); |
504 | } |
505 | |
506 | /*! |
507 | Creates a QQuaternion from \a axis1, \a angle1, \a axis2, and \a angle2. |
508 | Returns the resulting QQuaternion. |
509 | */ |
510 | QQuaternion QTransform::fromAxesAndAngles(const QVector3D &axis1, float angle1, |
511 | const QVector3D &axis2, float angle2) |
512 | { |
513 | const QQuaternion q1 = QQuaternion::fromAxisAndAngle(axis: axis1, angle: angle1); |
514 | const QQuaternion q2 = QQuaternion::fromAxisAndAngle(axis: axis2, angle: angle2); |
515 | return q2 * q1; |
516 | } |
517 | |
518 | /*! |
519 | Creates a QQuaternion from \a axis1, \a angle1, \a axis2, \a angle2, \a axis3, and \a angle3. |
520 | Returns the resulting QQuaternion. |
521 | */ |
522 | QQuaternion QTransform::fromAxesAndAngles(const QVector3D &axis1, float angle1, |
523 | const QVector3D &axis2, float angle2, |
524 | const QVector3D &axis3, float angle3) |
525 | { |
526 | const QQuaternion q1 = QQuaternion::fromAxisAndAngle(axis: axis1, angle: angle1); |
527 | const QQuaternion q2 = QQuaternion::fromAxisAndAngle(axis: axis2, angle: angle2); |
528 | const QQuaternion q3 = QQuaternion::fromAxisAndAngle(axis: axis3, angle: angle3); |
529 | return q3 * q2 * q1; |
530 | } |
531 | |
532 | /*! |
533 | Creates a QQuaterniom definining a rotation from the axes \a xAxis, \a yAxis and \a zAxis. |
534 | \since 5.11 |
535 | */ |
536 | QQuaternion QTransform::fromAxes(const QVector3D &xAxis, const QVector3D &yAxis, const QVector3D &zAxis) |
537 | { |
538 | return QQuaternion::fromAxes(xAxis, yAxis, zAxis); |
539 | } |
540 | |
541 | /*! |
542 | Creates a QQuaternion from \a eulerAngles. |
543 | Returns the resulting QQuaternion. |
544 | */ |
545 | QQuaternion QTransform::fromEulerAngles(const QVector3D &eulerAngles) |
546 | { |
547 | return QQuaternion::fromEulerAngles(eulerAngles); |
548 | } |
549 | |
550 | /*! |
551 | Creates a QQuaternion from \a pitch, \a yaw, and \a roll. |
552 | Returns the resulting QQuaternion. |
553 | */ |
554 | QQuaternion QTransform::fromEulerAngles(float pitch, float yaw, float roll) |
555 | { |
556 | return QQuaternion::fromEulerAngles(pitch, yaw, roll); |
557 | } |
558 | |
559 | /*! |
560 | Creates a rotation matrix from \a axis and \a angle around \a point. |
561 | Returns the resulting QMatrix4x4. |
562 | */ |
563 | QMatrix4x4 QTransform::rotateAround(const QVector3D &point, float angle, const QVector3D &axis) |
564 | { |
565 | QMatrix4x4 m; |
566 | m.translate(vector: point); |
567 | m.rotate(angle, vector: axis); |
568 | m.translate(vector: -point); |
569 | return m; |
570 | } |
571 | |
572 | /*! |
573 | Returns a rotation matrix defined from the axes \a xAxis, \a yAxis, \a zAxis. |
574 | \since 5.11 |
575 | */ |
576 | QMatrix4x4 QTransform::rotateFromAxes(const QVector3D &xAxis, const QVector3D &yAxis, const QVector3D &zAxis) |
577 | { |
578 | return QMatrix4x4(xAxis.x(), yAxis.x(), zAxis.x(), 0.0f, |
579 | xAxis.y(), yAxis.y(), zAxis.y(), 0.0f, |
580 | xAxis.z(), yAxis.z(), zAxis.z(), 0.0f, |
581 | 0.0f, 0.0f, 0.0f, 1.0f); |
582 | } |
583 | |
584 | } // namespace Qt3DCore |
585 | |
586 | QT_END_NAMESPACE |
587 | |
588 | #include "moc_qtransform.cpp" |
589 | |