如何为3D中的QML旋转变换制作动画并正确插入 [英] How to animate and propertly intepolate a QML rotation transform in 3D
问题描述
此代码示例在这里:
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度(向左旋转).
我在侧面使用GLM截断了以下c ++代码,以帮助我计算轴和角度:
// 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
的角度.因此,我将示例代码更新为:
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}.换句话说,我想从这里开始动画
到这里
我在这里插入了目标旋转
// 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
所以我将此动画添加到了样本中
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度)会更糟,因为它会沿随机方向旋转直到达到最终目标./p>
如何使该动画在起始旋转和目标旋转之间正确显示?
-------------------编辑-------------------
我要添加一个标准:我正在寻找的答案必须绝对提供与我上面提供的屏幕截图相同的输出.
例如,将3个旋转轴拆分为3个单独的旋转变换并不能得到正确的结果
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功能,一切就可以完美地工作.
这是我寻找寻求答案的人的代码:
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
}
}
This code sample here:
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
}
}
}
Will produce this output:
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).
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));
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
}
}
}
Which give me exactly what I wanted to see:
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
to 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));
which gave me an axis of {-0.621515, -0.102255, 0.7767}
and an angle of 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;
}
}
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.
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 }
}
]
Will give me this:
Which is incorrect.
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屋!