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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | QQuick3DNodePrivate::QQuick3DNodePrivate(QQuick3DObjectPrivate::Type t) |
16 | : QQuick3DObjectPrivate(t) |
17 | { |
18 | |
19 | } |
20 | |
21 | QQuick3DNodePrivate::~QQuick3DNodePrivate() |
22 | { |
23 | |
24 | } |
25 | |
26 | void QQuick3DNodePrivate::init() |
27 | { |
28 | |
29 | } |
30 | |
31 | void 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 | void 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 | |
129 | QQuick3DNode::QQuick3DNode(QQuick3DNode *parent) |
130 | : QQuick3DObject(*(new QQuick3DNodePrivate(QQuick3DNodePrivate::Type::Node)), parent) |
131 | { |
132 | Q_D(QQuick3DNode); |
133 | d->init(); |
134 | } |
135 | |
136 | QQuick3DNode::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 | |
145 | QQuick3DNode::~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 | */ |
155 | float 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 | */ |
169 | float 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 | */ |
183 | float 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 | */ |
195 | QQuaternion 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 | */ |
208 | QVector3D 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 | */ |
220 | QVector3D 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 | */ |
233 | QVector3D 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 | */ |
247 | float 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 | */ |
259 | bool 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 | */ |
272 | int QQuick3DNode::staticFlags() const |
273 | { |
274 | Q_D(const QQuick3DNode); |
275 | return d->m_staticFlags; |
276 | } |
277 | |
278 | QQuick3DNode *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 | */ |
294 | QVector3D 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 | */ |
308 | QVector3D 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 | */ |
322 | QVector3D 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 | */ |
339 | QVector3D 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 | */ |
350 | QQuaternion 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 | */ |
362 | QVector3D 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 | */ |
374 | QMatrix4x4 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 | |
382 | void 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 | |
408 | QMatrix4x4 QQuick3DNodePrivate::localRotationMatrix() const |
409 | { |
410 | return QMatrix4x4(m_rotation.toRotationMatrix()); |
411 | } |
412 | |
413 | QMatrix4x4 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 | |
438 | void 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 | |
498 | bool 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 | |
513 | bool 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 | |
526 | void 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 | |
539 | void 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 | |
548 | void 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 | |
556 | void 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 | |
584 | void 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 | |
597 | void 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 | |
610 | void 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 | |
623 | void 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 | |
638 | void 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 | |
664 | void 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 | |
677 | void 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 | |
690 | void 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 | |
701 | void 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 | |
712 | void 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 | |
723 | void 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 | */ |
753 | void 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 | |
794 | QSSGRenderGraphObject *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 | */ |
859 | QVector3D 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 | */ |
871 | QVector3D 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 | */ |
886 | QVector3D 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 | */ |
902 | QVector3D 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 | */ |
920 | QVector3D 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 | */ |
938 | QVector3D 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 | */ |
960 | QVector3D 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 | */ |
981 | QVector3D 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 | |
987 | void 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 | |
1005 | QVector3D QQuick3DNode::eulerRotation() const |
1006 | { |
1007 | const Q_D(QQuick3DNode); |
1008 | |
1009 | return d->m_rotation; |
1010 | } |
1011 | |
1012 | void QQuick3DNode::itemChange(ItemChange change, const ItemChangeData &) |
1013 | { |
1014 | if (change == QQuick3DObject::ItemParentHasChanged) |
1015 | QQuick3DNodePrivate::get(node: this)->markSceneTransformDirty(); |
1016 | } |
1017 | |
1018 | QT_END_NAMESPACE |
1019 | |