summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--pose-widget/pose-widget.cpp383
-rw-r--r--pose-widget/pose-widget.hpp83
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)};
};
}