如何在 3D 中为 QML 旋转变换设置动画并正确插入 [英] How to animate and propertly intepolate a QML rotation transform in 3D

查看:76
本文介绍了如何在 3D 中为 QML 旋转变换设置动画并正确插入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这里的代码示例:

import QtQuick 2.0
Item {
    width: 200; height: 200
    Rectangle {
        width: 100; height: 100
        anchors.centerIn: parent
        color: "#00FF00"
        Rectangle {
            color: "#FF0000"
            width: 10; height: 10
            anchors.top: parent.top
            anchors.right: parent.right
        }
    }
}

将产生此输出:

现在我想从这个绿色矩形的中心应用 3D 旋转.首先,我想在 X 上旋转 -45 度(向下弯曲),然后在 Y 上旋转 -60 度(向左转).

Now I want to apply a 3D rotation from the center of this green rectangle. First, I want to rotate on X by -45 degrees (bowing down), then on Y by -60 degrees (turning left).

我在侧面使用了以下使用 GLM 剪下的 C++ 代码来帮助我计算轴和角度:

I used the following c++ code snipped using GLM on the side to help me calculate the axis and angle:

// generate rotation matrix from euler in X-Y-Z order
// please note that GLM uses radians, not degrees
glm::mat4 rotationMatrix = glm::eulerAngleXY(glm::radians(-45.0f), glm::radians(-60.0f));

// convert the rotation matrix into a quaternion
glm::quat quaternion = glm::toQuat(rotationMatrix);

// extract the rotation axis from the quaternion
glm::vec3 axis = glm::axis(quaternion);

// extract the rotation angle from the quaternion
// and also convert it back to degrees for QML
double angle = glm::degrees(glm::angle(quaternion)); 

这个小 C++ 程序的输出给了我 {-0.552483, -0.770076, 0.318976} 的轴和 73.7201 的角度.所以我更新了我的示例代码:

The output of this little C++ program gave me an axis of {-0.552483, -0.770076, 0.318976} and an angle of 73.7201. So I updated my sample code to this:

import QtQuick 2.0
Item {
    width: 200; height: 200
    Rectangle {
        width: 100; height: 100
        anchors.centerIn: parent
        color: "#00FF00"
        Rectangle {
            color: "#FF0000"
            width: 10; height: 10
            anchors.top: parent.top
            anchors.right: parent.right
        }
        transform: Rotation {
            id: rot
            origin.x: 50; origin.y: 50
            axis: Qt.vector3d(-0.552483, -0.770076, 0.318976)
            angle: 73.7201
        }
    }
}

这正是我想看到的:

到目前为止一切顺利.现在是困难的部分.我如何动画这个?例如,如果我想从 {45.0, 60.0, 0} 转到 {45.0, 60.0, 90.0}.换句话说,我想从这里开始动画

So far so good. Now comes the hard part. How do I animate this? For example, if I want to go from {45.0, 60.0, 0} to {45.0, 60.0, 90.0}. In other word, I want to animate from here

到这里

我在这里插入了目标旋转

I plugged that target rotation here

// generate rotation matrix from euler in X-Y-Z order
// please note that GLM uses radians, not degrees
glm::mat4 rotationMatrix = glm::eulerAngleXYZ(glm::radians(-45.0f), glm::radians(-60.0f), glm::radians(90.0f);

// convert the rotation matrix into a quaternion
glm::quat quaternion = glm::toQuat(rotationMatrix);

// extract the rotation axis from the quaternion
glm::vec3 axis = glm::axis(quaternion);

// extract the rotation angle from the quaternion
// and also convert it back to degrees for QML
double angle = glm::degrees(glm::angle(quaternion)); 

这给了我 {-0.621515, -0.102255, 0.7767} 的轴和 129.007

所以我将此动画添加到我的示例中

So I added this animation to my sample

ParallelAnimation {
    running: true
    Vector3dAnimation {
        target: rot
        property: "axis"
        from: Qt.vector3d(-0.552483, -0.770076, 0.318976)
        to: Qt.vector3d(-0.621515, -0.102255, 0.7767)
        duration: 4000
    }
    NumberAnimation {
        target: rot;
        property: "angle";
        from: 73.7201; to: 129.007;
        duration: 4000;
    }
}

哪个几乎"有效.问题是,如果您尝试一下,您会看到在动画的前半部分旋转完全偏离了其所需的旋转轴,但在动画的后半部分会自行修复.起始旋转很好,目标旋转很好,但中间发生的一切都不够好.如果我使用较小的角度(例如 45 度而不是 90 度)会更好,如果我使用较大的角度(例如 180 度而不是 45 度)会更糟,因为它只会在随机方向旋转,直到达到最终目标.

Which 'almost' works. The problem is, if you try it, you will see that the rotation goes completely off its desired rotation axis for the first half of the animation, but fixes itself for the last half of the animation. The starting rotation is good, the target rotation is good, but whatever that happens in between is not good enough. It is better if I use smaller angles like 45 degrees instead of 90 degrees, and is going to be worst if I use larger angles like 180 degrees instead of 45 degrees, where it just spins in random directions until it reaches its final targets.

如何让这个动画在开始旋转和目标旋转之间看起来正确?

How do I get this animation to look right between the start rotation and the target rotation?

------------------- 编辑-------------------

------------------- EDIT -------------------

我再添加一个条件:我正在寻找的答案必须绝对提供与我上面提供的屏幕截图相同的输出.

I am adding one more criteria: The answer I am looking for must absolutely provide an identical output as the screenshots I provided above.

例如,在 3 个单独的旋转变换中拆分 3 个旋转轴不会给我正确的结果

For example, splitting the 3 rotation axis in 3 separate rotation transforms doesn't give me the right results

    transform: [
        Rotation {
            id: zRot
            origin.x: 50; origin.y: 50;
            angle: 0
        },
        Rotation {
            id: xRot
            origin.x: 50; origin.y: 50;
            angle: -45
            axis { x: 1; y: 0; z: 0 }
        },
        Rotation {
            id: yRot
            origin.x: 50; origin.y: 50;
            angle: -60
            axis { x: 0; y: 1; z: 0 }
        }
    ]

会给我这个:

哪个不正确.

推荐答案

我自己解决了问题.我完全忘记了 Qt 不做球面线性插值!!!一旦我完成了自己的 slerp 功能,一切都完美无缺.

I solved my own problem. I completely forgot that Qt doesn't do spherical linear interpolation!!! As soon as I did my own slerp function, it all worked perfectly.

这是我为寻求答案的人提供的代码:

Here's my code for those who are seeking the answer:

import QtQuick 2.0

Item {

    function angleAxisToQuat(angle, axis) {
        var a = angle * Math.PI / 180.0;
        var s = Math.sin(a * 0.5);
        var c = Math.cos(a * 0.5);
        return Qt.quaternion(c, axis.x * s, axis.y * s, axis.z * s);
    }

    function multiplyQuaternion(q1, q2) {
        return Qt.quaternion(q1.scalar * q2.scalar - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z,
                             q1.scalar * q2.x + q1.x * q2.scalar + q1.y * q2.z - q1.z * q2.y,
                             q1.scalar * q2.y + q1.y * q2.scalar + q1.z * q2.x - q1.x * q2.z,
                             q1.scalar * q2.z + q1.z * q2.scalar + q1.x * q2.y - q1.y * q2.x);
    }

    function eulerToQuaternionXYZ(x, y, z) {
        var quatX = angleAxisToQuat(x, Qt.vector3d(1, 0, 0));
        var quatY = angleAxisToQuat(y, Qt.vector3d(0, 1, 0));
        var quatZ = angleAxisToQuat(z, Qt.vector3d(0, 0, 1));
        return multiplyQuaternion(multiplyQuaternion(quatX, quatY), quatZ)
    }

    function slerp(start, end, t) {

        var halfCosTheta = ((start.x * end.x) + (start.y * end.y)) + ((start.z * end.z) + (start.scalar * end.scalar));

        if (halfCosTheta < 0.0)
        {
            end.scalar = -end.scalar
            end.x = -end.x
            end.y = -end.y
            end.z = -end.z
            halfCosTheta = -halfCosTheta;
        }

        if (Math.abs(halfCosTheta) > 0.999999)
        {
            return Qt.quaternion(start.scalar + (t * (end.scalar - start.scalar)),
                                 start.x      + (t * (end.x      - start.x     )),
                                 start.y      + (t * (end.y      - start.y     )),
                                 start.z      + (t * (end.z      - start.z     )));
        }

        var halfTheta = Math.acos(halfCosTheta);
        var s1 = Math.sin((1.0 - t) * halfTheta);
        var s2 = Math.sin(t * halfTheta);
        var s3 = 1.0 / Math.sin(halfTheta);
        return Qt.quaternion((s1 * start.scalar + s2 * end.scalar) * s3,
                             (s1 * start.x      + s2 * end.x     ) * s3,
                             (s1 * start.y      + s2 * end.y     ) * s3,
                             (s1 * start.z      + s2 * end.z     ) * s3);
    }

    function getAxis(quat) {
        var tmp1 = 1.0 - quat.scalar * quat.scalar;
        if (tmp1 <= 0) return Qt.vector3d(0.0, 0.0, 1.0);
        var tmp2 = 1 / Math.sqrt(tmp1);
        return Qt.vector3d(quat.x * tmp2, quat.y * tmp2, quat.z * tmp2);
    }

    function getAngle(quat) {
        return Math.acos(quat.scalar) * 2.0 * 180.0 / Math.PI;
    }

    width: 200; height: 200
    Rectangle {
        width: 100; height: 100
        anchors.centerIn: parent
        color: "#00FF00"
        Rectangle {
            color: "#FF0000"
            width: 10; height: 10
            anchors.top: parent.top
            anchors.right: parent.right
        }
        transform: Rotation {
            id: rot
            origin.x: 50; origin.y: 50
            axis: getAxis(animator.result)
            angle: getAngle(animator.result)
        }
    }

    NumberAnimation
    {
        property quaternion start: eulerToQuaternionXYZ(-45, -60, 0)
        property quaternion end: eulerToQuaternionXYZ(-45, -60, 180)
        property quaternion result: slerp(start, end, progress)
        property real progress: 0
        id: animator
        target: animator
        property: "progress"
        from: 0.0
        to: 1.0
        duration: 4000
        running: true
    }
}

这篇关于如何在 3D 中为 QML 旋转变换设置动画并正确插入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆