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