From 354fff96ffa06753c69a721ad017d621e95927d0 Mon Sep 17 00:00:00 2001
From: GO63-samara <go1@list.ru>
Date: Sun, 20 Sep 2020 02:48:57 +0400
Subject: Fix display of the Octopus pose in the Pose-widget.

In the extreme version of OpenTrack-2.3.12, the Octopus pose is still displayed incorrectly.
I have fixed pose-widget. A video of the corrected pose-widget is available here:
https://youtu.be/my4_VOwGmq4

Fixed bugs:
- The turns and movements of the Octopussy are now performed truly independently of each other, as it should be in 6DOF.
- When cornering, there is no "gimbal lock" at Pitch = +/- 90 degrees.
- Fixed directions of axes of rotations and positions. Now the Octopus pose displays the actual direction of view on the plane.
- Fixed display of the back (green) surface of the Octopus. Previously, it was displayed mirrored.

Additional features:
- Applied "perspective projection", the picture becomes more voluminous.
- Added lighting effect from above, with the same purpose.
- Added background fill for the widget. This makes it possible to see the borders of the widget.
- Added X and Y axes. This helps to estimate how far the Octopus is deviated from the center.
- Added [Mirror] checkbox, mirroring positions and rotations. It is often more convenient to observe the Octopussy's mirror pose.
- If before compilation in the file "pose-widget.hpp" include line 19: "#define TEST", then a rectangular frame will be drawn around the Octopus. This is useful when testing a pose-widget to assess distortion and size.
---
 pose-widget/ReadMe.txt      |  26 +++++++++
 pose-widget/pose-widget.cpp | 131 ++++++++++++++++++++++++++++++++++----------
 pose-widget/pose-widget.hpp |  10 +++-
 3 files changed, 137 insertions(+), 30 deletions(-)
 create mode 100644 pose-widget/ReadMe.txt

(limited to 'pose-widget')

diff --git a/pose-widget/ReadMe.txt b/pose-widget/ReadMe.txt
new file mode 100644
index 00000000..c504ff41
--- /dev/null
+++ b/pose-widget/ReadMe.txt
@@ -0,0 +1,26 @@
+Hi everyone!
+
+In the extreme version of OpenTrack-2.3.12, the Octopus pose is still displayed incorrectly.
+The pose-widget is responsible for displaying the Octopus pose in OpenTrack. I have fixed this widget.
+
+Fixed bugs:
+- The turns and movements of the Octopussy are now performed truly independently of each other, as it should be in 6DOF.
+- When cornering, there is no "gimbal lock" at Pitch = +/- 90 degrees.
+- Fixed directions of axes of rotations and positions. Now the Octopus pose displays the actual direction of view on the plane.
+- Fixed display of the back (green) surface of the Octopus. Previously, it was displayed mirrored.
+
+Additional features:
+- Applied "perspective projection", the picture becomes more voluminous.
+- Added lighting effect from above, with the same purpose.
+- Added background fill for the widget. This makes it possible to see the borders of the widget.
+- Added X and Y axes. This helps to estimate how far the Octopus is deviated from the center.
+- Added [Mirror] checkbox, mirroring positions and rotations. It is often more convenient to observe the Octopussy's mirror pose.
+- If before compilation in the file "pose-widget.hpp" include line 19: "#define TEST", then a rectangular frame will be drawn around the Octopus. This is useful when testing a pose-widget to assess distortion and size.
+
+The corrected pose-widget now displays the Octopus pose correctly and can be used to check OpenTrack settings, sometimes even without launching the flight simulator.
+
+A video of the corrected pose-widget is available here:
+
+https://youtu.be/my4_VOwGmq4
+
+fixed by GO63-samara <go1@list.ru> <github.com/GO63-samara> 2020
diff --git a/pose-widget/pose-widget.cpp b/pose-widget/pose-widget.cpp
index 3ad475cf..c5a6b78e 100644
--- a/pose-widget/pose-widget.cpp
+++ b/pose-widget/pose-widget.cpp
@@ -13,12 +13,40 @@
 #include <QtEvents>
 
 #include <QDebug>
-#include <QImage>
+#include <QQuaternion>
+#include <QMatrix4x4>
 
 namespace pose_widget_impl {
 
 pose_widget::pose_widget(QWidget* parent) : QWidget(parent)
 {
+    QPainter p;
+#ifdef TEST
+    //draw rectangle frame around of Octopus, only if TEST defined
+    p.begin(&front);
+    p.setPen(QPen(Qt::red, 3, Qt::SolidLine));
+    p.drawRect(0, 0, front.width()-1, front.height()-1);
+    p.end();
+
+    p.begin(&back);
+    p.setPen(QPen(Qt::darkGreen, 3, Qt::SolidLine));
+    p.drawRect(0, 0, back.width()-1, back.height()-1);
+    p.end();
+#endif
+
+    //draw Octopus shine
+    shine.fill(QColor(255,255,255));
+    p.begin(&shine);
+    p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
+    p.drawImage(QPointF(0,0), front);		
+    p.end();
+
+    //draw Octopus shadow
+    shadow.fill(QColor(0,0,0));
+    p.begin(&shadow);
+    p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
+    p.drawImage(QPointF(0,0), front);		
+    p.end();
 }
 
 void pose_widget::present(double yaw, double pitch, double roll, double x, double y, double z)
@@ -29,40 +57,87 @@ void pose_widget::present(double yaw, double pitch, double roll, double x, doubl
     repaint();
 }
 
+void pose_widget::resizeEvent(QResizeEvent *event)
+{
+    // adapt to widget size
+    float w = event->size().width();
+    float h = event->size().height();
+
+    // fill background by color
+    constexpr int clr = 220;
+    QImage background(QImage(w, h, QImage::Format_ARGB32));
+    background.fill(QColor(clr,clr,clr));
+
+    // draw axes
+    QPainter p(&background);
+    p.setPen(QPen(Qt::gray, 1, Qt::SolidLine));
+    p.drawLine(0.5*w,   0  , 0.5*w,   h  );
+    p.drawLine(  0  , 0.5*h,   w  , 0.5*h);
+
+    // set AutoFillBackground
+    QPalette palette;
+    palette.setBrush(this->backgroundRole(), QBrush(background));
+    setPalette(palette);
+    setAutoFillBackground(true);
+
+    // move the mirror checkbox in the lower right corner of the widget
+    mirror.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+    mirror.move(w - mirror.width(), h - mirror.height());
+}
+
 void pose_widget::paintEvent(QPaintEvent*)
 {
+    // widget settings:
+    constexpr float scale  =  0.5;   // scale of Octopus height, when x = y = z = 0.0
+    constexpr float XYZmax = 50.0;   // -XYZmax < x,y,z < +XYZmax (offset the Octopus by one body)
+    constexpr float Kz     =  0.25;  // Z scale change limit (simulate camera focus length)
+
+    // get a local copy of input data
     auto [ yaw, pitch, roll ] = R;
     auto [ x, y, z ] = T;
 
-    const QImage& img = (std::fabs(pitch) > 90) ^ (std::fabs(yaw) > 90)
-                        ? back
-                        : front;
-
-    constexpr double scale = .75;
-    int w = img.width(), h = iround(img.height()*scale);
-
-    QTransform t;
-
-    t.translate(w*.5, h*.5);
-
-    constexpr double z_scale = 1./250;
-    constexpr double xy_scale = .0075;
-    double xy = std::sqrt(w*w + h*h) * xy_scale;
-    double sx = clamp(.4 + -z * z_scale, .1, 2), sy = sx * 1/scale;
-
-    t.scale(sx, sy);
-
-    t.rotate(pitch, Qt::XAxis);
-    t.rotate(yaw, Qt::YAxis);
-    t.rotate(roll, Qt::ZAxis);
-
-    t.translate(x * xy / sx, y * xy / sy);
-
-    t.translate(w*-.5, h*-.5);
-
     QPainter p(this);
+    #ifdef TEST
+    // use antialiasing for correct frame around the Octopus, only if TEST defined
+    p.setRenderHint(QPainter::Antialiasing, true);
+    #endif
+
+    // check mirror state
+    if   (mirror.checkState() == Qt::Checked) x = -x;
+    else { yaw = -yaw; roll = -roll; }
+    y = -y;
+
+    // rotations
+    QQuaternion q = QQuaternion::fromEulerAngles(pitch,  yaw,  roll);
+    QMatrix4x4  m = QMatrix4x4(q.toRotationMatrix());
+
+    // x and y positions
+    const float Kxy = (float)front.height() / XYZmax;
+    QVector3D v(Kxy*x, Kxy*y, 0.0);
+    v = m.transposed().map(v);
+    m.translate(v);
+
+    // perspective projection to x-y plane
+    QTransform t = m.toTransform(1024).translate(-.5 * front.width(), -.5 * front.height());
+
+    // z position by setViewport 
+    const float mz = scale * height()/front.height()/exp(1.0) * exp(1.0 - z * (Kz/XYZmax)); 
+    p.setViewport(QRect(.5 * width(), .5 * height(), width()*mz, height()*mz));
+ 
+    // define forward or backward side by cross product of mapped x and y axes
+    QPointF point0 =  t.map(QPointF(0, 0));
+    QPointF x_dir  = (t.map(QPointF(1, 0)) -= point0);
+    QPointF y_dir  = (t.map(QPointF(0, 1)) -= point0);
+    const bool  forward  =  x_dir.ry()*y_dir.rx() - x_dir.rx()*y_dir.ry() < 0 ? true : false;
+
+    // draw red or green Octopus
     p.setTransform(t);
-    p.drawImage(rect(), img);
+    p.drawImage(QPointF(0,0), forward ? front : back);
+
+    // top lighting simulation
+    const float alpha = sin(pitch * M_PI / 180.0);
+    p.setOpacity(0.333 * fabs(alpha));
+    p.drawImage(QPointF(0,0), forward == (alpha >= 0.0) ? shine : shadow);
 }
 
 QSize pose_widget::sizeHint() const
diff --git a/pose-widget/pose-widget.hpp b/pose-widget/pose-widget.hpp
index b3267ff9..49044d93 100644
--- a/pose-widget/pose-widget.hpp
+++ b/pose-widget/pose-widget.hpp
@@ -14,7 +14,9 @@
 
 #include <QWidget>
 #include <QImage>
+#include <QCheckBox>
 
+//#define TEST
 namespace pose_widget_impl {
 
 using namespace euler;
@@ -25,13 +27,17 @@ public:
     pose_widget(QWidget *parent = nullptr);
     void present(double xAngle, double yAngle, double zAngle, double x, double y, double z);
     QSize sizeHint() const override;
-
+    QCheckBox mirror{QCheckBox{"Mirror", this}};
 private:
+    void resizeEvent(QResizeEvent *event) override;
     void paintEvent(QPaintEvent*) override;
 
     Pose_ R, T;
     QImage front{QImage{":/images/side1.png"}.convertToFormat(QImage::Format_ARGB32)};
-    QImage back{QImage{":/images/side6.png"}.convertToFormat(QImage::Format_ARGB32)};
+    QImage back {QImage{":/images/side6.png"}.convertToFormat(QImage::Format_ARGB32)
+                                             .mirrored(true,false)};
+    QImage shine {QImage{front.width(), front.height(), QImage::Format_ARGB32}};
+    QImage shadow{QImage{front.width(), front.height(), QImage::Format_ARGB32}};
 };
 
 }
-- 
cgit v1.2.3