инвертировать родительское преобразование сущности qt3d (не работает для scale3D) - PullRequest
3 голосов
/ 02 апреля 2020

По причинам, которые являются более сложными, чем этот минимальный тестовый сценарий, мне нужно иметь сущность (childEntity, пурпурный прямоугольник) дочерний элемент другой сущности (parentEntity, голубой прямоугольник), но childEntity должен быть независимым из преобразования parentEntity.

Поэтому я добавляю этот обработчик:

QtQuick.Connections {
    target: parentTransform
    onMatrixChanged: {
        // cancel parent's transform
        var m = parentTransform.matrix
        var i = m.inverted()
        childTransform.matrix = i

        // debug:
        console.log(parentTransform.matrix.times(i))
    }
}

, который хорошо работает для отмены перемещения и поворота родительского элемента, но не для масштаба.

Если родительский scale3D не равен [1,1,1] и также установлено вращение, тогда childEntity выглядит искаженным, несмотря на то, что произведение parentTransform.matrix раз childTransform.matrix дает идентичность 4x4. Почему?

Screenshot

Минимальный тестовый случай: (загрузить в QQuickView)

import QtQml 2.12 as QtQml
import QtQuick 2.12 as QtQuick
import QtQuick.Controls 2.12 as QtQuickControls
import QtQuick.Scene3D 2.0

import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
import Qt3D.Extras 2.0

Scene3D {
    function change_translation_and_rotation() {
        parentTransform.translation.x = 0.1
        parentTransform.translation.y = 0.5
        parentTransform.translation.z = 2
        parentTransform.rotationX = 30
        parentTransform.rotationY = 60
        parentTransform.rotationZ = 10
    }

    function change_rotation_and_scale() {
        parentTransform.rotationX = 30
        parentTransform.rotationY = 60
        parentTransform.rotationZ = 10
        parentTransform.scale3D.x = 0.1
        parentTransform.scale3D.y = 0.5
        parentTransform.scale3D.z = 2
    }

    function reset_transform() {
        parentTransform.translation.x = -0.5
        parentTransform.translation.y = 0
        parentTransform.translation.z = 0.5
        parentTransform.rotationX = 0
        parentTransform.rotationY = 0
        parentTransform.rotationZ = 0
        parentTransform.scale3D.x = 1
        parentTransform.scale3D.y = 1
        parentTransform.scale3D.z = 1
    }

    data: [
        QtQml.Connections {
            target: parentTransform
            onMatrixChanged: {
                // cancel parent's transform
                var m = parentTransform.matrix
                var i = m.inverted()
                childTransform.matrix = i

                // debug:
                console.log(parentTransform.matrix.times(i))
            }
        },

        QtQuick.Column {
            spacing: 5
            QtQuick.Repeater {
                id: buttons
                model: ["change_translation_and_rotation", "change_rotation_and_scale", "reset_transform"]
                delegate: QtQuickControls.Button {
                    text: modelData.replace(/_/g, ' ')
                    font.bold: focus
                    onClicked: {focus = true; scene3d[modelData]()}
                }
            }
        }
    ]

    id: scene3d
    anchors.fill: parent
    aspects: ["render", "logic", "input"]

    Entity {
        id: root
        components: [RenderSettings {activeFrameGraph: ForwardRenderer {camera: mainCamera}}, InputSettings {}]

        Camera {
            id: mainCamera
            projectionType: CameraLens.PerspectiveProjection
            fieldOfView: 45
            aspectRatio: 16/9
            nearPlane : 0.1
            farPlane : 1000.0
            position: Qt.vector3d(-3.46902, 4.49373, -3.78577)
            upVector: Qt.vector3d(0.41477, 0.789346, 0.452641)
            viewCenter: Qt.vector3d(0.0, 0.5, 0.0)
        }

        OrbitCameraController {
            camera: mainCamera
        }

        Entity {
            id: parentEntity
            components: [
                CuboidMesh {
                    xExtent: 1
                    yExtent: 1
                    zExtent: 1
                },
                PhongMaterial {
                    ambient: "#6cc"
                },
                Transform {
                    id: parentTransform
                    translation: Qt.vector3d(-0.5, 0, 0.5)
                }
            ]

            Entity {
                id: childEntity
                components: [
                    CuboidMesh {
                        xExtent: 0.5
                        yExtent: 0.5
                        zExtent: 0.5
                    },
                    PhongMaterial {
                        ambient: "#c6c"
                    },
                    Transform {
                        id: childTransform
                        translation: Qt.vector3d(-0.5, 0, 0.5)
                    }
                ]
            }
        }

        QtQuick.Component.onCompleted: reset_transform()
    }
}

1 Ответ

2 голосов
/ 20 апреля 2020

Проблема в том, что узел QTransform не хранит преобразование как общую матрицу 4x4. Скорее это разбивает матрицу на 3 преобразования, которые применяются в фиксированном порядке:

  • S - диагональная матрица масштабирования
  • R - матрица вращения
  • T - перевод

и затем применяет его в порядке T * R * S * X к точке X.

Документация по свойству матрицы пишет об этом этапе декомпозиции https://doc.qt.io/qt-5/qt3dcore-qtransform.html#matrix -prop

Таким образом, когда преобразование для родителя имеет вид M = T * R * S, тогда обратное значение для дочернего элемента будет M ^ -1 = S ^ -1 * R ^ - 1 * Т ^ -1. Установка обратного для дочернего QTransform попытается разложить его таким же образом:

M ^ -1 = T_i * R_i * S_i = S ^ -1 * R ^ -1 * T ^ -1

Это не работает, потому что особенно S и R не коммутируют так.

Вы можете проверить это в своем коде, сравнив значения childTransform.matrix и i после установки childTransform.matrix .

Я думаю, что единственное решение - это иметь 3 QTransform для сущностей, вложенных над дочерним элементом, чтобы реализовать правильный порядок инверсий как S ^ -1 * R ^ -1 * T ^ -1.

Довольно просто вычислить обратное S ^ -1, R ^ -1 T ^ -1 из соответствующих параметров в родительском QTransform.

...