diff options
-rw-r--r-- | pose-widget/pose-widget.cpp | 383 | ||||
-rw-r--r-- | pose-widget/pose-widget.hpp | 83 |
2 files changed, 35 insertions, 431 deletions
diff --git a/pose-widget/pose-widget.cpp b/pose-widget/pose-widget.cpp index e9bf1d01..47053ed4 100644 --- a/pose-widget/pose-widget.cpp +++ b/pose-widget/pose-widget.cpp @@ -6,389 +6,62 @@ */ #include "pose-widget.hpp" -#include "compat/timer.hpp" -#include "compat/sleep.hpp" #include "compat/check-visible.hpp" #include "compat/math.hpp" -#include <cmath> -#include <algorithm> - #include <QPainter> #include <QtEvents> #include <QDebug> - -// XXX this needs rewriting in terms of scanline rendering -sh 20180105 -// see: <https://mikro.naprvyraz.sk/docs/Coding/2/TEXTURE4.TXT> +#include <QImage> namespace pose_widget_impl { -pose_transform::pose_transform(QWidget* dst, double dpr) : dst(dst) +pose_widget::pose_widget(QWidget* parent) : QWidget(parent) { - for (QImage* img : { &image, &image2, &front, &back }) - img->setDevicePixelRatio(dpr); - - image.fill(Qt::transparent); - image2.fill(Qt::transparent); } -pose_transform::~pose_transform() +void pose_widget::present(double yaw, double pitch, double roll, double x, double y, double z) { - requestInterruption(); - wait(); -} - -void pose_widget::paintEvent(QPaintEvent* event) -{ - QPainter p(this); + T = { x, y, z }; + R = { yaw, pitch, roll }; - xform.with_image_lock([&](const QImage& image) - { - p.drawImage(event->rect(), image, QRect(0, 0, pose_transform::w, pose_transform::h)); - }); - - if (!xform.isRunning()) - xform.start(QThread::LowPriority); + repaint(); } -void pose_transform::run() +void pose_widget::paintEvent(QPaintEvent*) { - for (;;) - { - if (isInterruptionRequested()) - break; - - { - lock_guard l(mtx); - - if (fresh.load(std::memory_order_relaxed)) - goto end; + auto [ yaw, pitch, roll ] = R; + auto [ x, y, z ] = T; - rotation = rotation_; - translation = translation_; - } + const QImage& img = std::fabs(pitch) > 90 || std::fabs(yaw) > 90 + ? back + : front; - project_quad_texture(); + int w = img.width(), h = img.height(); -end: - portable::sleep(23); - } -} + QTransform t; -pose_widget::pose_widget(QWidget* parent) : QWidget(parent), xform{this, devicePixelRatioF()} -{ - rotate_sync(0,0,0, 0,0,0); -} + t.translate(w*.5, h*.5); -pose_widget::~pose_widget() = default; + constexpr double z_scale = 1./100; + constexpr double xy_scale = 3./400; + double xy = std::sqrt(w*w + h*h) * xy_scale; -void pose_widget::rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z) -{ - if (!check_is_visible()) - return; - - if (bool X = true; xform.fresh.compare_exchange_weak(X, false, std::memory_order_relaxed)) - { - repaint(); - xform.rotate_async(xAngle, yAngle, zAngle, x, y, z); - } -} - -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; - - constexpr double d2r = M_PI / 180; - - euler::euler_t euler(-zAngle * d2r, xAngle * d2r, -yAngle * d2r); - euler::rmat r = euler::euler_to_rmat(euler); - - lock_guard l(mtx); - - for (int i = 0; i < 3; i++) - for (int j = 0; j < 3; j++) - rotation_(i, j) = num(r(i, j)); - - translation_ = vec3(x, y, z); - - fun(); -} - -void pose_widget::rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z) -{ - xform.rotate_sync(xAngle, yAngle, zAngle, x, y, z); -} - -void pose_transform::rotate_async(double xAngle, double yAngle, double zAngle, double x, double y, double z) -{ - with_rotate([] {}, xAngle, yAngle, zAngle, x, y, z); -} - -void pose_transform::rotate_sync(double xAngle, double yAngle, double zAngle, double x, double y, double z) -{ - with_rotate([this] { - rotation = rotation_; - translation = translation_; - project_quad_texture(); - dst->repaint(); - }, xAngle, yAngle, zAngle, x, y, z); -} - -Triangle::Triangle(const vec2& p1, const vec2& p2, const vec2& p3) -{ - origin = p1; - - v0 = vec2(p3 - p1); - v1 = vec2(p2 - p1); - - dot00 = v0.dot(v0); - dot01 = v0.dot(v1); - dot11 = v1.dot(v1); - - const num denom = dot00 * dot11 - dot01 * dot01; - - 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; -} - -std::pair<vec2i, vec2i> pose_transform::get_bounds(const vec2& size) -{ - const num x = size.x(), y = size.y(); + double s = clamp(.5 + -z * z_scale, .5, 2); + t.scale(s, s); - const vec3 corners[] = { - { -x, -y, 0 }, - { x, -y, 0 }, - { -x, y, 0 }, - { x, y, 0 }, - }; + t.rotate(pitch, Qt::XAxis); + t.rotate(yaw, Qt::YAxis); + t.rotate(roll, Qt::ZAxis); - vec2 min(w-1, h-1), max(0, 0); + t.translate(x * xy / s, y * xy / s); - for (unsigned k = 0; k < 4; k++) // NOLINT(modernize-loop-convert) - { - const vec2 pt = project(corners[k]) + vec2(w/2, h/2); + t.translate(w*-.5, h*-.5); - min.x() = std::fmin(min.x(), pt.x()); - min.y() = std::fmin(min.y(), pt.y()); - - max.x() = std::fmax(max.x(), pt.x()); - max.y() = std::fmax(max.y(), pt.y()); - } - - min.x() = clamp(min.x(), 0, w-1); - min.y() = clamp(min.y(), 0, h-1); - max.x() = clamp(max.x(), 0, w-1); - max.y() = clamp(max.y(), 0, h-1); - -#if 0 - { - QPainter p(&image); - p.drawRect(min.x(), min.y(), max.x()-min.x(), max.y()-min.y()); - } -#endif - - return std::make_pair(vec2i(iround(min.x()), iround(min.y())), - vec2i(iround(max.x()), iround(max.y()))); -} - -void pose_transform::project_quad_texture() -{ - vec3 bgcolor; - { - QColor bg = dst->palette().background().color(); - image.fill(bg); - bgcolor = vec3(bg.red(), bg.green(), bg.blue()); - } - - num dir; - vec2 pt[4]; - - vec2i min, max; - - { - constexpr double c = 85/100.; - - const int sx_ = (w - std::max(0, (w - h)/2)) * 5/9; - const int sy_ = (h - std::max(0, (h - w)/2)) * 5/9; - - std::tie(min, max) = get_bounds(vec2(sx_/2.*c, sy_/2)); - - const vec3 dst_corners[] = - { - { -sx_/2. * c, -sy_/2., 0 }, - { sx_/2. * c, -sy_/2., 0 }, - { -sx_/2. * c, sy_/2., 0 }, - { sx_/2. * c, sy_/2., 0 }, - }; - - for (int i = 0; i < 4; i++) - pt[i] = project(dst_corners[i]) + vec2(w/2, h/2); - - { - vec3 foo[3]; - for (int i = 0; i < 3; i++) - foo[i] = project2(dst_corners[i]); - - vec3 p1 = foo[1] - foo[0]; - vec3 p2 = foo[2] - foo[0]; - dir = p1.x() * p2.y() - p1.y() * p2.x(); // Z part of the cross product - } - } - - // rotation of (0, 90, 0) makes it numerically unstable - if (std::fabs(dir) < 1e-3f) - { - lock_guard l(mtx2); - image.swap(image2); - fresh.store(true, std::memory_order_relaxed); - return; - } - - const QImage& tex = dir < 0 ? back : front; - - const unsigned orig_pitch = (unsigned)tex.bytesPerLine(); - const unsigned dest_pitch = (unsigned)image.bytesPerLine(); - - unsigned char const* __restrict orig = tex.constBits(); - unsigned char* __restrict dest = image.bits(); - - const int orig_depth = tex.depth() / 8; - const int dest_depth = image.depth() / 8; - constexpr unsigned const_depth = 4; - - if (unlikely(orig_depth != const_depth || dest_depth != const_depth)) - { - qDebug() << "pose-widget: octopus must be saved as .png with 32 bits depth"; - qDebug() << "pose-widget: target texture must be ARGB32"; - return; - } - - Triangle t(pt[0], pt[1], pt[2]); - - const vec2u dist(max.x() - min.x(), max.y() - min.y()); - unsigned len = (unsigned)(dist.x() * dist.y()); - - if (uv_vec.size() < len) - uv_vec.resize(len); - - for (int y = 0; y < dist.y(); y++) - for (int x = 0; x < dist.x(); x++) - { - uv_* __restrict uv = &uv_vec[y * dist.x() + x]; - if (!t.barycentric_coords(vec2(x + min.x(), y + min.y()), uv->coords, uv->i)) - uv->i = -1; - } - - const int ow = tex.width(), oh = tex.height(); - - vec2 const origs[2][3] = - { - { - { 0, 0 }, - { ow-1, 0 }, - { 0, oh-1 }, - }, - { - { ow-1, oh-1 }, - vec2(0, oh-1) - vec2(ow-1, oh-1), - vec2(ow-1, 0) - vec2(ow-1, oh-1), - } - }; - - for (int y_ = 0, dy = dist.y(); y_ < dy; y_++) - { - for (int x_ = 0, dx = dist.x(); x_ < dx; x_++) - { - const int y = y_ + min.y(), x = x_ + min.x(); - const uv_* __restrict uv__ = &uv_vec[y_ * dx + x_]; - - if (uv__->i != -1) - { - using uc = unsigned char; - - vec2 const& uv = uv__->coords; - int const i = uv__->i; - - unsigned px = (unsigned)(origs[i][0].x() + - uv.x() * origs[i][2].x() + - uv.y() * origs[i][1].x()); - unsigned py = (unsigned)(origs[i][0].y() + - uv.x() * origs[i][2].y() + - uv.y() * origs[i][1].y()); - - const unsigned orig_pos = py * orig_pitch + px * const_depth; - const unsigned pos = y * dest_pitch + x * const_depth; - - if (orig[orig_pos + 3] == uc(255)) // alpha - for (int k = 0; k < 3; k++) - dest[pos + k] = orig[orig_pos + k]; - else - for (int k = 0; k < 3; k++) - dest[pos + k] = (unsigned char)bgcolor(k); - } - } - } - - { - lock_guard l2(mtx2); - image.swap(image2); - fresh.store(true, std::memory_order_relaxed); - } -} - -vec2 pose_transform::project(const vec3 &point) -{ - using std::fabs; - - vec3 ret = rotation * point; - num z = std::fmax(num(.5), 1 + translation.z()/-80); - num w_ = w, h_ = h; - 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 { 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); + QPainter p(this); + p.setTransform(t); + p.drawImage(rect(), img); } } // ns pose_widget_impl diff --git a/pose-widget/pose-widget.hpp b/pose-widget/pose-widget.hpp index 1e50b392..15dd7866 100644 --- a/pose-widget/pose-widget.hpp +++ b/pose-widget/pose-widget.hpp @@ -12,94 +12,25 @@ #include "export.hpp" -#include <tuple> -#include <mutex> -#include <atomic> -#include <vector> - #include <QWidget> -#include <QThread> #include <QImage> namespace pose_widget_impl { -using num = float; -using vec3 = Mat<num, 3, 1>; -using vec2 = Mat<num, 2, 1>; -using vec2i = Mat<int, 2, 1>; -using vec2u = Mat<int, 2, 1>; - -using rmat = Mat<num, 3, 3>; - using namespace euler; -using lock_guard = std::unique_lock<std::mutex>; - -class pose_widget; - -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; -}; - -struct pose_transform final : QThread -{ - pose_transform(QWidget* dst, double device_pixel_ratio); - ~pose_transform() override; - - 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(); - std::pair<vec2i, vec2i> get_bounds(const vec2& size); - - template<typename F> inline void with_image_lock(F&& fun); - - rmat rotation, rotation_; - vec3 translation, translation_; - - std::mutex mtx, mtx2; - - QWidget* dst; - - QImage front{QImage{":/images/side1.png"}.convertToFormat(QImage::Format_ARGB32)}; - QImage back{QImage{":/images/side6.png"}.convertToFormat(QImage::Format_ARGB32)}; - QImage image{w, h, QImage::Format_ARGB32}; - QImage image2{w, h, QImage::Format_ARGB32}; - - struct uv_ // NOLINT(cppcoreguidelines-pro-type-member-init) - { - vec2 coords; - int i; - }; - - std::vector<uv_> uv_vec; - std::atomic<bool> fresh = false; - - static constexpr int w = 320, h = 240; -}; - -class OTR_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() override; - 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); + void present(double xAngle, double yAngle, double zAngle, double x, double y, double z); private: - pose_transform xform; - void paintEvent(QPaintEvent *event) override; + void paintEvent(QPaintEvent*) override; + + euler_t R, T; + QImage front{QImage{":/images/side1.png"}.convertToFormat(QImage::Format_ARGB32)}; + QImage back{QImage{":/images/side6.png"}.convertToFormat(QImage::Format_ARGB32)}; }; } |