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)};  };  } | 
