diff options
Diffstat (limited to 'pose-widget')
| -rw-r--r-- | pose-widget/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | pose-widget/ReadMe.txt | 26 | ||||
| -rw-r--r-- | pose-widget/export.hpp | 11 | ||||
| -rw-r--r-- | pose-widget/images/side1.png | bin | 31455 -> 35513 bytes | |||
| -rw-r--r-- | pose-widget/images/side6.png | bin | 31356 -> 35720 bytes | |||
| -rw-r--r-- | pose-widget/lang/de_DE.ts | 4 | ||||
| -rw-r--r-- | pose-widget/lang/zh_CN.ts | 4 | ||||
| -rw-r--r-- | pose-widget/pose-widget.cpp | 448 | ||||
| -rw-r--r-- | pose-widget/pose-widget.hpp | 92 |
9 files changed, 169 insertions, 418 deletions
diff --git a/pose-widget/CMakeLists.txt b/pose-widget/CMakeLists.txt index fd109bc3..28dc918b 100644 --- a/pose-widget/CMakeLists.txt +++ b/pose-widget/CMakeLists.txt @@ -1,2 +1,2 @@ otr_module(pose-widget BIN) -target_link_libraries(opentrack-pose-widget) +target_link_libraries(${self}) 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/export.hpp b/pose-widget/export.hpp new file mode 100644 index 00000000..bfe55138 --- /dev/null +++ b/pose-widget/export.hpp @@ -0,0 +1,11 @@ +// generates export.hpp for each module from compat/linkage.hpp + +#pragma once + +#include "compat/linkage-macros.hpp" + +#ifdef BUILD_POSE_WIDGET +# define OTR_POSE_WIDGET_EXPORT OTR_GENERIC_EXPORT +#else +# define OTR_POSE_WIDGET_EXPORT OTR_GENERIC_IMPORT +#endif diff --git a/pose-widget/images/side1.png b/pose-widget/images/side1.png Binary files differindex c1013de0..2955bc01 100644 --- a/pose-widget/images/side1.png +++ b/pose-widget/images/side1.png diff --git a/pose-widget/images/side6.png b/pose-widget/images/side6.png Binary files differindex e32f4198..3bae0e50 100644 --- a/pose-widget/images/side6.png +++ b/pose-widget/images/side6.png diff --git a/pose-widget/lang/de_DE.ts b/pose-widget/lang/de_DE.ts new file mode 100644 index 00000000..1552582e --- /dev/null +++ b/pose-widget/lang/de_DE.ts @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="de_DE"> +</TS> diff --git a/pose-widget/lang/zh_CN.ts b/pose-widget/lang/zh_CN.ts new file mode 100644 index 00000000..e5ca8aa9 --- /dev/null +++ b/pose-widget/lang/zh_CN.ts @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +</TS> diff --git a/pose-widget/pose-widget.cpp b/pose-widget/pose-widget.cpp index 751c4700..ac3aa74a 100644 --- a/pose-widget/pose-widget.cpp +++ b/pose-widget/pose-widget.cpp @@ -6,374 +6,134 @@ */ #include "pose-widget.hpp" -#include "compat/util.hpp" -#include "compat/timer.hpp" -#include "compat/sleep.hpp" -#include <cmath> -#include <algorithm> +#include "compat/check-visible.hpp" +#include "compat/math.hpp" + #include <QPainter> -#include <QPaintEvent> +#include <QtEvents> #include <QDebug> +#include <QQuaternion> +#include <QMatrix4x4> -using namespace pose_widget_impl; - -pose_transform::pose_transform(QWidget* dst) : - dst(dst), - image(w, h, QImage::Format_ARGB32), - image2(w, h, QImage::Format_ARGB32), - width(w), height(h), - fresh(false) -{ - front = QImage(QString(":/images/side1.png")); - back = QImage(QString(":/images/side6.png")); - - image.fill(Qt::transparent); - image2.fill(Qt::transparent); - - start(); -} - -pose_transform::~pose_transform() -{ - requestInterruption(); - wait(); -} - -void pose_widget::paintEvent(QPaintEvent* event) -{ - QPainter p(this); - xform.with_image_lock([&](const QImage& image) { - p.drawImage(event->rect(), image); - }); -} - -void pose_transform::run() -{ - for (;;) - { - if (isInterruptionRequested()) - break; - - { - lock_guard l(mtx); - - if (fresh) - goto end; - - rotation = rotation_; - translation = translation_; - } - - project_quad_texture(); - -end: - portable::sleep(9); - } -} - -pose_widget::pose_widget(QWidget* parent) : QWidget(parent), xform(this) -{ - rotate_sync(0,0,0, 0,0,0); -} - -pose_widget::~pose_widget() -{ -} +namespace pose_widget_impl { -void pose_widget::rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z) +pose_widget::pose_widget(QWidget* parent) : QWidget(parent) { - bool expected = true; - if (xform.fresh.compare_exchange_weak(expected, false)) - { - update(); - xform.rotate_async(xAngle, yAngle, zAngle, x, y, z); - } -} + 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(); -template<typename F> -void pose_transform::with_rotate(F&& fun, double xAngle, double yAngle, double zAngle, double x, double y, double z) -{ - using std::sin; - using std::cos; - - static constexpr double d2r = M_PI / 180; - - euler::euler_t euler(-zAngle * d2r, xAngle * d2r, -yAngle * d2r); - euler::rmat r = euler::euler_to_rmat(euler); + p.begin(&back); + p.setPen(QPen(Qt::darkGreen, 3, Qt::SolidLine)); + p.drawRect(0, 0, back.width()-1, back.height()-1); + p.end(); +#endif - lock_guard l(mtx); + //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(); - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - rotation_(i, j) = num(r(i, j)); + //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(); - translation_ = vec3(x, y, z); - - fun(); + mirror.setFocusPolicy(Qt::NoFocus); } -void pose_widget::rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z) +void pose_widget::present(double yaw, double pitch, double roll, double x, double y, double z) { - xform.rotate_sync(xAngle, yAngle, zAngle, x, y, z); -} + T = { x, y, z }; + R = { yaw, pitch, roll }; -void pose_transform::rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z) -{ - with_rotate([this]() {}, xAngle, yAngle, zAngle, x, y, z); + repaint(); } -void pose_transform::rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z) +void pose_widget::resizeEvent(QResizeEvent *event) { - with_rotate([this]() { - rotation = rotation_; - translation = translation_; - project_quad_texture(); - dst->repaint(); - }, xAngle, yAngle, zAngle, x, y, z); -} - -class Triangle { - num dot00, dot01, dot11, invDenom; - vec2 v0, v1, origin; -public: - Triangle(const vec2& p1, const vec2& p2, const vec2& p3); - bool barycentric_coords(const vec2& px, vec2& uv, int& i) const; -}; - -inline vec3 pose_transform::normal(const vec3& p1, const vec3& p2, const vec3& p3) -{ - using std::sqrt; - - vec3 u = p2 - p1; - vec3 v = p3 - p1; - - vec3 tmp = u.cross(v); + // adapt to widget size + float w = event->size().width(); + float h = event->size().height(); - num i = 1/sqrt(tmp.dot(tmp)); - - return tmp * i; + // 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()); } -Triangle::Triangle(const vec2& p1, const vec2& p2, const vec2& p3) +void pose_widget::paintEvent(QPaintEvent*) { - using std::fabs; - - origin = p1; - - v0 = vec2(p3 - p1); - v1 = vec2(p2 - p1); + // 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) - dot00 = v0.dot(v0); - dot01 = v0.dot(v1); - dot11 = v1.dot(v1); + // get a local copy of input data + auto [ yaw, pitch, roll ] = R; + auto [ x, y, z ] = T; - const num denom = dot00 * dot11 - dot01 * dot01; - - if (fabs(denom) < num(1e3)) - { - // for perpendicular plane, ensure u and v don't come out right - // this is done here to avoid branching below, in a hot loop - invDenom = 0; - dot00 = dot01 = dot11 = 0; - v0 = v1 = vec2(0, 0); - } - else - invDenom = 1 / denom; -} - -bool Triangle::barycentric_coords(const vec2& px, vec2& uv, int& i) const -{ - i = 0; - const vec2 v2 = px - origin; - const num dot12 = v1.dot(v2); - const num dot02 = v0.dot(v2); - num u = (dot11 * dot02 - dot01 * dot12) * invDenom; - num v = (dot00 * dot12 - dot01 * dot02) * invDenom; - if (!(u >= 0 && v >= 0)) - return false; - if (u + v > 1) - { - i = 1; - - u = 1 - u; - v = 1 - v; - } - uv = vec2(u, v); - return u >= 0 && v >= 0 && u + v <= 1; -} - -void pose_transform::project_quad_texture() -{ - num dir; - vec2 pt[4]; - const int sx = width - 1, sy = height - 1; - vec2 projected[3]; - - { - const int sx_ = (sx - std::max(0, (sx - sy)/2)) * 5/9; - const int sy_ = (sy - std::max(0, (sy - sx)/2)) * 5/9; - - static constexpr const double c = 85/100.; - - const vec3 dst_corners[] = - { - vec3(-sx_/2. * c, -sy_/2., 0), - vec3(sx_/2. * c, -sy_/2., 0), - vec3(-sx_/2. * c, sy_/2., 0), - vec3(sx_/2. * c, sy_/2., 0.) - }; - - for (int i = 0; i < 4; i++) - pt[i] = project(dst_corners[i]) + vec2(sx/2, sy/2); - - vec3 normal1(0, 0, 1); - vec3 normal2; - { - vec3 foo[3]; - for (int i = 0; i < 3; i++) - { - foo[i] = project2(dst_corners[i]); - projected[i] = project(dst_corners[i]) + vec2(sx/2, sy/2); - } - normal2 = normal(foo[0], foo[1], foo[2]); - } - dir = normal1.dot(normal2); - } - - const QImage& tex = dir < 0 ? back : front; - const int ow = tex.width(), oh = tex.height(); - - vec2 origs[2][3] = - { - { - vec2(0, 0), - vec2(ow-1, 0), - vec2(0, oh-1) - }, - { - vec2(ow-1, oh-1), - vec2(0, oh-1) - vec2(ow-1, oh-1), - vec2(ow-1, 0) - vec2(ow-1, oh-1), - } - }; - - Triangle t(projected[0], projected[1], projected[2]); - - const int orig_pitch = tex.bytesPerLine(); - const int dest_pitch = image.bytesPerLine(); - - const unsigned char* orig = tex.bits(); - unsigned char* dest = image.bits(); - - const int orig_depth = tex.depth() / 8; - const int dest_depth = image.depth() / 8; - - /* image breakage? */ - if (orig_depth != 4) - { - qDebug() << "pose-widget: octopus must be saved as .png with 32-bit depth"; - return; - } - - const vec3 half = rotation * vec3(.5f, .5f, 0); - - for (int y = 1; y < sy; y++) - for (int x = 1; x < sx; x++) - { - vec2 pos(x, y); - vec2 uv; - int i; - if (t.barycentric_coords(pos, uv, i)) - { - const float fx = origs[i][0].x() - + uv.x() * origs[i][2].x() - + uv.y() * origs[i][1].x(); - const float fy = origs[i][0].y() - + uv.x() * origs[i][2].y() - + uv.y() * origs[i][1].y(); - - const int px_ = fx + half.x(); - const int py_ = fy + half.y(); - const int px = fx; - const int py = fy; - const float ax_ = fx - px; - const float ay_ = fy - py; - const float ax = 1 - ax_; - const float ay = 1 - ay_; - - // 0, 0 -- ax, ay - const int orig_pos = py * orig_pitch + px * orig_depth; - const unsigned char r = orig[orig_pos + 2]; - const unsigned char g = orig[orig_pos + 1]; - const unsigned char b = orig[orig_pos + 0]; - - // 1, 1 -- ax_, ay_ - const int orig_pos_ = py_ * orig_pitch + px_ * orig_depth; - const unsigned char r_ = orig[orig_pos_ + 2]; - const unsigned char g_ = orig[orig_pos_ + 1]; - const unsigned char b_ = orig[orig_pos_ + 0]; - - // 1, 0 -- ax_, ay - const int orig_pos__ = py * orig_pitch + px_ * orig_depth; - const unsigned char r__ = orig[orig_pos__ + 2]; - const unsigned char g__ = orig[orig_pos__ + 1]; - const unsigned char b__ = orig[orig_pos__ + 0]; - - // 0, 1 -- ax, ay_ - const int orig_pos___ = py_ * orig_pitch + px * orig_depth; - const unsigned char r___ = orig[orig_pos___ + 2]; - const unsigned char g___ = orig[orig_pos___ + 1]; - const unsigned char b___ = orig[orig_pos___ + 0]; - - const unsigned char a1 = orig[orig_pos + 3]; - const unsigned char a2 = orig[orig_pos_ + 3]; - const unsigned char a3 = orig[orig_pos__ + 3]; - const unsigned char a4 = orig[orig_pos___ + 3]; - - const int pos = y * dest_pitch + x * dest_depth; - - dest[pos + 2] = (r * ax + r__ * ax_) * ay + (r___ * ax + r_ * ax_) * ay_; - dest[pos + 1] = (g * ax + g__ * ax_) * ay + (g___ * ax + g_ * ax_) * ay_; - dest[pos + 0] = (b * ax + b__ * ax_) * ay + (b___ * ax + b_ * ax_) * ay_; - dest[pos + 3] = (a1 * ax + a3 * ax_) * ay + (a4 * ax + a2 * ax_) * ay_; - } - } + QPainter p(this); + #ifdef TEST + // use antialiasing for correct frame around the Octopus, only if TEST defined + p.setRenderHint(QPainter::Antialiasing, true); + #endif { - lock_guard l2(mtx2); - image2.fill(Qt::transparent); - std::swap(image, image2); - fresh = true; + p.fillRect(rect(), palette().brush(backgroundRole())); + // draw axes + p.save(); + p.setPen(QPen(Qt::gray, 1, Qt::SolidLine)); + int w = width(), h = height(); + p.drawLine(w/2, 0, w/2, h); + p.drawLine( 0, h/2, w, h/2); + p.restore(); } -} - -vec2 pose_transform::project(const vec3 &point) -{ - using std::fabs; - - vec3 ret = rotation * point; - num z = std::max<num>(.5, 1 + translation.z()/-80); - num w = width, h = height; - num x = w * translation.x() / 2 / -80; - if (fabs(x) > w/2) - x = x > 0 ? w/2 : w/-2; - num y = h * translation.y() / 2 / -80; - if (fabs(y) > h/2) - y = y > 0 ? h/2 : h/-2; - return vec2(z * (ret.x() + x), z * (ret.y() + y)); -} - -vec3 pose_transform::project2(const vec3 &point) -{ - return rotation * point; -} - -template<typename F> -inline void pose_transform::with_image_lock(F&& fun) -{ - lock_guard l(mtx2); - - fun(image2); -} + // 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(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); +} + +} // ns pose_widget_impl diff --git a/pose-widget/pose-widget.hpp b/pose-widget/pose-widget.hpp index 3e98624c..9152e960 100644 --- a/pose-widget/pose-widget.hpp +++ b/pose-widget/pose-widget.hpp @@ -7,92 +7,38 @@ #pragma once -#include <QtGlobal> -#include <QWidget> -#include <QThread> -#include <QPixmap> #include "api/plugin-api.hpp" #include "compat/euler.hpp" -#include <mutex> -#include <atomic> +#include "export.hpp" -#ifdef BUILD_POSE_WIDGET -# define POSE_WIDGET_EXPORT Q_DECL_EXPORT -#else -# define POSE_WIDGET_EXPORT Q_DECL_IMPORT -#endif +#include <QWidget> +#include <QImage> +#include <QCheckBox> +//#define TEST namespace pose_widget_impl { -using num = float; -using vec3 = Mat<num, 3, 1>; -using vec2 = Mat<num, 2, 1>; - -using rmat = Mat<num, 3, 3>; - using namespace euler; -using lock_guard = std::unique_lock<std::mutex>; - -using cv_status = std::cv_status; - -class pose_widget; - -class pose_transform final : private QThread -{ - pose_transform(QWidget* dst); - ~pose_transform(); - - friend class pose_widget; - - void rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z); - void rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z); - - template<typename F> - void with_rotate(F&& fun, double xAngle, double yAngle, double zAngle, double x, double y, double z); - - void run() override; - - vec2 project(const vec3& point); - vec3 project2(const vec3& point); - void project_quad_texture(); - - template<typename F> - inline void with_image_lock(F&& fun); - - static vec3 normal(const vec3& p1, const vec3& p2, const vec3& p3); - - rmat rotation, rotation_; - vec3 translation, translation_; - - std::mutex mtx, mtx2; - - QWidget* dst; - - QImage front, back; - QImage image, image2; - - int width, height; - - std::atomic<bool> fresh; - - static constexpr int w = 320, h = 240; -}; - -class POSE_WIDGET_EXPORT pose_widget final : public QWidget +struct OTR_POSE_WIDGET_EXPORT pose_widget final : QWidget { public: - pose_widget(QWidget *parent = nullptr); - ~pose_widget(); - void rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z); - void rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z); - + explicit pose_widget(QWidget *parent = nullptr); + void present(double xAngle, double yAngle, double zAngle, double x, double y, double z); + QCheckBox mirror{"Mirror", this}; private: - pose_transform xform; - void paintEvent(QPaintEvent *event) override; + 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) + .mirrored(true,false)}; + QImage shine {QImage{front.width(), front.height(), QImage::Format_ARGB32}}; + QImage shadow{QImage{front.width(), front.height(), QImage::Format_ARGB32}}; }; } -using pose_widget_impl::pose_widget; +using pose_widget = pose_widget_impl::pose_widget; |
