1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5
6#include "qssgrendernode_p.h"
7
8#include <QtQuick3DUtils/private/qssgutils_p.h>
9
10#include <QtQuick3DRuntimeRender/private/qssgrendermodel_p.h>
11
12#include <QtQuick3DRuntimeRender/private/qssgrenderbuffermanager_p.h>
13
14#include <QtQuick3DUtils/private/qssgplane_p.h>
15
16QT_BEGIN_NAMESPACE
17
18QSSGRenderNode::QSSGRenderNode()
19 : QSSGRenderNode(Type::Node)
20{
21}
22
23QSSGRenderNode::QSSGRenderNode(Type type)
24 : QSSGRenderGraphObject(type)
25{
26 globalTransform = localTransform = calculateTransformMatrix(position: {}, scale: initScale, pivot: {}, rotation: {});
27}
28
29QSSGRenderNode::~QSSGRenderNode()
30 = default;
31
32void QSSGRenderNode::markDirty(DirtyFlag dirtyFlag)
33{
34 if ((flags & FlagT(dirtyFlag)) == 0) { // If not already marked
35 flags |= FlagT(dirtyFlag);
36 const bool markSubtreeDirty = ((FlagT(dirtyFlag) & FlagT(DirtyFlag::GlobalValuesDirty)) != 0);
37 if (markSubtreeDirty) {
38 for (auto &cld : children)
39 cld.markDirty(dirtyFlag);
40 }
41 }
42}
43
44void QSSGRenderNode::clearDirty(DirtyFlag dirtyFlag)
45{
46 flags &= ~FlagT(dirtyFlag);
47}
48
49void QSSGRenderNode::setState(LocalState state, bool on)
50{
51 const bool changed = (getLocalState(stateFlag: state) != on);
52 if (changed) { // Mark state dirty
53 flags = on ? (flags | FlagT(state)) : (flags & ~FlagT(state));
54
55 // Mark state dirty
56 switch (state) {
57 case QSSGRenderNode::LocalState::Active:
58 markDirty(dirtyFlag: DirtyFlag::ActiveDirty);
59 break;
60 case QSSGRenderNode::LocalState::Pickable:
61 markDirty(dirtyFlag: DirtyFlag::PickableDirty);
62 break;
63 }
64 }
65
66}
67
68// Calculate global transform and opacity
69// Walks up the graph ensure all parents are not dirty so they have
70// valid global transforms.
71
72bool QSSGRenderNode::calculateGlobalVariables()
73{
74 bool retval = isDirty(dirtyFlag: DirtyFlag::GlobalValuesDirty);
75 if (retval) {
76 globalOpacity = localOpacity;
77 globalTransform = localTransform;
78
79 if (parent) {
80 retval = parent->calculateGlobalVariables() || retval;
81 const bool globallyActive = getLocalState(stateFlag: LocalState::Active) && parent->getGlobalState(stateFlag: GlobalState::Active);
82 flags = globallyActive ? (flags | FlagT(GlobalState::Active)) : (flags & ~FlagT(GlobalState::Active));
83 const bool globallyPickable = getLocalState(stateFlag: LocalState::Pickable) || parent->getGlobalState(stateFlag: GlobalState::Pickable);
84 flags = globallyPickable ? (flags | FlagT(GlobalState::Pickable)) : (flags & ~FlagT(GlobalState::Pickable));
85 globalOpacity *= parent->globalOpacity;
86 // Skip calculating the transform for non-active nodes
87 if (globallyActive && parent->type != QSSGRenderGraphObject::Type::Layer) {
88 globalTransform = parent->globalTransform * localTransform;
89 if (this == instanceRoot) {
90 globalInstanceTransform = parent->globalTransform;
91 localInstanceTransform = localTransform;
92 } else if (instanceRoot) {
93 globalInstanceTransform = instanceRoot->globalInstanceTransform;
94 //### technically O(n^2) -- we could cache localInstanceTransform if every node in the
95 // tree is guaranteed to have the same instance root. That would require an API change.
96 localInstanceTransform = localTransform;
97 auto *p = parent;
98 while (p) {
99 if (p == instanceRoot) {
100 localInstanceTransform = p->localInstanceTransform * localInstanceTransform;
101 break;
102 }
103 localInstanceTransform = p->localTransform * localInstanceTransform;
104 p = p->parent;
105 }
106 } else {
107 // By default, we do magic: translation is applied to the global instance transform,
108 // while scale/rotation is local
109
110 localInstanceTransform = localTransform;
111 auto &localInstanceMatrix = *reinterpret_cast<float (*)[4][4]>(localInstanceTransform.data());
112 QVector3D localPos{localInstanceMatrix[3][0], localInstanceMatrix[3][1], localInstanceMatrix[3][2]};
113 localInstanceMatrix[3][0] = 0;
114 localInstanceMatrix[3][1] = 0;
115 localInstanceMatrix[3][2] = 0;
116 globalInstanceTransform = parent->globalTransform;
117 globalInstanceTransform.translate(vector: localPos);
118 }
119 }
120 } else {
121 const bool globallyActive = getLocalState(stateFlag: LocalState::Active);
122 flags = globallyActive ? (flags | FlagT(GlobalState::Active)) : (flags & ~FlagT(GlobalState::Active));
123 const bool globallyPickable = getLocalState(stateFlag: LocalState::Pickable);
124 flags = globallyPickable ? (flags | FlagT(GlobalState::Pickable)) : (flags & ~FlagT(GlobalState::Pickable));
125 localInstanceTransform = localTransform;
126 globalInstanceTransform = {};
127 }
128 // Clear dirty flags
129 clearDirty(dirtyFlag: DirtyFlag::GlobalValuesDirty);
130 }
131 // We always clear dirty in a reasonable manner but if we aren't active
132 // there is no reason to tell the universe if we are dirty or not.
133 return retval && getLocalState(stateFlag: LocalState::Active);
134}
135
136QMatrix4x4 QSSGRenderNode::calculateTransformMatrix(QVector3D position, QVector3D scale, QVector3D pivot, QQuaternion rotation)
137{
138 QMatrix4x4 transform;
139
140 // Offset the origin (this is our pivot point)
141 auto offset = (-pivot * scale);
142
143 // Scale
144 transform(0, 0) = scale[0];
145 transform(1, 1) = scale[1];
146 transform(2, 2) = scale[2];
147
148 // Offset (before rotation)
149 transform(0, 3) = offset[0];
150 transform(1, 3) = offset[1];
151 transform(2, 3) = offset[2];
152
153 // rotate
154 transform = QMatrix4x4{rotation.toRotationMatrix()} * transform;
155
156 // translate
157 transform(0, 3) += position[0];
158 transform(1, 3) += position[1];
159 transform(2, 3) += position[2];
160
161 return transform;
162}
163
164void QSSGRenderNode::addChild(QSSGRenderNode &inChild)
165{
166 // Adding children to a layer does not reset parent
167 // because layers can share children over with other layers
168 if (type != QSSGRenderNode::Type::Layer) {
169 if (inChild.parent && inChild.parent != this)
170 inChild.parent->removeChild(inChild);
171 inChild.parent = this;
172 }
173 children.push_back(inObj&: inChild);
174 inChild.markDirty(dirtyFlag: DirtyFlag::GlobalValuesDirty);
175}
176
177void QSSGRenderNode::removeChild(QSSGRenderNode &inChild)
178{
179 if (Q_UNLIKELY(type != QSSGRenderNode::Type::Layer && inChild.parent != this)) {
180 Q_ASSERT(inChild.parent == this);
181 return;
182 }
183
184 inChild.parent = nullptr;
185 children.remove(inObj&: inChild);
186 inChild.markDirty(dirtyFlag: DirtyFlag::GlobalValuesDirty);
187}
188
189void QSSGRenderNode::removeFromGraph()
190{
191 if (parent)
192 parent->removeChild(inChild&: *this);
193
194 // Orphan all of my children.
195 for (auto it = children.begin(), end = children.end(); it != end;) {
196 auto &removedChild = *it++;
197 children.remove(inObj&: removedChild);
198 removedChild.parent = nullptr;
199 }
200}
201
202QSSGBounds3 QSSGRenderNode::getBounds(QSSGBufferManager &inManager,
203 bool inIncludeChildren) const
204{
205 QSSGBounds3 retval;
206 if (inIncludeChildren)
207 retval = getChildBounds(inManager);
208
209 if (type == QSSGRenderGraphObject::Type::Model) {
210 auto model = static_cast<const QSSGRenderModel *>(this);
211 retval.include(b: inManager.getModelBounds(model));
212 }
213 return retval;
214}
215
216QSSGBounds3 QSSGRenderNode::getChildBounds(QSSGBufferManager &inManager) const
217{
218 QSSGBounds3 retval;
219 QSSGBounds3 childBounds;
220 for (auto &child : children) {
221 childBounds = child.getBounds(inManager);
222 if (!childBounds.isEmpty()) {
223 // Transform the bounds into our local space.
224 childBounds.transform(inMatrix: child.localTransform);
225 retval.include(b: childBounds);
226 }
227 }
228 return retval;
229}
230
231QVector3D QSSGRenderNode::getDirection() const
232{
233 const float *dataPtr(globalTransform.data());
234 QVector3D retval(dataPtr[8], dataPtr[9], dataPtr[10]);
235 retval.normalize();
236 return retval;
237}
238
239QVector3D QSSGRenderNode::getScalingCorrectDirection() const
240{
241 QMatrix3x3 theDirMatrix = globalTransform.normalMatrix();
242 QVector3D theOriginalDir(0, 0, -1);
243 QVector3D retval = QSSGUtils::mat33::transform(m: theDirMatrix, v: theOriginalDir);
244 // Should already be normalized, but whatever
245 retval.normalize();
246 return retval;
247}
248
249QVector3D QSSGRenderNode::getGlobalPivot() const
250{
251 QVector3D retval(QSSGUtils::mat44::getPosition(m: localTransform));
252 retval.setZ(retval.z() * -1);
253
254 if (parent && parent->type != QSSGRenderGraphObject::Type::Layer) {
255 const QVector4D direction(retval.x(), retval.y(), retval.z(), 1.0f);
256 const QVector4D result = parent->globalTransform * direction;
257 return QVector3D(result.x(), result.y(), result.z());
258 }
259
260 return retval;
261}
262
263void QSSGRenderNode::calculateMVPAndNormalMatrix(const QMatrix4x4 &inViewProjection, QMatrix4x4 &outMVP, QMatrix3x3 &outNormalMatrix) const
264{
265 outMVP = inViewProjection * globalTransform;
266 outNormalMatrix = calculateNormalMatrix();
267}
268
269QMatrix3x3 QSSGRenderNode::calculateNormalMatrix() const
270{
271 // NB! QMatrix4x4:normalMatrix() uses double precision for determinant
272 // calculations when inverting the matrix, which is good and is important
273 // in practice e.g. in scenes with with small scale factors.
274
275 return globalTransform.normalMatrix();
276}
277
278QT_END_NAMESPACE
279

source code of qtquick3d/src/runtimerender/graphobjects/qssgrendernode.cpp