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