2015-11-11 3 views
4

Этот пример кода здесь:Как анимировать и propertly intepolate вращение QML преобразование в 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 
     } 
    } 
} 

будет производить этот вывод:

step 1

Теперь я хочу применить 3D вращение от центр этого зеленого прямоугольника. Во-первых, я хочу повернуть на X на -45 градусов (поклониться), затем на Y на -60 градусов (поворачивая влево).

Я использовал следующий C++ код пропущен с помощью GLM на стороне, чтобы помочь мне вычислить ось и угол:

// 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 
     } 
    } 
} 

Что даст мне то, что я хотел бы видеть:

step 2

До сих пор так хорошо. Сейчас идет сложная часть. Как мне оживить это? Например, если я хочу перейти от {45.0, 60.0, 0} до {45.0, 60.0, 90.0}. Другими словами, я хочу, чтобы анимировать здесь

step 2

, чтобы здесь

step3

Я подключил, что цель вращения здесь

// 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 градусов, где он просто вращается в случайных направлениях, пока не достигнет своих конечных целей.

Как заставить эту анимацию смотреть прямо между началом вращения и вращением цели?

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

Я добавляю еще один критерий: Ответ, который я ищу , должен обязательно предоставить идентичный результат в качестве скриншотов, которые я изложил выше.

Например, разделив оси 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 } 
     } 
    ] 

даст мне это:

incorrect

что неправильно.

ответ

3

Я решил свою проблему. Я полностью забыл, что 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 
    } 
} 
+0

Это действительно непонятно, потому что они обеспечивают функцию slerp в классе QQuaternion, но не отображаются в QML: http://doc.qt.io/qt-5/qquaternion.html#slerp – mchiasson

+1

Они действительно должны добавить QuaternionAnimation для выполнения этого сферического линейного интерполятора для нас или, по крайней мере, разоблачить функцию slerp – mchiasson

+0

, почему вы сделали это только по функциям? Почему бы не использовать декларативный характер QML? –

2

Вы пытаетесь это неправильно. Вы можете комбинировать преобразования и анимировать один из них. Таким образом вы достигнете именно того, что вам нужно.

Еще одна проблема, которую я вижу, это то, что вы пишете о степенях и в коде я вижу радианов :).

Нижняя линия это должно выглядеть следующим образом:

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: 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 } 
      } 
     ] 
     NumberAnimation { 
      running: true 
      loops: 100 
      target: zRot; 
      property: "angle"; 
      from: 0; to: 360; 
      duration: 4000; 
     } 
    } 

Результат отличается от этого на ваши картины, но это результат вы испортили градусов и радиан. Я использовал преобразование, описанное в тексте, а не из вашего кода.

+0

GLM работает исключительно в радианах, поэтому я преобразовал свои эйлеровых углов в радианах (например, 'GLM :: радиан (45.0f)') , и, в конце концов, я преобразовываю его обратно в градусы (например, 'glm :: degrees (glm :: angle (quaternion))) – mchiasson

+0

И нет, я не могу принять ваш ответ. Ваш код не дает мне точно результат моих изображений выше. Но ты заставил меня осознать ошибку, которую я совершил. Углы используют правое правило, что означает, что мои углы должны были быть -45 градусов и -60 градусов. Спасибо за это :-) – mchiasson

+0

Я обновил свой код на C++ с комментариями о степени-радиан и переходом от радианов к градусу. И добавил критерии, которые я ищу, что вывод должен быть ** идентичным ** на снимки экрана, которые я предоставил. – mchiasson