1

I have a task to draw 3D objects on the ground using OpenGL. I use OpenGL left-handed coord system where Y axis is up. But 3D objects and camera orbiting objects should use different coord systems with following properties:

  • XY plane is a ground plane;
  • Z-axis is up;
  • Y-axis is "North";
  • X-axis is "East";
  • The azimuth (or horizontal) angle is [0, 360] degrees;
  • The elevation (or vertical) angle is [0, 90] degrees from XY plane. End user uses azimuth and elevation to rotate camera around some center. So I made following code to convert from spherical coordinates to quaternion:
//polar: x - radius, y - horizontal angle, z - vertical 
    QQuaternion CoordinateSystemGround::fromPolarToQuat(const QVector3D& polar) const {
    QQuaternion dest = QQuaternion::fromEulerAngles({0, polar.y(), polar.z()});
    //convert user coord system back to OpenGL by rotation around X-axis
    QQuaternion orig = QQuaternion::fromAxisAndAngle(1, 0, 0, -90);
    return dest * orig;
}

and back:

    QVector3D CoordinateSystemGround::fromQuatToPolar(const QQuaternion& q) const {
    //convert OpenGL coord system to destination by rotation around X-axis
    QQuaternion dest = QQuaternion::fromAxisAndAngle(1, 0, 0, 90);
    QQuaternion out = q * dest;
    QVector3D euler = out.toEulerAngles();

    float hor = euler.y();
    if(hor < 0.0f)
        hor += 360.0f;

    float ver = euler.z();
    if(ver > 90.0f)
        ver = 90.0f;
    else if(ver < 0.0f)
        ver = 0.0f;

    //x changes later
    return QVector3D(0, hor, ver);
}

But it doesn't work right. I suppose fromPolarToQuat conversion has mistake somewhere and I can't understand where.

Alexey S.
  • 180
  • 2
  • 11
  • 1
    I recommend to us [`QMatrix4x4::lookAt`](https://doc.qt.io/qt-5/qmatrix4x4.html#lookAt) to setup a view matrix. – Rabbid76 Aug 18 '19 at 19:03
  • @Rabbid76, I already use it. But at first I need to get camera coordinates in cartesian, so steps are: polar -> quaternion -> cartesian -> view matrix. – Alexey S. Aug 19 '19 at 03:47
  • 1
    why not use [4x4 homogenous transform matrices](https://stackoverflow.com/a/28084380/2521214) ? they are exactly for this ... its just matter of multiplying by direct or inverse matrix ... – Spektre Aug 19 '19 at 06:35
  • Why don't you transform from polar to world coordinates directly? There is no need for quaternions. – Michael Nastenko Aug 19 '19 at 12:51
  • @MichaelNastenko, well, I found easier and less error-prone to get angles with quaternions rather than with direct conversion. – Alexey S. Aug 19 '19 at 13:30
  • @Spektre, good advice! But too much code to rewrite in order to support matrices, so maybe later. – Alexey S. Aug 19 '19 at 13:31
  • @AlexeyS. The direct way is way simpler than yours, thus it actually less error-prone. – Michael Nastenko Aug 19 '19 at 22:26
  • @MichaelNastenko, I disagree. Have a look on my edited answer. It's more intuitive and you don't need to deal with a cos, sin and tan. – Alexey S. Aug 20 '19 at 06:42
  • @AlexeyS. your way is using a lot of goniometrics therefore its much less accurate and slow. Also I see a lot of euler angles conversions so there is high risk of singularities (like in most games these days :( ). If you convert your Quaternion into matrix (most engines have that capability) so you have either position and 3x3 rotation matrix or 4x4 matrix directly then its just a matter of simple matrix multiplication and inverse (transpose + dot product) no goniometryics at all .... – Spektre Aug 20 '19 at 06:54
  • looks like `QQuaternion dest = QQuaternion::fromAxes({-1, 0, 0}, {0, 0, 1}, {0, 1, 0});` is converting 3x3 rotation matrix to your quaternion so there must be its counterpart too. Also your Question reminds me of this [Representing Points on a Circular Radar Math approach](https://stackoverflow.com/a/24404784/2521214) – Spektre Aug 20 '19 at 06:57
  • @Spektre, thanks for explanations! Seems like [QTransform from Qt 3D](https://code.woboq.org/qt5/qt3d/src/core/transforms/qtransform.cpp.html) is a way to go. – Alexey S. Aug 20 '19 at 07:51

1 Answers1

1

Seems like I found the solution. So, to get polar angles from quaternion:

QVector3D CoordinateSystemGround::fromQuatToPolar(const QQuaternion& q) const {
    QQuaternion coord1 = QQuaternion::fromAxisAndAngle(0, 1, 0, -180);
    QQuaternion coord2 = QQuaternion::fromAxisAndAngle(1, 0, 0, -90);
    QQuaternion out = orig1 * orig2 * q;
    QVector3D euler = out.toEulerAngles();

    float hor = euler.y();
    if(hor < 0.0f)
        hor += 360.0f;

    float ver = euler.x();

    return QVector3D(0, hor, -ver);
}

And back:

QQuaternion CoordinateSystemGround::fromPolarToQuat(const QVector3D& polar) const {
    QQuaternion dest = QQuaternion::fromEulerAngles({-polar.z(), polar.y(), 0});
    QQuaternion coord1 = QQuaternion::fromAxisAndAngle(1, 0, 0, 90);
    QQuaternion coord2 = QQuaternion::fromAxisAndAngle(0, 1, 0, 180);
    return coord1 * coord2 * dest;
}

Not sure it's an optimal solution though, but it works as it should.

Edited

After some research I've found few mistakes and made optimized and hope correct version of conversion:

QVector3D CoordinateSystemGround::fromQuatToPolar(const QQuaternion& q) const {
    // back to OpenGL coord system: just multiplication on inverted destination coord system
    QQuaternion dest = QQuaternion::fromAxes({-1, 0, 0}, {0, 0, 1}, {0, 1, 0}).inverted();
    QVector3D euler = (dest * q).toEulerAngles();

    float hor = euler.y();
    if(hor < 0.0f)
        hor += 360.0f;

    float ver = euler.x();

    return QVector3D(0, hor, -ver);
}

And back:

QQuaternion CoordinateSystemGround::fromPolarToQuat(const QVector3D& polar) const {
    //just rotate if we were in OpenGL coord system
    QQuaternion orig = QQuaternion::fromEulerAngles({-polar.z(), polar.y(), 0});
    //and then multiply on destination coord system
    QQuaternion dest = QQuaternion::fromAxes({-1, 0, 0}, {0, 0, 1}, {0, 1, 0});
    return dest * orig;
}
Alexey S.
  • 180
  • 2
  • 11