summaryrefslogtreecommitdiffhomepage
path: root/pose-widget/pose-widget.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'pose-widget/pose-widget.cpp')
-rw-r--r--pose-widget/pose-widget.cpp448
1 files changed, 104 insertions, 344 deletions
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