1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquick3dnode_p.h"
5#include "qquick3dnode_p_p.h"
6
7#include <QtQuick3DRuntimeRender/private/qssgrendernode_p.h>
8#include <QtQuick3DUtils/private/qssgutils_p.h>
9#include <QtQuick3D/private/qquick3dobject_p.h>
10
11#include <QtMath>
12
13QT_BEGIN_NAMESPACE
14
15QQuick3DNodePrivate::QQuick3DNodePrivate(QQuick3DObjectPrivate::Type t)
16 : QQuick3DObjectPrivate(t)
17{
18
19}
20
21QQuick3DNodePrivate::~QQuick3DNodePrivate()
22{
23
24}
25
26void QQuick3DNodePrivate::init()
27{
28
29}
30
31void QQuick3DNodePrivate::setIsHiddenInEditor(bool isHidden)
32{
33 Q_Q(QQuick3DNode);
34 if (isHidden == m_isHiddenInEditor)
35 return;
36 m_isHiddenInEditor = isHidden;
37 q->update();
38}
39
40/*!
41 \qmltype Node
42 \inherits Object3D
43 \inqmlmodule QtQuick3D
44 \brief The base component for an object that exists in a 3D scene.
45
46 The Node type serves as the base class for other spatial types, such as, \l Model, \l Camera, \l Light.
47 These objects represent an entity that exists in the 3D scene, due to having a position and
48 other properties in the 3D world. With the exception of the root node(s), all Node types are
49 transformed relative to their parent Node, that is, in local coordinates. In many ways the Node
50 type serves the same purpose in Qt Quick 3D scenes as \l Item does for Qt Quick scenes.
51
52 In addition to types deriving from Node, it is also possible to parent other types to
53 a Node. This includes QObject instances, where the Node merely serves as the
54 \l{QObject::parent()}{QObject parent}, and \l{Qt Quick 3D Scenes with 2D Content}{Qt
55 Quick items}.
56
57 Wrapping other objects for the purpose of grouping them into components or sub-trees can be
58 a convenient way to, for example, animated a group of nodes as a whole. This snippet shows how
59 to use Node to animate a camera:
60
61 \qml
62 Node {
63 PerspectiveCamera {
64 position: Qt.vector3d(0, 0, -600)
65 }
66
67 SequentialAnimation on eulerRotation.y {
68 loops: Animation.Infinite
69 PropertyAnimation {
70 duration: 5000
71 from: 0
72 to: 360
73 }
74 }
75 }
76 \endqml
77
78 Node has to be used also if creating a scene outside of \l View3D, for example for the
79 purpose of switching scenes on the fly, or showing the same scene on multiple views.
80
81 \qml
82 Node {
83 id: standAloneScene
84
85 DirectionalLight {}
86
87 Model {
88 source: "#Sphere"
89 materials: [ DefaultMaterial {} ]
90 }
91
92 PerspectiveCamera {
93 z: 600
94 }
95 }
96
97 View3D {
98 importScene: standAloneScene
99 }
100 \endqml
101*/
102
103QQuick3DNode::QQuick3DNode(QQuick3DNode *parent)
104 : QQuick3DObject(*(new QQuick3DNodePrivate(QQuick3DNodePrivate::Type::Node)), parent)
105{
106 Q_D(QQuick3DNode);
107 d->init();
108}
109
110QQuick3DNode::QQuick3DNode(QQuick3DNodePrivate &dd, QQuick3DNode *parent)
111 : QQuick3DObject(dd, parent)
112{
113 Q_ASSERT_X(QSSGRenderGraphObject::isNodeType(dd.type), "", "Type needs to be identified as a node type!");
114
115 Q_D(QQuick3DNode);
116 d->init();
117}
118
119QQuick3DNode::~QQuick3DNode() {}
120
121/*!
122 \qmlproperty real QtQuick3D::Node::x
123
124 This property contains the x value of the position translation in
125 local coordinate space.
126
127 \sa position
128*/
129float QQuick3DNode::x() const
130{
131 Q_D(const QQuick3DNode);
132 return d->m_position.x();
133}
134
135/*!
136 \qmlproperty real QtQuick3D::Node::y
137
138 This property contains the y value of the position translation in
139 local coordinate space.
140
141 \sa position
142*/
143float QQuick3DNode::y() const
144{
145 Q_D(const QQuick3DNode);
146 return d->m_position.y();
147}
148
149/*!
150 \qmlproperty real QtQuick3D::Node::z
151
152 This property contains the z value of the position translation in
153 local coordinate space.
154
155 \sa position
156*/
157float QQuick3DNode::z() const
158{
159 Q_D(const QQuick3DNode);
160 return d->m_position.z();
161}
162
163/*!
164 \qmlproperty quaternion QtQuick3D::Node::rotation
165
166 This property contains the rotation values for the node.
167 These values are stored as a quaternion.
168*/
169QQuaternion QQuick3DNode::rotation() const
170{
171 Q_D(const QQuick3DNode);
172 return d->m_rotation;
173}
174
175/*!
176 \qmlproperty vector3d QtQuick3D::Node::position
177
178 This property contains the position translation in local coordinate space.
179
180 \sa x, y, z
181*/
182QVector3D QQuick3DNode::position() const
183{
184 Q_D(const QQuick3DNode);
185 return d->m_position;
186}
187
188
189/*!
190 \qmlproperty vector3d QtQuick3D::Node::scale
191
192 This property contains the scale values for the x, y, and z axis.
193*/
194QVector3D QQuick3DNode::scale() const
195{
196 Q_D(const QQuick3DNode);
197 return d->m_scale;
198}
199
200/*!
201 \qmlproperty vector3d QtQuick3D::Node::pivot
202
203 This property contains the pivot values for the x, y, and z axis. These
204 values are used as the pivot points when applying rotations to the node.
205
206*/
207QVector3D QQuick3DNode::pivot() const
208{
209 Q_D(const QQuick3DNode);
210 return d->m_pivot;
211}
212
213/*!
214 \qmlproperty real QtQuick3D::Node::opacity
215
216 This property contains the local opacity value of the Node. Since Node
217 objects are not necessarily visible, this value might not have any effect,
218 but this value is inherited by all children of the Node, which might be visible.
219
220*/
221float QQuick3DNode::localOpacity() const
222{
223 Q_D(const QQuick3DNode);
224 return d->m_opacity;
225}
226
227/*!
228 \qmlproperty bool QtQuick3D::Node::visible
229
230 When this property is true, the Node (and its children) can be visible.
231
232*/
233bool QQuick3DNode::visible() const
234{
235 Q_D(const QQuick3DNode);
236 return d->m_visible;
237}
238
239/*!
240 \qmlproperty int QtQuick3D::Node::staticFlags
241 \since 5.15
242
243 This property defines the static flags that are used to evaluate how the node is rendered.
244 Currently doesn't do anything but act as a placeholder for a future implementation.
245*/
246int QQuick3DNode::staticFlags() const
247{
248 Q_D(const QQuick3DNode);
249 return d->m_staticFlags;
250}
251
252QQuick3DNode *QQuick3DNode::parentNode() const
253{
254 // The parent of a QQuick3DNode should never be anything else than a (subclass
255 // of) QQuick3DNode (but the children/leaf nodes can be something else).
256 return static_cast<QQuick3DNode *>(parentItem());
257}
258
259/*!
260 \qmlproperty vector3d QtQuick3D::Node::forward
261 \readonly
262
263 This property returns a normalized vector of the nodes forward direction
264 in scene space.
265
266 \sa up, right, mapDirectionToScene
267*/
268QVector3D QQuick3DNode::forward() const
269{
270 return mapDirectionToScene(localDirection: QVector3D(0, 0, -1)).normalized();
271}
272
273/*!
274 \qmlproperty vector3d QtQuick3D::Node::up
275 \readonly
276
277 This property returns a normalized vector of the nodes up direction
278 in scene space.
279
280 \sa forward, right, mapDirectionToScene
281*/
282QVector3D QQuick3DNode::up() const
283{
284 return mapDirectionToScene(localDirection: QVector3D(0, 1, 0)).normalized();
285}
286
287/*!
288 \qmlproperty vector3d QtQuick3D::Node::right
289 \readonly
290
291 This property returns a normalized vector of the nodes right direction
292 in scene space.
293
294 \sa forward, up, mapDirectionToScene
295*/
296QVector3D QQuick3DNode::right() const
297{
298 return mapDirectionToScene(localDirection: QVector3D(1, 0, 0)).normalized();
299}
300/*!
301 \qmlproperty vector3d QtQuick3D::Node::scenePosition
302 \readonly
303
304 This property returns the position of the node in scene space.
305
306 \note This is sometimes also referred to as the global position. But
307 then in the meaning "global in the 3D world", and not "global to the
308 screen or desktop" (which is usually the interpretation in other Qt APIs).
309 \note the position will be reported in the same orientation as the node.
310
311 \sa mapPositionToScene
312*/
313QVector3D QQuick3DNode::scenePosition() const
314{
315 return QSSGUtils::mat44::getPosition(m: sceneTransform());
316}
317
318/*!
319 \qmlproperty quaternion QtQuick3D::Node::sceneRotation
320 \readonly
321
322 This property returns the rotation of the node in scene space.
323*/
324QQuaternion QQuick3DNode::sceneRotation() const
325{
326 Q_D(const QQuick3DNode);
327 return QQuaternion::fromRotationMatrix(rot3x3: QSSGUtils::mat44::getUpper3x3(m: d->sceneRotationMatrix())).normalized();
328}
329
330/*!
331 \qmlproperty vector3d QtQuick3D::Node::sceneScale
332 \readonly
333
334 This property returns the scale of the node in scene space.
335*/
336QVector3D QQuick3DNode::sceneScale() const
337{
338 return QSSGUtils::mat44::getScale(m: sceneTransform());
339}
340
341/*!
342 \qmlproperty matrix4x4 QtQuick3D::Node::sceneTransform
343 \readonly
344
345 This property returns the global transform matrix for this node.
346 \note the return value will be in right-handed coordinates.
347*/
348QMatrix4x4 QQuick3DNode::sceneTransform() const
349{
350 Q_D(const QQuick3DNode);
351 if (d->m_sceneTransformDirty)
352 const_cast<QQuick3DNodePrivate *>(d)->calculateGlobalVariables();
353 return d->m_sceneTransform;
354}
355
356void QQuick3DNodePrivate::calculateGlobalVariables()
357{
358 Q_Q(QQuick3DNode);
359 m_sceneTransformDirty = false;
360 QMatrix4x4 localTransform = QSSGRenderNode::calculateTransformMatrix(position: m_position, scale: m_scale, pivot: m_pivot, rotation: m_rotation);
361 QQuick3DNode *parent = q->parentNode();
362 if (!parent) {
363 m_sceneTransform = localTransform;
364 m_hasInheritedUniformScale = true;
365 return;
366 }
367 QQuick3DNodePrivate *privateParent = QQuick3DNodePrivate::get(node: parent);
368
369 if (privateParent->m_sceneTransformDirty)
370 privateParent->calculateGlobalVariables();
371 m_sceneTransform = privateParent->m_sceneTransform * localTransform;
372
373 // Check if we have an ancestor with non-uniform scale. This will decide whether
374 // or not we can use the sceneTransform to extract sceneRotation and sceneScale.
375 m_hasInheritedUniformScale = privateParent->m_hasInheritedUniformScale;
376 if (m_hasInheritedUniformScale) {
377 const QVector3D ps = privateParent->m_scale;
378 m_hasInheritedUniformScale = qFuzzyCompare(p1: ps.x(), p2: ps.y()) && qFuzzyCompare(p1: ps.x(), p2: ps.z());
379 }
380}
381
382QMatrix4x4 QQuick3DNodePrivate::localRotationMatrix() const
383{
384 return QMatrix4x4(m_rotation.toRotationMatrix());
385}
386
387QMatrix4x4 QQuick3DNodePrivate::sceneRotationMatrix() const
388{
389 Q_Q(const QQuick3DNode);
390
391 if (m_sceneTransformDirty) {
392 // Ensure m_hasInheritedUniformScale is up to date
393 const_cast<QQuick3DNodePrivate *>(this)->calculateGlobalVariables();
394 }
395
396 if (m_hasInheritedUniformScale) {
397 // When we know that every node up to the root have a uniform scale, we can extract the
398 // rotation directly from the sceneTransform(). This is optimizing, since we reuse that
399 // matrix for more than just calculating the sceneRotation.
400 QMatrix4x4 rotationMatrix = q->sceneTransform();
401 QSSGUtils::mat44::normalize(m&: rotationMatrix);
402 return rotationMatrix;
403 }
404
405 // When we have an ancestor that has a non-uniform scale, we cannot extract
406 // the rotation from the sceneMatrix directly. Instead, we need to calculate
407 // it separately, which is slightly more costly.
408 const QMatrix4x4 parentRotationMatrix = QQuick3DNodePrivate::get(node: q->parentNode())->sceneRotationMatrix();
409 return parentRotationMatrix * localRotationMatrix();
410}
411
412void QQuick3DNodePrivate::emitChangesToSceneTransform()
413{
414 Q_Q(QQuick3DNode);
415 const QVector3D prevPosition = QSSGUtils::mat44::getPosition(m: m_sceneTransform);
416 const QQuaternion prevRotation = QQuaternion::fromRotationMatrix(rot3x3: QSSGUtils::mat44::getUpper3x3(m: m_sceneTransform)).normalized();
417 const QVector3D prevScale = QSSGUtils::mat44::getScale(m: m_sceneTransform);
418 QVector3D prevForward, prevUp, prevRight;
419 QVector3D newForward, newUp, newRight;
420 // Do direction (forward, up, right) calculations only if they have connections
421 bool emitDirectionChanges = (m_directionConnectionCount > 0);
422 if (emitDirectionChanges) {
423 // Instead of calling forward(), up() and right(), calculate them here.
424 // This way m_sceneTransform isn't updated due to m_sceneTransformDirty and
425 // common theDirMatrix operations are not duplicated.
426 QMatrix3x3 theDirMatrix = m_sceneTransform.normalMatrix();
427 prevForward = QSSGUtils::mat33::transform(m: theDirMatrix, v: QVector3D(0, 0, -1)).normalized();
428 prevUp = QSSGUtils::mat33::transform(m: theDirMatrix, v: QVector3D(0, 1, 0)).normalized();
429 prevRight = QSSGUtils::mat33::transform(m: theDirMatrix, v: QVector3D(1, 0, 0)).normalized();
430 }
431
432 calculateGlobalVariables();
433
434 const QVector3D newPosition = QSSGUtils::mat44::getPosition(m: m_sceneTransform);
435 const QQuaternion newRotation = QQuaternion::fromRotationMatrix(rot3x3: QSSGUtils::mat44::getUpper3x3(m: m_sceneTransform)).normalized();
436 const QVector3D newScale = QSSGUtils::mat44::getScale(m: m_sceneTransform);
437 if (emitDirectionChanges) {
438 QMatrix3x3 theDirMatrix = m_sceneTransform.normalMatrix();
439 newForward = QSSGUtils::mat33::transform(m: theDirMatrix, v: QVector3D(0, 0, -1)).normalized();
440 newUp = QSSGUtils::mat33::transform(m: theDirMatrix, v: QVector3D(0, 1, 0)).normalized();
441 newRight = QSSGUtils::mat33::transform(m: theDirMatrix, v: QVector3D(1, 0, 0)).normalized();
442 }
443
444 const bool positionChanged = prevPosition != newPosition;
445 const bool rotationChanged = prevRotation != newRotation;
446 const bool scaleChanged = !qFuzzyCompare(v1: prevScale, v2: newScale);
447
448 if (!positionChanged && !rotationChanged && !scaleChanged)
449 return;
450
451 emit q->sceneTransformChanged();
452
453 if (positionChanged)
454 emit q->scenePositionChanged();
455 if (rotationChanged)
456 emit q->sceneRotationChanged();
457 if (scaleChanged)
458 emit q->sceneScaleChanged();
459 if (emitDirectionChanges) {
460 const bool forwardChanged = prevForward != newForward;
461 const bool upChanged = prevUp != newUp;
462 const bool rightChanged = prevRight != newRight;
463 if (forwardChanged)
464 Q_EMIT q->forwardChanged();
465 if (upChanged)
466 Q_EMIT q->upChanged();
467 if (rightChanged)
468 Q_EMIT q->rightChanged();
469 }
470}
471
472bool QQuick3DNodePrivate::isSceneTransformRelatedSignal(const QMetaMethod &signal) const
473{
474 // Return true if its likely that we need to emit
475 // the given signal when our global transform changes.
476 static const QMetaMethod sceneTransformSignal = QMetaMethod::fromSignal(signal: &QQuick3DNode::sceneTransformChanged);
477 static const QMetaMethod scenePositionSignal = QMetaMethod::fromSignal(signal: &QQuick3DNode::scenePositionChanged);
478 static const QMetaMethod sceneRotationSignal = QMetaMethod::fromSignal(signal: &QQuick3DNode::sceneRotationChanged);
479 static const QMetaMethod sceneScaleSignal = QMetaMethod::fromSignal(signal: &QQuick3DNode::sceneScaleChanged);
480
481 return (signal == sceneTransformSignal
482 || signal == scenePositionSignal
483 || signal == sceneRotationSignal
484 || signal == sceneScaleSignal);
485}
486
487bool QQuick3DNodePrivate::isDirectionRelatedSignal(const QMetaMethod &signal) const
488{
489 // Return true if its likely that we need to emit
490 // the given signal when our global transform changes.
491 static const QMetaMethod forwardSignal = QMetaMethod::fromSignal(signal: &QQuick3DNode::forwardChanged);
492 static const QMetaMethod upSignal = QMetaMethod::fromSignal(signal: &QQuick3DNode::upChanged);
493 static const QMetaMethod rightSignal = QMetaMethod::fromSignal(signal: &QQuick3DNode::rightChanged);
494
495 return (signal == forwardSignal
496 || signal == upSignal
497 || signal == rightSignal);
498}
499
500void QQuick3DNode::connectNotify(const QMetaMethod &signal)
501{
502 Q_D(QQuick3DNode);
503 // Since we want to avoid calculating the global transform in the frontend
504 // unnecessary, we keep track of the number of connections/QML bindings
505 // that needs it. If there are no connections, we can skip calculating it
506 // whenever our geometry changes (unless someone asks for it explicitly).
507 if (d->isSceneTransformRelatedSignal(signal))
508 d->m_sceneTransformConnectionCount++;
509 if (d->isDirectionRelatedSignal(signal))
510 d->m_directionConnectionCount++;
511}
512
513void QQuick3DNode::disconnectNotify(const QMetaMethod &signal)
514{
515 Q_D(QQuick3DNode);
516 if (d->isSceneTransformRelatedSignal(signal))
517 d->m_sceneTransformConnectionCount--;
518 if (d->isDirectionRelatedSignal(signal))
519 d->m_directionConnectionCount--;
520}
521
522void QQuick3DNode::componentComplete()
523{
524 Q_D(QQuick3DNode);
525 QQuick3DObject::componentComplete();
526 if (d->m_sceneTransformConnectionCount > 0 || d->m_directionConnectionCount > 0)
527 d->emitChangesToSceneTransform();
528}
529
530void QQuick3DNodePrivate::markSceneTransformDirty()
531{
532 Q_Q(QQuick3DNode);
533 // Note: we recursively set m_sceneTransformDirty to true whenever our geometry
534 // changes. But we only set it back to false if someone actually queries our global
535 // transform (because only then do we need to calculate it). This means that if no
536 // one ever does that, m_sceneTransformDirty will remain true, perhaps through out
537 // the life time of the node. This is in contrast with the backend, which need to
538 // update dirty transform nodes for every scene graph sync (and clear the backend
539 // dirty transform flags - QQuick3DObjectPrivate::dirtyAttributes).
540 // This means that for most nodes, calling markSceneTransformDirty() should be
541 // cheap, since we normally expect to return early in the following test.
542 if (m_sceneTransformDirty)
543 return;
544
545 m_sceneTransformDirty = true;
546
547 if (m_sceneTransformConnectionCount > 0 || m_directionConnectionCount > 0)
548 emitChangesToSceneTransform();
549
550 auto children = QQuick3DObjectPrivate::get(item: q)->childItems;
551 for (auto child : children) {
552 if (auto node = qobject_cast<QQuick3DNode *>(object: child)) {
553 QQuick3DNodePrivate::get(node)->markSceneTransformDirty();
554 }
555 }
556}
557
558void QQuick3DNode::setX(float x)
559{
560 Q_D(QQuick3DNode);
561 if (qFuzzyCompare(p1: d->m_position.x(), p2: x))
562 return;
563
564 d->m_position.setX(x);
565 d->markSceneTransformDirty();
566 emit positionChanged();
567 emit xChanged();
568 update();
569}
570
571void QQuick3DNode::setY(float y)
572{
573 Q_D(QQuick3DNode);
574 if (qFuzzyCompare(p1: d->m_position.y(), p2: y))
575 return;
576
577 d->m_position.setY(y);
578 d->markSceneTransformDirty();
579 emit positionChanged();
580 emit yChanged();
581 update();
582}
583
584void QQuick3DNode::setZ(float z)
585{
586 Q_D(QQuick3DNode);
587 if (qFuzzyCompare(p1: d->m_position.z(), p2: z))
588 return;
589
590 d->m_position.setZ(z);
591 d->markSceneTransformDirty();
592 emit positionChanged();
593 emit zChanged();
594 update();
595}
596
597void QQuick3DNode::setRotation(const QQuaternion &rotation)
598{
599 Q_D(QQuick3DNode);
600 if (d->m_rotation == rotation)
601 return;
602
603 d->m_rotation = rotation;
604 d->markSceneTransformDirty();
605 emit rotationChanged();
606 emit eulerRotationChanged();
607
608 update();
609}
610
611void QQuick3DNode::setPosition(const QVector3D &position)
612{
613 Q_D(QQuick3DNode);
614 if (d->m_position == position)
615 return;
616
617 const bool xUnchanged = qFuzzyCompare(p1: position.x(), p2: d->m_position.x());
618 const bool yUnchanged = qFuzzyCompare(p1: position.y(), p2: d->m_position.y());
619 const bool zUnchanged = qFuzzyCompare(p1: position.z(), p2: d->m_position.z());
620
621 d->m_position = position;
622 d->markSceneTransformDirty();
623 emit positionChanged();
624
625 if (!xUnchanged)
626 emit xChanged();
627 if (!yUnchanged)
628 emit yChanged();
629 if (!zUnchanged)
630 emit zChanged();
631
632 update();
633}
634
635void QQuick3DNode::setScale(const QVector3D &scale)
636{
637 Q_D(QQuick3DNode);
638 if (d->m_scale == scale)
639 return;
640
641 d->m_scale = scale;
642 d->markSceneTransformDirty();
643 emit scaleChanged();
644 update();
645}
646
647void QQuick3DNode::setPivot(const QVector3D &pivot)
648{
649 Q_D(QQuick3DNode);
650 if (d->m_pivot == pivot)
651 return;
652
653 d->m_pivot = pivot;
654 d->markSceneTransformDirty();
655 emit pivotChanged();
656 update();
657}
658
659void QQuick3DNode::setLocalOpacity(float opacity)
660{
661 Q_D(QQuick3DNode);
662 if (qFuzzyCompare(p1: d->m_opacity, p2: opacity))
663 return;
664
665 d->m_opacity = opacity;
666 emit localOpacityChanged();
667 update();
668}
669
670void QQuick3DNode::setVisible(bool visible)
671{
672 Q_D(QQuick3DNode);
673 if (d->m_visible == visible)
674 return;
675
676 d->m_visible = visible;
677 emit visibleChanged();
678 update();
679}
680
681void QQuick3DNode::setStaticFlags(int staticFlags)
682{
683 Q_D(QQuick3DNode);
684 if (d->m_staticFlags == staticFlags)
685 return;
686
687 d->m_staticFlags = staticFlags;
688 emit staticFlagsChanged();
689 update();
690}
691
692void QQuick3DNode::setEulerRotation(const QVector3D &eulerRotation) {
693 Q_D(QQuick3DNode);
694
695 if (d->m_rotation == eulerRotation)
696 return;
697
698 d->m_rotation = eulerRotation;
699
700 emit rotationChanged();
701 d->markSceneTransformDirty();
702 emit eulerRotationChanged();
703 update();
704}
705
706/*!
707 \qmlmethod QtQuick3D::Node::rotate(real degrees, vector3d axis, enumeration space)
708
709 Rotates this node around an \a axis by the given \a degrees. The specified
710 rotation will be added to the node's current rotation. The axis can
711 be specified relative to different \a {space}s.
712
713 \value Node.LocalSpace
714 Axis is relative to the local orientation of this node.
715 \value Node.ParentSpace
716 Axis is relative to the local orientation of the parent node.
717 \value Node.SceneSpace
718 Axis is relative to the scene.
719
720*/
721void QQuick3DNode::rotate(qreal degrees, const QVector3D &axis, TransformSpace space)
722{
723 Q_D(QQuick3DNode);
724
725 const QQuaternion addRotationQuat = QQuaternion::fromAxisAndAngle(axis, angle: float(degrees));
726 const QMatrix4x4 addRotationMatrix = QMatrix4x4(addRotationQuat.toRotationMatrix());
727 QMatrix4x4 newRotationMatrix;
728
729 switch (space) {
730 case LocalSpace:
731 newRotationMatrix = d->localRotationMatrix() * addRotationMatrix;
732 break;
733 case ParentSpace:
734 newRotationMatrix = addRotationMatrix * d->localRotationMatrix();
735 break;
736 case SceneSpace:
737 if (const auto parent = parentNode()) {
738 const QMatrix4x4 lrm = d->localRotationMatrix();
739 const QMatrix4x4 prm = QQuick3DNodePrivate::get(node: parent)->sceneRotationMatrix();
740 newRotationMatrix = prm.inverted() * addRotationMatrix * prm * lrm;
741 } else {
742 newRotationMatrix = d->localRotationMatrix() * addRotationMatrix;
743 }
744 break;
745 }
746
747 const QQuaternion newRotationQuaternion = QQuaternion::fromRotationMatrix(rot3x3: QSSGUtils::mat44::getUpper3x3(m: newRotationMatrix)).normalized();
748
749 if (d->m_rotation == newRotationQuaternion)
750 return;
751
752 d->m_rotation = newRotationQuaternion;
753 d->markSceneTransformDirty();
754
755 emit rotationChanged();
756 emit eulerRotationChanged();
757
758 update();
759}
760
761QSSGRenderGraphObject *QQuick3DNode::updateSpatialNode(QSSGRenderGraphObject *node)
762{
763 Q_D(QQuick3DNode);
764 if (!node) {
765 markAllDirty();
766 node = new QSSGRenderNode();
767 }
768 QQuick3DObject::updateSpatialNode(node);
769 auto spacialNode = static_cast<QSSGRenderNode *>(node);
770 bool transformIsDirty = false;
771
772 if (spacialNode->pivot != d->m_pivot) {
773 transformIsDirty = true;
774 spacialNode->pivot = d->m_pivot;
775 }
776
777 if (!qFuzzyCompare(p1: spacialNode->localOpacity, p2: d->m_opacity)) {
778 spacialNode->localOpacity = d->m_opacity;
779 spacialNode->markDirty(dirtyFlag: QSSGRenderNode::DirtyFlag::OpacityDirty);
780 }
781
782 if (!transformIsDirty && !qFuzzyCompare(v1: d->m_position, v2: QSSGUtils::mat44::getPosition(m: spacialNode->localTransform)))
783 transformIsDirty = true;
784
785 if (!transformIsDirty && !qFuzzyCompare(v1: d->m_scale, v2: QSSGUtils::mat44::getScale(m: spacialNode->localTransform)))
786 transformIsDirty = true;
787
788 if (!transformIsDirty && !qFuzzyCompare(q1: d->m_rotation, q2: QQuaternion::fromRotationMatrix(rot3x3: QSSGUtils::mat44::getUpper3x3(m: spacialNode->localTransform))))
789 transformIsDirty = true;
790
791 if (transformIsDirty) {
792 spacialNode->localTransform = QSSGRenderNode::calculateTransformMatrix(position: d->m_position, scale: d->m_scale, pivot: d->m_pivot, rotation: d->m_rotation);;
793 spacialNode->markDirty(dirtyFlag: QSSGRenderNode::DirtyFlag::TransformDirty);
794 }
795
796 spacialNode->staticFlags = d->m_staticFlags;
797
798 // The Hidden in Editor flag overrides the visible value
799 if (d->m_isHiddenInEditor)
800 spacialNode->setState(state: QSSGRenderNode::LocalState::Active, on: false);
801 else
802 spacialNode->setState(state: QSSGRenderNode::LocalState::Active, on: d->m_visible);
803
804 DebugViewHelpers::ensureDebugObjectName(node: spacialNode, src: this);
805
806 return spacialNode;
807}
808
809/*!
810 \qmlmethod vector3d QtQuick3D::Node::mapPositionToScene(vector3d localPosition)
811
812 Transforms \a localPosition from local space to scene space.
813
814 \note "Scene space" is sometimes also referred to as the "global space". But
815 then in the meaning "global in the 3D world", and not "global to the
816 screen or desktop" (which is usually the interpretation in other Qt APIs).
817
818 \sa mapPositionFromScene, mapPositionToNode, mapPositionFromNode
819*/
820QVector3D QQuick3DNode::mapPositionToScene(const QVector3D &localPosition) const
821{
822 return QSSGUtils::mat44::transform(m: sceneTransform(), v: localPosition);
823}
824
825/*!
826 \qmlmethod vector3d QtQuick3D::Node::mapPositionFromScene(vector3d scenePosition)
827
828 Transforms \a scenePosition from scene space to local space.
829
830 \sa mapPositionToScene, mapPositionToNode, mapPositionFromNode
831*/
832QVector3D QQuick3DNode::mapPositionFromScene(const QVector3D &scenePosition) const
833{
834 return QSSGUtils::mat44::transform(m: sceneTransform().inverted(), v: scenePosition);
835}
836
837/*!
838 \qmlmethod vector3d QtQuick3D::Node::mapPositionToNode(QtQuick3D::Node node, vector3d localPosition)
839
840 Transforms \a localPosition from the local space of this node to
841 the local space of \a node.
842
843 \note If \a node is null, then \a localPosition will be transformed into scene space coordinates.
844
845 \sa mapPositionToScene, mapPositionFromScene, mapPositionFromNode
846*/
847QVector3D QQuick3DNode::mapPositionToNode(const QQuick3DNode *node, const QVector3D &localPosition) const
848{
849 const auto scenePositionSelf = mapPositionToScene(localPosition);
850 return node ? node->mapPositionFromScene(scenePosition: scenePositionSelf) : scenePositionSelf;
851}
852
853/*!
854 \qmlmethod vector3d QtQuick3D::Node::mapPositionFromNode(QtQuick3D::Node node, vector3d localPosition)
855
856 Transforms \a localPosition from the local space of \a node to
857 the local space of this node.
858
859 \note If \a node is null, then \a localPosition is interpreted as it is in scene space coordinates.
860
861 \sa mapPositionToScene, mapPositionFromScene, mapPositionToNode
862*/
863QVector3D QQuick3DNode::mapPositionFromNode(const QQuick3DNode *node, const QVector3D &localPosition) const
864{
865 const auto scenePositionOther = node ? node->mapPositionToScene(localPosition) : localPosition;
866 return mapPositionFromScene(scenePosition: scenePositionOther);
867}
868
869/*!
870 \qmlmethod vector3d QtQuick3D::Node::mapDirectionToScene(vector3d localDirection)
871
872 Transforms \a localDirection from local space to scene space.
873 The return value is not affected by the (inherited) scale or
874 position of the node.
875
876 \note the return value will have the same length as \a localDirection
877 (i.e. not normalized).
878
879 \sa mapDirectionFromScene, mapDirectionToNode, mapDirectionFromNode
880*/
881QVector3D QQuick3DNode::mapDirectionToScene(const QVector3D &localDirection) const
882{
883 QMatrix3x3 theDirMatrix = sceneTransform().normalMatrix();
884 return QSSGUtils::mat33::transform(m: theDirMatrix, v: localDirection);
885}
886
887/*!
888 \qmlmethod vector3d QtQuick3D::Node::mapDirectionFromScene(vector3d sceneDirection)
889
890 Transforms \a sceneDirection from scene space to local space.
891 The return value is not affected by the (inherited) scale or
892 position of the node.
893
894 \note the return value will have the same length as \a sceneDirection
895 (i.e not normalized).
896
897 \sa mapDirectionToScene, mapDirectionToNode, mapDirectionFromNode
898*/
899QVector3D QQuick3DNode::mapDirectionFromScene(const QVector3D &sceneDirection) const
900{
901 QMatrix3x3 theDirMatrix = QSSGUtils::mat44::getUpper3x3(m: sceneTransform());
902 theDirMatrix = theDirMatrix.transposed();
903 return QSSGUtils::mat33::transform(m: theDirMatrix, v: sceneDirection);
904}
905
906/*!
907 \qmlmethod vector3d QtQuick3D::Node::mapDirectionToNode(QtQuick3D::Node node, vector3d localDirection)
908
909 Transforms \a localDirection from this nodes local space to the
910 local space of \a node.
911 The return value is not affected by the (inherited) scale or
912 position of the node.
913
914 \note the return value will have the same length as \a localDirection
915 (i.e. not normalized).
916
917 \note if \a node is null, then the returned direction will be transformed into scene space coordinates.
918
919 \sa mapDirectionFromNode, mapDirectionFromScene, mapDirectionToScene
920*/
921QVector3D QQuick3DNode::mapDirectionToNode(const QQuick3DNode *node, const QVector3D &localDirection) const
922{
923 const auto sceneDirectionSelf = mapDirectionToScene(localDirection);
924 return node ? node->mapDirectionFromScene(sceneDirection: sceneDirectionSelf) : sceneDirectionSelf;
925}
926
927/*!
928 \qmlmethod vector3d QtQuick3D::Node::mapDirectionFromNode(QtQuick3D::Node node, vector3d localDirection)
929
930 Transforms \a localDirection from the local space of \a node to the
931 local space of this node.
932 The return value is not affected by the (inherited) scale or
933 position of the node.
934
935 \note the return value will have the same length as \a localDirection
936 (i.e. not normalized).
937
938 \note If \a node is null, then \a localDirection is interpreted as it is in scene space coordinates.
939
940 \sa mapDirectionToNode, mapDirectionFromScene, mapDirectionToScene
941*/
942QVector3D QQuick3DNode::mapDirectionFromNode(const QQuick3DNode *node, const QVector3D &localDirection) const
943{
944 const auto sceneDirectionOther = node ? node->mapDirectionToScene(localDirection) : localDirection;
945 return mapDirectionFromScene(sceneDirection: sceneDirectionOther);
946}
947
948void QQuick3DNode::markAllDirty()
949{
950 Q_D(QQuick3DNode);
951
952 d->markSceneTransformDirty();
953 QQuick3DObject::markAllDirty();
954}
955
956/*!
957 \qmlproperty vector3d QtQuick3D::Node::eulerRotation
958
959 This property contains the rotation values for the x, y, and z axis.
960 These values are stored as a vector3d. Rotation order is assumed to
961 be ZXY.
962
963 \sa QQuaternion::fromEulerAngles()
964*/
965
966QVector3D QQuick3DNode::eulerRotation() const
967{
968 const Q_D(QQuick3DNode);
969
970 return d->m_rotation;
971}
972
973void QQuick3DNode::itemChange(ItemChange change, const ItemChangeData &)
974{
975 if (change == QQuick3DObject::ItemParentHasChanged)
976 QQuick3DNodePrivate::get(node: this)->markSceneTransformDirty();
977}
978
979QT_END_NAMESPACE
980

source code of qtquick3d/src/quick3d/qquick3dnode.cpp