/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qquick3dnode_p.h"
#include "qquick3dnode_p_p.h"

#include <QtQuick3DRuntimeRender/private/qssgrendernode_p.h>
#include <QtQuick3DUtils/private/qssgutils_p.h>
#include <QtQuick3DUtils/private/qssgrendereulerangles_p.h>
#include <QtQuick3D/private/qquick3dobject_p_p.h>

#include <QtMath>

QT_BEGIN_NAMESPACE

QQuick3DNodePrivate::QQuick3DNodePrivate()
{

}

QQuick3DNodePrivate::~QQuick3DNodePrivate()
{

}

void QQuick3DNodePrivate::init()
{

}

void QQuick3DNodePrivate::setIsHiddenInEditor(bool isHidden)
{
    Q_Q(QQuick3DNode);
    if (isHidden == m_isHiddenInEditor)
        return;
    m_isHiddenInEditor = isHidden;
    q->update();
}

/*!
    \qmltype Node
    \inherits Object3D
    \inqmlmodule QtQuick3D
    \brief The base component for an object that exists in a 3D Scene.

    Node type can be used to wrap other objects for the purpose of grouping them, or animating them.
    This snippet shows how to use Node to animate a camera.

    \qml
    Node {
        PerspectiveCamera {
            position: Qt.vector3d(0, 0, -600)
        }

        SequentialAnimation on rotation {
            loops: Animation.Infinite
            PropertyAnimation {
                duration: 5000
                to: Qt.vector3d(0, 360, 0)
                from: Qt.vector3d(0, 0, 0)
            }
        }
    }
    \endqml

    Node has to be used also if creating a scene outside of \l View3D, for example for the purpose
    of switching scenes on the fly, or showing the same scene on multiple views.

    \qml
    Node {
        id: standAloneScene

        DirectionalLight {}

        Model {
            source: "#Sphere"
            materials: [ CopperMaterial {} ]
        }

        PerspectiveCamera {
            z: -600
        }
    }

    View3D {
        importScene: standAloneScene
    }
    \endqml
*/

QQuick3DNode::QQuick3DNode(QQuick3DNode *parent)
    : QQuick3DObject(*(new QQuick3DNodePrivate), parent)
{
    Q_D(QQuick3DNode);
    d->init();
}

QQuick3DNode::QQuick3DNode(QQuick3DNodePrivate &dd, QQuick3DNode *parent)
    : QQuick3DObject(dd, parent)
{
    Q_D(QQuick3DNode);
    d->init();
}

QQuick3DNode::~QQuick3DNode() {}

/*!
    \qmlproperty real QtQuick3D::Node::x

    This property contains the x value of the position translation in
    local coordinate space.

    \sa position
*/
float QQuick3DNode::x() const
{
    Q_D(const QQuick3DNode);
    return d->m_position.x();
}

/*!
    \qmlproperty real QtQuick3D::Node::y

    This property contains the y value of the position translation in
    local coordinate space.

    \sa position
*/
float QQuick3DNode::y() const
{
    Q_D(const QQuick3DNode);
    return d->m_position.y();
}

/*!
    \qmlproperty real QtQuick3D::Node::z

    This property contains the z value of the position translation in
    local coordinate space.

    \sa position
*/
float QQuick3DNode::z() const
{
    Q_D(const QQuick3DNode);
    return d->m_position.z();
}

/*!
    \qmlproperty vector3d QtQuick3D::Node::rotation

    This property contains the rotation values for the x, y, and z axis.
    These values are stored as euler angles.
*/
QVector3D QQuick3DNode::rotation() const
{
    Q_D(const QQuick3DNode);
    return d->m_rotation;
}

/*!
    \qmlproperty vector3d QtQuick3D::Node::position

    This property contains the position translation in local coordinate space.

    \sa x, y, z
*/
QVector3D QQuick3DNode::position() const
{
    Q_D(const QQuick3DNode);
    return d->m_position;
}


/*!
    \qmlproperty vector3d QtQuick3D::Node::scale

    This property contains the scale values for the x, y, and z axis.
*/
QVector3D QQuick3DNode::scale() const
{
    Q_D(const QQuick3DNode);
    return d->m_scale;
}

/*!
    \qmlproperty vector3d QtQuick3D::Node::pivot

    This property contains the pivot values for the x, y, and z axis.  These
    values are used as the pivot points when applying rotations to the node.

*/
QVector3D QQuick3DNode::pivot() const
{
    Q_D(const QQuick3DNode);
    return d->m_pivot;
}

/*!
    \qmlproperty real QtQuick3D::Node::opacity

    This property contains the local opacity value of the Node.  Since Node
    objects are not necessarialy visible, this value might not have any affect,
    but this value is inherited by all children of the Node, which might be visible.

*/
float QQuick3DNode::localOpacity() const
{
    Q_D(const QQuick3DNode);
    return d->m_opacity;
}

/*!
    \qmlproperty enumeration QtQuick3D::Node::rotationOrder

    This property defines in what order the \l rotation properties components
    are applied in.

    \value Node.XYZ
           Left-handed XYZ rotation order.
    \value Node.YZX
           Left-handed YZX rotation order.
    \value Node.ZXY
           Left-handed ZXY rotation order.
    \value Node.XZY
           Left-handed XZY rotation order.
    \value Node.YXZ
           Left-handed YXZ rotation order.
    \value Node.ZYX
           Left-handed ZYX rotation order.
    \value Node.XYZr
           Right-handed XYZ rotation order.
    \value Node.YZXr
           Right-handed YZX rotation order.
    \value Node.ZXYr
           Right-handed ZXY rotation order.
    \value Node.XZYr
           Right-handed XZY rotation order.
    \value Node.YXZr
           Right-handed YXZ rotation order.
    \value Node.ZYXr
           Right-handed ZYX rotation order.
*/
QQuick3DNode::RotationOrder QQuick3DNode::rotationOrder() const
{
    Q_D(const QQuick3DNode);
    return d->m_rotationorder;
}

/*!
    \qmlproperty enumeration QtQuick3D::Node::orientation

    This property defines whether the Node is using a right-handed or left-handed
    coordinate system.

    \value Node.LeftHanded
           Left-handed coordinate system.
    \value Node.RightHanded
           Right-handed coordinate system.

*/
QQuick3DNode::Orientation QQuick3DNode::orientation() const
{
    Q_D(const QQuick3DNode);
    return d->m_orientation;
}

/*!
    \qmlproperty bool QtQuick3D::Node::visible

    When this property is true, the Node (and its children) can be visible.

*/
bool QQuick3DNode::visible() const
{
    Q_D(const QQuick3DNode);
    return d->m_visible;
}

QQuick3DNode *QQuick3DNode::parentNode() const
{
    // The parent of a QQuick3DNode should never be anything else than a (subclass
    // of) QQuick3DNode (but the children/leaf nodes can be something else).
    return static_cast<QQuick3DNode *>(parentItem());
}

/*!
    \qmlproperty vector3d QtQuick3D::Node::forward

    This property returns a normalized vector of the nodes forward direction
    in scene space.

    \sa up, right, mapDirectionToScene
*/
QVector3D QQuick3DNode::forward() const
{
    return mapDirectionToScene(QVector3D(0, 0, 1)).normalized();
}

/*!
    \qmlproperty vector3d QtQuick3D::Node::up

    This property returns a normalized vector of the nodes up direction
    in scene space.

    \sa forward, right, mapDirectionToScene
*/
QVector3D QQuick3DNode::up() const
{
    return mapDirectionToScene(QVector3D(0, 1, 0)).normalized();
}

/*!
    \qmlproperty vector3d QtQuick3D::Node::right

    This property returns a normalized vector of the nodes right direction
    in scene space.

    \sa forward, up, mapDirectionToScene
*/
QVector3D QQuick3DNode::right() const
{
    return mapDirectionToScene(QVector3D(1, 0, 0)).normalized();
}
/*!
    \qmlproperty vector3d QtQuick3D::Node::scenePosition

    This property returns the position of the node in scene space.

    \note This is sometimes also referred to as the global position. But
    then in the meaning "global in the 3D world", and not "global to the
    screen or desktop" (which is usually the interpretation in other Qt APIs).
    \note the position will be reported in the same orientation as the node.

    \sa mapPositionToScene
*/
QVector3D QQuick3DNode::scenePosition() const
{
    return mat44::getPosition(sceneTransform());
}

/*!
    \qmlproperty vector3d QtQuick3D::Node::sceneRotation

    This property returns the rotation of the node in scene space.
*/
QVector3D QQuick3DNode::sceneRotation() const
{
    Q_D(const QQuick3DNode);
    return mat44::getRotation(d->sceneRotationMatrix(), EulerOrder(d->m_rotationorder));
}

/*!
    \qmlproperty vector3d QtQuick3D::Node::sceneScale

    This property returns the scale of the node in scene space.
*/
QVector3D QQuick3DNode::sceneScale() const
{
    return mat44::getScale(sceneTransform());
}

/*!
    \qmlproperty matrix4x4 QtQuick3D::Node::sceneTransform

    This property returns the global transform matrix for this node.
    \note the return value will be \c LeftHanded or \c RightHanded
    depending on \l orientation.
*/
QMatrix4x4 QQuick3DNode::sceneTransform() const
{
    Q_D(const QQuick3DNode);
    return d->m_orientation == LeftHanded ? sceneTransformLeftHanded() : sceneTransformRightHanded();
}

/*
    This function returns the global transform matrix for this node
    as a left-handed coordinate system, regardless of orientation.
*/
QMatrix4x4 QQuick3DNode::sceneTransformLeftHanded() const
{
    QMatrix4x4 transform = sceneTransformRightHanded();
    mat44::flip(transform);
    return transform;
}

/*
    This function returns the global transform matrix for this node
    as a right-handed coordinate system, regardless of orientation.
*/
QMatrix4x4 QQuick3DNode::sceneTransformRightHanded() const
{
    Q_D(const QQuick3DNode);
    if (d->m_sceneTransformDirty)
        const_cast<QQuick3DNodePrivate *>(d)->calculateGlobalVariables();
    return d->m_sceneTransformRightHanded;
}

void QQuick3DNodePrivate::calculateGlobalVariables()
{
    Q_Q(QQuick3DNode);
    m_sceneTransformDirty = false;
    QMatrix4x4 localTransformRightHanded = calculateLocalTransformRightHanded();
    QQuick3DNode *parent = q->parentNode();
    if (!parent) {
        m_sceneTransformRightHanded = localTransformRightHanded;
        m_hasInheritedUniformScale = true;
        return;
    }
    QQuick3DNodePrivate *privateParent = QQuick3DNodePrivate::get(parent);

    if (privateParent->m_sceneTransformDirty)
        privateParent->calculateGlobalVariables();
    m_sceneTransformRightHanded = privateParent->m_sceneTransformRightHanded * localTransformRightHanded;

    // Check if we have an ancestor with non-uniform scale. This will decide whether
    // or not we can use the sceneTransform to extract sceneRotation and sceneScale.
    m_hasInheritedUniformScale = privateParent->m_hasInheritedUniformScale;
    if (m_hasInheritedUniformScale) {
        const QVector3D ps = privateParent->m_scale;
        m_hasInheritedUniformScale = qFuzzyCompare(ps.x(), ps.y()) && qFuzzyCompare(ps.x(), ps.z());
    }
}

QMatrix4x4 QQuick3DNodePrivate::calculateLocalTransformRightHanded()
{
    const QVector3D pivot = -m_pivot * m_scale;
    QMatrix4x4 localTransform;

    localTransform(0, 0) = m_scale[0];
    localTransform(1, 1) = m_scale[1];
    localTransform(2, 2) = m_scale[2];

    localTransform(0, 3) = pivot[0];
    localTransform(1, 3) = pivot[1];
    localTransform(2, 3) = pivot[2];

    localTransform = localRotationMatrix() * localTransform;

    localTransform(0, 3) += m_position[0];
    localTransform(1, 3) += m_position[1];
    localTransform(2, 3) += m_position[2];

    if (Q_LIKELY(m_orientation == QQuick3DNode::LeftHanded))
        mat44::flip(localTransform);

    return localTransform;
}

QMatrix4x4 QQuick3DNodePrivate::localRotationMatrix() const
{
    const QVector3D radians = degToRad(m_rotation);
    return QSSGEulerAngleConverter::createRotationMatrix(radians, EulerOrder(m_rotationorder));
}

QMatrix4x4 QQuick3DNodePrivate::sceneRotationMatrix() const
{
    Q_Q(const QQuick3DNode);

    if (m_sceneTransformDirty) {
        // Ensure m_hasInheritedUniformScale is up to date
        const_cast<QQuick3DNodePrivate *>(this)->calculateGlobalVariables();
    }

    if (m_hasInheritedUniformScale) {
        // When we know that every node up to the root have a uniform scale, we can extract the
        // rotation directly from the sceneTransform(). This is optimizing, since we reuse that
        // matrix for more than just calculating the sceneRotation.
        QMatrix4x4 rotationMatrix = q->sceneTransform();
        mat44::normalize(rotationMatrix);
        return rotationMatrix;
    } else {
        // When we have an ancestor that has a non-uniform scale, we cannot extract
        // the rotation from the sceneMatrix directly. Instead, we need to calculate
        // it separately, which is slightly more costly.
        const QMatrix4x4 parentRotationMatrix = QQuick3DNodePrivate::get(q->parentNode())->sceneRotationMatrix();
        return localRotationMatrix() * parentRotationMatrix;
    }
}

QQuick3DObject::Type QQuick3DNode::type() const
{
    return QQuick3DObject::Node;
}

void QQuick3DNodePrivate::emitChangesToSceneTransform()
{
    Q_Q(QQuick3DNode);
    const QVector3D prevPosition = mat44::getPosition(m_sceneTransformRightHanded);
    const QVector3D prevRotation = mat44::getRotation(m_sceneTransformRightHanded, EulerOrder(m_rotationorder));
    const QVector3D prevScale = mat44::getScale(m_sceneTransformRightHanded);

    calculateGlobalVariables();

    const QVector3D newPosition = mat44::getPosition(m_sceneTransformRightHanded);
    const QVector3D newRotation = mat44::getRotation(m_sceneTransformRightHanded, EulerOrder(m_rotationorder));
    const QVector3D newScale = mat44::getScale(m_sceneTransformRightHanded);

    const bool positionChanged = prevPosition != newPosition;
    const bool rotationChanged = prevRotation != newRotation;
    const bool scaleChanged = prevScale != newScale;

    if (!positionChanged && !rotationChanged && !scaleChanged)
        return;

    emit q->sceneTransformChanged();

    if (positionChanged)
        emit q->scenePositionChanged();
    if (rotationChanged)
        emit q->sceneRotationChanged();
    if (scaleChanged)
        emit q->sceneScaleChanged();
}

bool QQuick3DNodePrivate::isSceneTransformRelatedSignal(const QMetaMethod &signal) const
{
    // Return true if its likely that we need to emit
    // the given signal when our global transform changes.
    static const QMetaMethod sceneTransformSignal = QMetaMethod::fromSignal(&QQuick3DNode::sceneTransformChanged);
    static const QMetaMethod scenePositionSignal = QMetaMethod::fromSignal(&QQuick3DNode::scenePositionChanged);
    static const QMetaMethod sceneRotationSignal = QMetaMethod::fromSignal(&QQuick3DNode::sceneRotationChanged);
    static const QMetaMethod sceneScaleSignal = QMetaMethod::fromSignal(&QQuick3DNode::sceneScaleChanged);

    return (signal == sceneTransformSignal
            || signal == scenePositionSignal
            || signal == sceneRotationSignal
            || signal == sceneScaleSignal);
}

void QQuick3DNode::connectNotify(const QMetaMethod &signal)
{
    Q_D(QQuick3DNode);
    // Since we want to avoid calculating the global transform in the frontend
    // unnecessary, we keep track of the number of connections/QML bindings
    // that needs it. If there are no connections, we can skip calculating it
    // whenever our geometry changes (unless someone asks for it explicitly).
    if (d->isSceneTransformRelatedSignal(signal))
        d->m_sceneTransformConnectionCount++;
}

void QQuick3DNode::disconnectNotify(const QMetaMethod &signal)
{
    Q_D(QQuick3DNode);
    if (d->isSceneTransformRelatedSignal(signal))
        d->m_sceneTransformConnectionCount--;
}

void QQuick3DNode::componentComplete()
{
    Q_D(QQuick3DNode);
    QQuick3DObject::componentComplete();
    if (d->m_sceneTransformConnectionCount > 0)
        d->emitChangesToSceneTransform();
}

void QQuick3DNodePrivate::markSceneTransformDirty()
{
    Q_Q(QQuick3DNode);
    // Note: we recursively set m_sceneTransformDirty to true whenever our geometry
    // changes. But we only set it back to false if someone actually queries our global
    // transform (because only then do we need to calculate it). This means that if no
    // one ever does that, m_sceneTransformDirty will remain true, perhaps through out
    // the life time of the node. This is in contrast with the backend, which need to
    // update dirty transform nodes for every scene graph sync (and clear the backend
    // dirty transform flags - QQuick3DObjectPrivate::dirtyAttributes).
    // This means that for most nodes, calling markSceneTransformDirty() should be
    // cheap, since we normally expect to return early in the following test.
    if (m_sceneTransformDirty)
        return;

    m_sceneTransformDirty = true;

    if (m_sceneTransformConnectionCount > 0)
        emitChangesToSceneTransform();

    auto children = QQuick3DObjectPrivate::get(q)->childItems;
    for (auto child : children) {
        if (auto node = qobject_cast<QQuick3DNode *>(child)) {
            QQuick3DNodePrivate::get(node)->markSceneTransformDirty();
        }
    }
}

void QQuick3DNode::setX(float x)
{
    Q_D(QQuick3DNode);
    if (qFuzzyCompare(d->m_position.x(), x))
        return;

    d->m_position.setX(x);
    d->markSceneTransformDirty();
    emit positionChanged();
    emit xChanged();
    update();
}

void QQuick3DNode::setY(float y)
{
    Q_D(QQuick3DNode);
    if (qFuzzyCompare(d->m_position.y(), y))
        return;

    d->m_position.setY(y);
    d->markSceneTransformDirty();
    emit positionChanged();
    emit yChanged();
    update();
}

void QQuick3DNode::setZ(float z)
{
    Q_D(QQuick3DNode);
    if (qFuzzyCompare(d->m_position.z(), z))
        return;

    d->m_position.setZ(z);
    d->markSceneTransformDirty();
    emit positionChanged();
    emit zChanged();
    update();
}

void QQuick3DNode::setRotation(const QVector3D &rotation)
{
    Q_D(QQuick3DNode);
    if (d->m_rotation == rotation)
        return;

    d->m_rotation = rotation;
    d->markSceneTransformDirty();
    emit rotationChanged();
    update();
}

void QQuick3DNode::setPosition(const QVector3D &position)
{
    Q_D(QQuick3DNode);
    if (d->m_position == position)
        return;

    const bool xUnchanged = qFuzzyCompare(position.x(), d->m_position.x());
    const bool yUnchanged = qFuzzyCompare(position.y(), d->m_position.y());
    const bool zUnchanged = qFuzzyCompare(position.z(), d->m_position.z());

    d->m_position = position;
    d->markSceneTransformDirty();
    emit positionChanged();

    if (!xUnchanged)
        emit xChanged();
    if (!yUnchanged)
        emit yChanged();
    if (!zUnchanged)
        emit zChanged();

    update();
}

void QQuick3DNode::setScale(const QVector3D &scale)
{
    Q_D(QQuick3DNode);
    if (d->m_scale == scale)
        return;

    d->m_scale = scale;
    d->markSceneTransformDirty();
    emit scaleChanged();
    update();
}

void QQuick3DNode::setPivot(const QVector3D &pivot)
{
    Q_D(QQuick3DNode);
    if (d->m_pivot == pivot)
        return;

    d->m_pivot = pivot;
    d->markSceneTransformDirty();
    emit pivotChanged();
    update();
}

void QQuick3DNode::setLocalOpacity(float opacity)
{
    Q_D(QQuick3DNode);
    if (qFuzzyCompare(d->m_opacity, opacity))
        return;

    d->m_opacity = opacity;
    emit localOpacityChanged();
    update();
}

void QQuick3DNode::setRotationOrder(QQuick3DNode::RotationOrder rotationorder)
{
    Q_D(QQuick3DNode);
    if (d->m_rotationorder == rotationorder)
        return;

    d->m_rotationorder = rotationorder;
    d->markSceneTransformDirty();
    emit rotationOrderChanged();
    update();
}

void QQuick3DNode::setOrientation(QQuick3DNode::Orientation orientation)
{
    Q_D(QQuick3DNode);
    if (d->m_orientation == orientation)
        return;

    d->m_orientation = orientation;
    d->markSceneTransformDirty();
    emit orientationChanged();
    update();
}

void QQuick3DNode::setVisible(bool visible)
{
    Q_D(QQuick3DNode);
    if (d->m_visible == visible)
        return;

    d->m_visible = visible;
    emit visibleChanged();
    update();
}

/*!
    \qmlproperty enumeration QQuick3D::Node::TransformSpace

    Defines the relationship of the axis.

    \value Node.LocalSpace
           Axis is relative to the local orientation of this node.
    \value Node.ParentSpace
           Axis is relative to the local orientation of the parent node.
    \value Node.SceneSpace
           Axis is relative to the scene.
*/

/*!
    \qmlmethod QQuick3D::Node::rotate(real degrees, vector3d axis, TransformSpace space)

    Rotates this node around an \a axis by the given \a degrees. The specified
    rotation will be added to the node's current rotation. The axis can
    be specified relative to different \l {TransformSpace}{\a {space}s}.
*/
void QQuick3DNode::rotate(qreal degrees, const QVector3D &axis, TransformSpace space)
{
    Q_D(QQuick3DNode);

    const QQuaternion addRotationQuat = QQuaternion::fromAxisAndAngle(axis, float(degrees));
    const QMatrix4x4 addRotationMatrix = QMatrix4x4(addRotationQuat.toRotationMatrix());
    QMatrix4x4 newRotationMatrix;

    switch (space) {
    case LocalSpace:
        newRotationMatrix = d->localRotationMatrix() * addRotationMatrix;
        break;
    case ParentSpace:
        newRotationMatrix = addRotationMatrix * d->localRotationMatrix();
        break;
    case SceneSpace:
        if (const auto parent = parentNode()) {
            const QMatrix4x4 lrm = d->localRotationMatrix();
            const QMatrix4x4 prm = QQuick3DNodePrivate::get(parent)->sceneRotationMatrix();
            newRotationMatrix = prm.inverted() * addRotationMatrix * prm * lrm;
        } else {
            newRotationMatrix = d->localRotationMatrix() * addRotationMatrix;
        }
        break;
    }

    const QVector3D newRotationEuler = mat44::getRotation(newRotationMatrix, EulerOrder(d->m_rotationorder));

    if (d->m_rotation == newRotationEuler)
        return;

    d->m_rotation = newRotationEuler;
    d->markSceneTransformDirty();
    emit rotationChanged();
    update();
}

QSSGRenderGraphObject *QQuick3DNode::updateSpatialNode(QSSGRenderGraphObject *node)
{
    Q_D(QQuick3DNode);
    if (!node) {
        markAllDirty();
        node = new QSSGRenderNode();
    }

    auto spacialNode = static_cast<QSSGRenderNode *>(node);
    bool transformIsDirty = false;
    if (spacialNode->position != d->m_position) {
        transformIsDirty = true;
        spacialNode->position = d->m_position;
    }

    const QVector3D rotation = degToRad(d->m_rotation);
    if (spacialNode->rotation != rotation) {
        transformIsDirty = true;
        spacialNode->rotation = rotation;
    }

    if (spacialNode->scale != d->m_scale) {
        transformIsDirty = true;
        spacialNode->scale = d->m_scale;
    }
    if (spacialNode->pivot != d->m_pivot) {
        transformIsDirty = true;
        spacialNode->pivot = d->m_pivot;
    }

    if (spacialNode->rotationOrder != EulerOrder(d->m_rotationorder)) {
        transformIsDirty = true;
        spacialNode->rotationOrder = EulerOrder(d->m_rotationorder);
    }

    spacialNode->localOpacity = d->m_opacity;

    const bool leftHanded = d->m_orientation == LeftHanded;
    if (spacialNode->flags.testFlag(QSSGRenderNode::Flag::LeftHanded) != leftHanded) {
        transformIsDirty = true;
        spacialNode->flags.setFlag(QSSGRenderNode::Flag::LeftHanded, leftHanded);
    }

    // The Hidden in Editor flag overrides the visible value
    if (d->m_isHiddenInEditor)
        spacialNode->flags.setFlag(QSSGRenderNode::Flag::Active, false);
    else
        spacialNode->flags.setFlag(QSSGRenderNode::Flag::Active, d->m_visible);

    if (transformIsDirty) {
        spacialNode->markDirty(QSSGRenderNode::TransformDirtyFlag::TransformIsDirty);
        spacialNode->calculateGlobalVariables();
        // Still needs to be marked dirty if it will show up correctly in the backend
        // Note: no longer sure if this is still needed after we now do our own
        // calculation of the global matrix from the front-end.
        spacialNode->flags.setFlag(QSSGRenderNode::Flag::Dirty, true);
    } else {
        spacialNode->markDirty(QSSGRenderNode::TransformDirtyFlag::TransformNotDirty);
    }

    return spacialNode;
}

/*!
    \qmlmethod vector3d QtQuick3D::Node::mapPositionToScene(vector3d localPosition)

    Transforms \a localPosition from local space to scene space.

    \note "Scene space" is sometimes also reffered to as the "global space". But
    then in the meaning "global in the 3D world", and not "global to the
    screen or desktop" (which is usually the interpretation in other Qt APIs).

    \sa mapPositionFromScene, mapPositionToNode, mapPositionFromNode
*/
QVector3D QQuick3DNode::mapPositionToScene(const QVector3D &localPosition) const
{
    return mat44::transform(sceneTransform(), localPosition);
}

/*!
    \qmlmethod vector3d QtQuick3D::Node::mapPositionFromScene(vector3d scenePosition)

    Transforms \a scenePosition from scene space to local space.

    \sa mapPositionToScene, mapPositionToNode, mapPositionFromNode
*/
QVector3D QQuick3DNode::mapPositionFromScene(const QVector3D &scenePosition) const
{
    return mat44::transform(sceneTransform().inverted(), scenePosition);
}

/*!
    \qmlmethod vector3d QtQuick3D::Node::mapPositionToNode(QtQuick3D::Node node, vector3d localPosition)

    Transforms \a localPosition from the local space of this node to
    the local space of \a node.

    \sa mapPositionToScene, mapPositionFromScene, mapPositionFromNode
*/
QVector3D QQuick3DNode::mapPositionToNode(QQuick3DNode *node, const QVector3D &localPosition) const
{
    return node->mapPositionFromScene(mapPositionToScene(localPosition));
}

/*!
    \qmlmethod vector3d QtQuick3D::Node::mapPositionFromNode(QtQuick3D::Node node, vector3d localPosition)

    Transforms \a localPosition from the local space of \a node to
    the local space of this node.

    \sa mapPositionToScene, mapPositionFromScene, mapPositionToNode
*/
QVector3D QQuick3DNode::mapPositionFromNode(QQuick3DNode *node, const QVector3D &localPosition) const
{
    return mapPositionFromScene(node->mapPositionToScene(localPosition));
}

/*!
    \qmlmethod vector3d QtQuick3D::Node::mapDirectionToScene(vector3d localDirection)

    Transforms \a localDirection from local space to scene space.
    The return value is not affected by the (inherited) scale or
    position of the node.

    \note the return value will have the same length as \a localDirection
    (i.e. not normalized).

    \sa mapDirectionFromScene, mapDirectionToNode, mapDirectionFromNode
*/
QVector3D QQuick3DNode::mapDirectionToScene(const QVector3D &localDirection) const
{
    QMatrix3x3 theDirMatrix = mat44::getUpper3x3(sceneTransform());
    theDirMatrix = mat33::getInverse(theDirMatrix).transposed();
    return mat33::transform(theDirMatrix, localDirection);
}

/*!
    \qmlmethod vector3d QtQuick3D::Node::mapDirectionFromScene(vector3d sceneDirection)

    Transforms \a sceneDirection from scene space to local space.
    The return value is not affected by the (inherited) scale or
    position of the node.

    \note the return value will have the same length as \a sceneDirection
    (i.e not normalized).

    \sa mapDirectionToScene, mapDirectionToNode, mapDirectionFromNode
*/
QVector3D QQuick3DNode::mapDirectionFromScene(const QVector3D &sceneDirection) const
{
    QMatrix3x3 theDirMatrix = mat44::getUpper3x3(sceneTransform());
    theDirMatrix = theDirMatrix.transposed();
    return mat33::transform(theDirMatrix, sceneDirection);
}

/*!
    \qmlmethod vector3d QtQuick3D::Node::mapDirectionToNode(QtQuick3D::Node node, vector3d localDirection)

    Transforms \a localDirection from this nodes local space to the
    local space of \a node.
    The return value is not affected by the (inherited) scale or
    position of the node.

    \note the return value will have the same length as \a localDirection
    (i.e. not normalized).

    \sa mapDirectionFromNode, mapDirectionFromScene, mapDirectionToScene
*/
QVector3D QQuick3DNode::mapDirectionToNode(QQuick3DNode *node, const QVector3D &localDirection) const
{
    return node->mapDirectionFromScene(mapDirectionToScene(localDirection));
}

/*!
    \qmlmethod vector3d QtQuick3D::Node::mapDirectionFromNode(QtQuick3D::Node node, vector3d localDirection)

    Transforms \a localDirection from the local space of \a node to the
    local space of this node.
    The return value is not affected by the (inherited) scale or
    position of the node.

    \note the return value will have the same length as \a localDirection
    (i.e. not normalized).

    \sa mapDirectionToNode, mapDirectionFromScene, mapDirectionToScene
*/
QVector3D QQuick3DNode::mapDirectionFromNode(QQuick3DNode *node, const QVector3D &localDirection) const
{
    return mapDirectionFromScene(node->mapDirectionToScene(localDirection));
}

void QQuick3DNode::markAllDirty()
{
    Q_D(QQuick3DNode);

    d->markSceneTransformDirty();
    QQuick3DObject::markAllDirty();
}

QT_END_NAMESPACE
