有多种方法可以实现此效果。在这个 Qt/QML 演示中,我使用了一种基于 NumberAnimation 的方法来旋转立方体。
这些是它背后的关键思想:
- 立方体有一个
Transform 组件,它定义了Qt3D 用来使用预定义的旋转和缩放值将立方体放置在世界中的4x4 矩阵。 NumberAnimation 只是改变了这个变换使用的旋转角度,使立方体在屏幕上旋转;
- 旋转速度由
NumberAnimation 的duration 属性定义:值越小,立方体旋转得越快。立方体从 0 度旋转到 360 度需要 5 秒;
- 更改旋转速度意味着根据当前旋转角度距 0 度的距离重新计算动画的
duration。每当我们更改任何NumberAnimation 属性时,动画都需要重新启动;
- 当旋转方向改变时,我们将当前的旋转角度存储在
cubeTransform.startAngle中,并更新NumberAnimation的to和from属性以及duration,这样立方体就可以在一个从当前角度开始的新方向。
我在 UI 中添加了几个按钮来改变运行时的方向和速度:
main.qml:
import QtQuick 2.12 as QQ2
import QtQuick.Controls 2.12
import QtQuick.Scene3D 2.12
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
import Qt3D.Extras 2.0
import Qt3D.Logic 2.0
QQ2.Item {
id: windowId
width: 800
height: 800
property real cubeScale: 100.0
property int rotationSpeed: 5000
property real speedFactor: 1.0
property bool clockwiseRotation: true
Scene3D {
id: scene3D
anchors.fill: parent
aspects: ["input", "logic"]
focus: true
cameraAspectRatioMode: Scene3D.AutomaticAspectRatio
Entity {
id: rootEntity
Camera {
id: camera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
nearPlane : 0.1
farPlane : 100000.0
position: Qt.vector3d(0.0, 0.0, 500.0)
viewCenter: Qt.vector3d(0.0, 0.0, 0.0)
upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
}
components: [
RenderSettings {
activeFrameGraph: ForwardRenderer {
camera: camera
clearColor: "silver"
}
},
InputSettings { }
]
Entity {
id: lightEntity
enabled: true
DirectionalLight {
id: infiniteLight
color: "white"
intensity: 1.0
worldDirection: Qt.vector3d(0, 0, 0)
}
Transform {
id: infiniteLightTransform
translation: Qt.vector3d(0.0, 0.0, 500.0)
}
components: [ infiniteLight, infiniteLightTransform ]
}
Entity {
id: cubeEntity
CuboidMesh {
id: cubeMesh
}
PhongMaterial {
id: cubeMaterial
ambient: "red"
diffuse: Qt.rgba(1.0, 1.0, 1.0, 1.0)
}
Transform {
id: cubeTransform
property real angle: 0.0
property real startAngle: 0.0
matrix: {
var m = Qt.matrix4x4();
m.rotate(cubeTransform.angle, Qt.vector3d(0, 1, 0));
m.translate(Qt.vector3d(0, 0, 0));
m.scale(windowId.cubeScale, windowId.cubeScale, windowId.cubeScale);
return m;
}
}
components: [ cubeMesh, cubeMaterial, cubeTransform ]
}
QQ2.NumberAnimation {
id: cubeAnimation
property int startPos: {
if (cubeTransform.startAngle === 0)
{
if (windowId.clockwiseRotation === true)
return 0;
return 360;
}
return cubeTransform.startAngle;
}
property int endPos: (windowId.clockwiseRotation === true) ? 360 : 0
target: cubeTransform
property: "angle"
duration: windowId.rotationSpeed / windowId.speedFactor
from: cubeAnimation.startPos
to: cubeAnimation.endPos
loops: 1
running: true
onStarted: {
console.log("onStarted");
}
onFinished: {
console.log("onFinished");
// reset the starting angle
cubeTransform.startAngle = 0;
// reset the duration of the animation
cubeAnimation.duration = windowId.rotationSpeed / windowId.speedFactor;
// the animation is currently stopped, run it again
cubeAnimation.running = true;
}
}
}
}
QQ2.Row {
anchors.fill: parent
Button {
text: "Change Direction"
onClicked: {
// pause the animation
cubeAnimation.pause();
// invert rotation
windowId.clockwiseRotation = !windowId.clockwiseRotation;
// store current angle as the starting angle for the rotation
cubeTransform.startAngle = cubeTransform.angle;
// update the remaining time to avoid changing the rotation speed
let progressPercentage = cubeTransform.startAngle / 360.0;
if (windowId.clockwiseRotation)
progressPercentage = 1.0 - progressPercentage;
cubeAnimation.duration = (windowId.rotationSpeed / windowId.speedFactor) * progressPercentage;
// restart the animation from this angle using a new direction
cubeAnimation.restart();
}
}
Button {
text: "1x"
highlighted: (windowId.speedFactor === 1.0)
onClicked: changeSpeed(1.0)
}
Button {
text: "2x"
highlighted: (windowId.speedFactor === 2.0)
onClicked: changeSpeed(2.0)
}
}
function changeSpeed(newSpeed) {
windowId.speedFactor = newSpeed;
// pause the animation
cubeAnimation.pause();
// store current angle as the starting angle for the rotation
cubeTransform.startAngle = cubeTransform.angle;
// update the remaining time to avoid changing the rotation speed
let progressPercentage = cubeTransform.startAngle / 360.0;
if (windowId.clockwiseRotation)
progressPercentage = 1.0 - progressPercentage;
cubeAnimation.duration = (windowId.rotationSpeed / windowId.speedFactor) * progressPercentage;
// restart the animation from this angle using a new direction
cubeAnimation.restart();
}
}