// Copyright (C) 2008-2012 NVIDIA Corporation. // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qssgrendernode_p.h" #include #include #include #include QT_BEGIN_NAMESPACE QSSGRenderNode::QSSGRenderNode() : QSSGRenderNode(Type::Node) { } QSSGRenderNode::QSSGRenderNode(Type type, QSSGRenderGraphObject::FlagT flags) : QSSGRenderGraphObject(type, flags) { globalTransform = localTransform = calculateTransformMatrix({}, initScale, {}, {}); } QSSGRenderNode::~QSSGRenderNode() = default; void QSSGRenderNode::markDirty(DirtyFlag dirtyFlag) { if ((flags & FlagT(dirtyFlag)) == 0) { // If not already marked flags |= FlagT(dirtyFlag); const bool markSubtreeDirty = ((FlagT(dirtyFlag) & FlagT(DirtyFlag::GlobalValuesDirty)) != 0); if (markSubtreeDirty) { for (auto &cld : children) cld.markDirty(dirtyFlag); } } } void QSSGRenderNode::clearDirty(DirtyFlag dirtyFlag) { flags &= ~FlagT(dirtyFlag); } void QSSGRenderNode::setState(LocalState state, bool on) { const bool changed = (getLocalState(state) != on); if (changed) { // Mark state dirty flags = on ? (flags | FlagT(state)) : (flags & ~FlagT(state)); // Mark state dirty switch (state) { case QSSGRenderNode::LocalState::Active: markDirty(DirtyFlag::ActiveDirty); break; case QSSGRenderNode::LocalState::Pickable: markDirty(DirtyFlag::PickableDirty); break; } } } // Calculate global transform and opacity // Walks up the graph ensure all parents are not dirty so they have // valid global transforms. bool QSSGRenderNode::calculateGlobalVariables() { bool retval = isDirty(DirtyFlag::GlobalValuesDirty); if (retval) { globalOpacity = localOpacity; globalTransform = localTransform; if (parent) { retval = parent->calculateGlobalVariables() || retval; const bool globallyActive = getLocalState(LocalState::Active) && parent->getGlobalState(GlobalState::Active); flags = globallyActive ? (flags | FlagT(GlobalState::Active)) : (flags & ~FlagT(GlobalState::Active)); const bool globallyPickable = getLocalState(LocalState::Pickable) || parent->getGlobalState(GlobalState::Pickable); flags = globallyPickable ? (flags | FlagT(GlobalState::Pickable)) : (flags & ~FlagT(GlobalState::Pickable)); globalOpacity *= parent->globalOpacity; // Skip calculating the transform for non-active nodes if (globallyActive && parent->type != QSSGRenderGraphObject::Type::Layer) { globalTransform = parent->globalTransform * localTransform; if (this == instanceRoot) { globalInstanceTransform = parent->globalTransform; localInstanceTransform = localTransform; } else if (instanceRoot) { globalInstanceTransform = instanceRoot->globalInstanceTransform; //### technically O(n^2) -- we could cache localInstanceTransform if every node in the // tree is guaranteed to have the same instance root. That would require an API change. localInstanceTransform = localTransform; auto *p = parent; while (p) { if (p == instanceRoot) { localInstanceTransform = p->localInstanceTransform * localInstanceTransform; break; } localInstanceTransform = p->localTransform * localInstanceTransform; p = p->parent; } } else { // By default, we do magic: translation is applied to the global instance transform, // while scale/rotation is local localInstanceTransform = localTransform; auto &localInstanceMatrix = *reinterpret_cast(localInstanceTransform.data()); QVector3D localPos{localInstanceMatrix[3][0], localInstanceMatrix[3][1], localInstanceMatrix[3][2]}; localInstanceMatrix[3][0] = 0; localInstanceMatrix[3][1] = 0; localInstanceMatrix[3][2] = 0; globalInstanceTransform = parent->globalTransform; globalInstanceTransform.translate(localPos); } } } else { const bool globallyActive = getLocalState(LocalState::Active); flags = globallyActive ? (flags | FlagT(GlobalState::Active)) : (flags & ~FlagT(GlobalState::Active)); const bool globallyPickable = getLocalState(LocalState::Pickable); flags = globallyPickable ? (flags | FlagT(GlobalState::Pickable)) : (flags & ~FlagT(GlobalState::Pickable)); localInstanceTransform = localTransform; globalInstanceTransform = {}; } // Clear dirty flags clearDirty(DirtyFlag::GlobalValuesDirty); } // We always clear dirty in a reasonable manner but if we aren't active // there is no reason to tell the universe if we are dirty or not. return retval && getLocalState(LocalState::Active); } QMatrix4x4 QSSGRenderNode::calculateTransformMatrix(QVector3D position, QVector3D scale, QVector3D pivot, QQuaternion rotation) { QMatrix4x4 transform; // Offset the origin (this is our pivot point) auto offset = (-pivot * scale); // Scale transform(0, 0) = scale[0]; transform(1, 1) = scale[1]; transform(2, 2) = scale[2]; // Offset (before rotation) transform(0, 3) = offset[0]; transform(1, 3) = offset[1]; transform(2, 3) = offset[2]; // rotate transform = QMatrix4x4{rotation.toRotationMatrix()} * transform; // translate transform(0, 3) += position[0]; transform(1, 3) += position[1]; transform(2, 3) += position[2]; return transform; } void QSSGRenderNode::addChild(QSSGRenderNode &inChild) { // Adding children to a layer does not reset parent // because layers can share children over with other layers if (type != QSSGRenderNode::Type::Layer) { if (inChild.parent && inChild.parent != this) inChild.parent->removeChild(inChild); inChild.parent = this; } children.push_back(inChild); inChild.markDirty(DirtyFlag::GlobalValuesDirty); } void QSSGRenderNode::removeChild(QSSGRenderNode &inChild) { if (Q_UNLIKELY(type != QSSGRenderNode::Type::Layer && inChild.parent != this)) { Q_ASSERT(inChild.parent == this); return; } inChild.parent = nullptr; children.remove(inChild); inChild.markDirty(DirtyFlag::GlobalValuesDirty); } void QSSGRenderNode::removeFromGraph() { if (parent) parent->removeChild(*this); // Orphan all of my children. for (auto it = children.begin(), end = children.end(); it != end;) { auto &removedChild = *it++; children.remove(removedChild); removedChild.parent = nullptr; } } QSSGBounds3 QSSGRenderNode::getBounds(QSSGBufferManager &inManager, bool inIncludeChildren) const { QSSGBounds3 retval; if (inIncludeChildren) retval = getChildBounds(inManager); if (type == QSSGRenderGraphObject::Type::Model) { auto model = static_cast(this); retval.include(inManager.getModelBounds(model)); } return retval; } QSSGBounds3 QSSGRenderNode::getChildBounds(QSSGBufferManager &inManager) const { QSSGBounds3 retval; QSSGBounds3 childBounds; for (auto &child : children) { childBounds = child.getBounds(inManager); if (!childBounds.isEmpty()) { // Transform the bounds into our local space. childBounds.transform(child.localTransform); retval.include(childBounds); } } return retval; } QVector3D QSSGRenderNode::getDirection() const { const float *dataPtr(globalTransform.data()); QVector3D retval(dataPtr[8], dataPtr[9], dataPtr[10]); retval.normalize(); return retval; } QVector3D QSSGRenderNode::getScalingCorrectDirection() const { QMatrix3x3 theDirMatrix = globalTransform.normalMatrix(); QVector3D theOriginalDir(0, 0, -1); QVector3D retval = QSSGUtils::mat33::transform(theDirMatrix, theOriginalDir); // Should already be normalized, but whatever retval.normalize(); return retval; } QVector3D QSSGRenderNode::getGlobalPivot() const { QVector3D retval(QSSGUtils::mat44::getPosition(localTransform)); retval.setZ(retval.z() * -1); if (parent && parent->type != QSSGRenderGraphObject::Type::Layer) { const QVector4D direction(retval.x(), retval.y(), retval.z(), 1.0f); const QVector4D result = parent->globalTransform * direction; return QVector3D(result.x(), result.y(), result.z()); } return retval; } void QSSGRenderNode::calculateMVPAndNormalMatrix(const QMatrix4x4 &inViewProjection, QMatrix4x4 &outMVP, QMatrix3x3 &outNormalMatrix) const { outMVP = inViewProjection * globalTransform; outNormalMatrix = calculateNormalMatrix(); } void QSSGRenderNode::calculateMVPAndNormalMatrix(const QMatrix4x4 &globalTransform, const QMatrix4x4 &inViewProjection, QMatrix4x4 &outMVP, QMatrix3x3 &outNormalMatrix) { outMVP = inViewProjection * globalTransform; outNormalMatrix = globalTransform.normalMatrix(); } QMatrix3x3 QSSGRenderNode::calculateNormalMatrix() const { // NB! QMatrix4x4:normalMatrix() uses double precision for determinant // calculations when inverting the matrix, which is good and is important // in practice e.g. in scenes with with small scale factors. return globalTransform.normalMatrix(); } QT_END_NAMESPACE