diff --git a/src/quick3d/qquick3dcamera.cpp b/src/quick3d/qquick3dcamera.cpp index b5c8c967bd498a90d199d332d2af00835d79922d..76e1a16d64b9ab382ceaeb7e60e0af4b6cdf4f14 100644 --- a/src/quick3d/qquick3dcamera.cpp +++ b/src/quick3d/qquick3dcamera.cpp @@ -272,7 +272,21 @@ QVector3D QQuick3DCamera::mapFromViewport(const QVector3D &viewportPos, void QQuick3DCamera::lookAt(const QVector3D &scenePos) { - this->setRotation(QQuick3DQuaternionUtils::lookAt(scenePosition(), forward(), scenePos, up())); + // Assumption: we never want the camera to roll. + // We use Euler angles here to avoid roll to sneak in through numerical instability. + + const auto &targetPosition = scenePos; + auto sourcePosition = scenePosition(); + + QVector3D targetVector = sourcePosition - targetPosition; + + float yaw = qRadiansToDegrees(atan2(targetVector.x(), targetVector.z())); + + QVector2D p(targetVector.x(), targetVector.z()); // yaw vector projected to horizontal plane + float pitch = qRadiansToDegrees(atan2(p.length(), targetVector.y())) - 90; + + const float previousRoll = eulerRotation().z(); + setEulerRotation(QVector3D(pitch, yaw, previousRoll)); } /*! @@ -282,7 +296,7 @@ void QQuick3DCamera::lookAt(const QVector3D &scenePos) Sets the rotation value of a camera to be directed at \a node. */ -void QQuick3DCamera::lookAt(const QQuick3DNode *node) +void QQuick3DCamera::lookAt(QQuick3DNode *node) { if (!node) return; diff --git a/src/quick3d/qquick3dcamera_p.h b/src/quick3d/qquick3dcamera_p.h index 3bb923dbb7d9647510b22e59ac54c87d6446d2e9..aa8b15105d2f6ecf08fabe8ef83cf4bf9f02b756 100644 --- a/src/quick3d/qquick3dcamera_p.h +++ b/src/quick3d/qquick3dcamera_p.h @@ -70,7 +70,7 @@ public: qreal height); Q_REVISION(1) Q_INVOKABLE void lookAt(const QVector3D &scenePos); - Q_REVISION(1) Q_INVOKABLE void lookAt(const QQuick3DNode *node); + Q_REVISION(1) Q_INVOKABLE void lookAt(QQuick3DNode *node); QSSGRenderCamera *cameraNode() const; void setCameraNode(QSSGRenderCamera *camera) { m_cameraNode = camera; } diff --git a/src/quick3d/qquick3dnode_p.h b/src/quick3d/qquick3dnode_p.h index ea4dc8125fd0da63baddf73c22c03b08b1a6c140..a11a2e3cb96d8605ee5188c059409b875b1a249a 100644 --- a/src/quick3d/qquick3dnode_p.h +++ b/src/quick3d/qquick3dnode_p.h @@ -173,4 +173,6 @@ private: QT_END_NAMESPACE +QML_DECLARE_TYPE(QQuick3DNode) + #endif // QSSGNODE_H diff --git a/src/quick3d/qquick3dquaternionutils.cpp b/src/quick3d/qquick3dquaternionutils.cpp index 80bf004702ecfde5b564e0275b1ed4bb4453583f..5c0e0f0f3bfd866e4a501b715f1dbc8c622c24e6 100644 --- a/src/quick3d/qquick3dquaternionutils.cpp +++ b/src/quick3d/qquick3dquaternionutils.cpp @@ -74,12 +74,15 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlmethod quaternion Quick3D::Quaternion::lookAt(vector3d sourcePosition, vector3d sourceDirection, - vector3d targetPosition, vector3d upDirection) - Creates a quaternion from \a sourcePosition, \a sourceDirection, \a targetPosition, and + \qmlmethod quaternion Quick3D::Quaternion::lookAt(vector3d sourcePosition, vector3d targetPosition, + vector3d forwardDirection, vector3d upDirection) + Creates a quaternion from \a sourcePosition, \a targetPosition, \a forwardDirection, and \a upDirection. This is used for getting a rotation value for pointing at a particular target, and can be used to point a camera at a position in a scene. + \a forwardDirection defaults to \c Qt.vector3d(0, 0, -1) + \a upDirection defaults to \c Qt.vector3d(0, 1, 0) + Returns the resulting quaternion. */ @@ -109,20 +112,20 @@ QQuaternion QQuick3DQuaternionUtils::fromEulerAngles(const QVector3D &eulerAngle } QQuaternion QQuick3DQuaternionUtils::lookAt(const QVector3D &sourcePosition, - const QVector3D &sourceDirection, const QVector3D &targetPosition, + const QVector3D &forwardDirection, const QVector3D &upDirection) { - QVector3D targetDirection = sourcePosition - targetPosition; + QVector3D targetDirection = targetPosition - sourcePosition; targetDirection.normalize(); - QVector3D rotationAxis = QVector3D::crossProduct(sourceDirection, targetDirection); + QVector3D rotationAxis = QVector3D::crossProduct(forwardDirection, targetDirection); const QVector3D normalizedAxis = rotationAxis.normalized(); - if (normalizedAxis.lengthSquared() == 0) + if (qFuzzyIsNull(normalizedAxis.lengthSquared())) rotationAxis = upDirection; - float dot = QVector3D::dotProduct(sourceDirection, targetDirection); + float dot = QVector3D::dotProduct(forwardDirection, targetDirection); float rotationAngle = qRadiansToDegrees(qAcos(dot)); return QQuaternion::fromAxisAndAngle(rotationAxis, rotationAngle); diff --git a/src/quick3d/qquick3dquaternionutils_p.h b/src/quick3d/qquick3dquaternionutils_p.h index 2bcd55367a51cddf96b32c1775549eaa30425b1f..4bac4e83e1f09ff75656f064ef252161945b4084 100644 --- a/src/quick3d/qquick3dquaternionutils_p.h +++ b/src/quick3d/qquick3dquaternionutils_p.h @@ -71,8 +71,9 @@ public: Q_INVOKABLE static QQuaternion fromEulerAngles(const QVector3D &eulerAngles); Q_REVISION(1) Q_INVOKABLE static QQuaternion lookAt(const QVector3D &sourcePosition, - const QVector3D &sourceDirection, const QVector3D &targetPosition, + const QVector3D &forwardDirection + = QVector3D(0, 0, -1), const QVector3D &upDirection = QVector3D(0, 1, 0)); diff --git a/tests/manual/cameralookat/main.qml b/tests/manual/cameralookat/main.qml index 6e4200d9bd24770c84f1b959acc6e6b8977a3d0d..59f1fab8e6a71478ebcc70df75ccbc03a5b147ae 100644 --- a/tests/manual/cameralookat/main.qml +++ b/tests/manual/cameralookat/main.qml @@ -44,6 +44,9 @@ Window { PerspectiveCamera { id: sceneCamera z: 300 + x: -200 + y: 100 + eulerRotation: Qt.vector3d(-15, -30, 0) } Model { @@ -86,7 +89,7 @@ Window { Button { text: "Cube" onClicked: { - sceneCamera.rotation = Quaternion.lookAt(sceneCamera.scenePosition, sceneCamera.forward, cube.scenePosition, sceneCamera.up); + sceneCamera.rotation = Quaternion.lookAt(sceneCamera.scenePosition, cube.scenePosition) } } Button {