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

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