From d37608dac1783d9a15cbdc3d6d49173afde0835d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 12 Apr 2020 10:32:23 +0200 Subject: [PATCH] WIP. --- .../tools/view_25d/lay_plugin/layD25View.cc | 3 + .../view_25d/lay_plugin/layD25ViewUtils.cc | 61 +++++++ .../view_25d/lay_plugin/layD25ViewUtils.h | 12 ++ .../view_25d/lay_plugin/layD25ViewWidget.cc | 154 +++++++++++++----- .../view_25d/lay_plugin/layD25ViewWidget.h | 10 +- .../unit_tests/layD25ViewUtilsTests.cc | 62 +++++++ 6 files changed, 260 insertions(+), 42 deletions(-) diff --git a/src/plugins/tools/view_25d/lay_plugin/layD25View.cc b/src/plugins/tools/view_25d/lay_plugin/layD25View.cc index 44f905517..fdb5230d9 100644 --- a/src/plugins/tools/view_25d/lay_plugin/layD25View.cc +++ b/src/plugins/tools/view_25d/lay_plugin/layD25View.cc @@ -37,6 +37,9 @@ D25View::D25View (QWidget *parent) mp_ui = new Ui::D25View (); mp_ui->setupUi (this); + // @@@ should be an event filter? + mp_ui->d25_view->setFocusPolicy (Qt::StrongFocus); + mp_ui->d25_view->setFocus (); // @@@ } diff --git a/src/plugins/tools/view_25d/lay_plugin/layD25ViewUtils.cc b/src/plugins/tools/view_25d/lay_plugin/layD25ViewUtils.cc index b194e55fa..e63a75969 100644 --- a/src/plugins/tools/view_25d/lay_plugin/layD25ViewUtils.cc +++ b/src/plugins/tools/view_25d/lay_plugin/layD25ViewUtils.cc @@ -161,4 +161,65 @@ camera_normal (const QMatrix4x4 &camera_trans, double x, double y) return (std::make_pair (p, u.normalized ())); } +void +normalize_scene_trans (const QMatrix4x4 &cam_trans, QVector3D &displacement, double &scale, double ztarget) +{ + QMatrix4x4 m; + + // Here is the theory: + // Let: + // cam = ( M t ) M = 3x3 matrix, t = 3x1 translation vector, z = scalar, p = 1x3 perspective + // ( p z ) + // and: + // scene = ( S d ) S = s*U1 (s = scale factor, U1 = 3x3 unit matrix), d = 3x1 displacement vector + // ( 0 1 ) + // then: + // cam * scene = ( M*s M*d+t ) + // ( p*s p*d+z ) (p*d = dot product) + // + // this is image invariant (only x,y results are considered) against changes of s (s->s') if + // + // 1.) (p*d+z)/s = (p*d'+z)/s' + // 2.) (M*d+t)/s = (M*d'+t)/s' for [x] and [y] + // + // Ff we seek a solution with d'[z] == b (b = ztarget), we get these equations (f:=s'/s) + // + // 2.) M[xx] * d'[x] + M[xy] * d'[y] - ((M*d)[x] + t[x]) * f = -t[x] - M[xz] * b + // M[yx] * d'[x] + M[yy] * d'[y] - ((M*d)[y] + t[y]) * f = -t[y] - M[yz] * b + // 1.) p[x] * d'[x] + p[y] * d'[y] - (p*d+z) * f = -z - p[z] * b + // + // we can solve these equations for d'[x], d'[y] and f. + // With p[x]=M[wx], p[y]=M[wy] and z=t[w], the above equation system can be written as + // + // M[ix] * d'[x] + M[iy] * d'[y] - ((M*d)[i] + t[i]) * f = -t[i] - M[iz]*b i = x,y,w + // + // and with M,t->M (4x4 matrix) and d->(d,1) (4d vector) + // + // M[ix] * d'[x] + M[iy] * d'[y] - (M*d)[i] * f = -t[i] - M[iz]*b i = x,y,w + // + + QVector4D d4 (displacement); + d4.setW (1.0); + + for (int i = 0; i < 4; ++i) { + if (i != 2) { + m (i, 0) = cam_trans (i, 0); + m (i, 1) = cam_trans (i, 1); + m (i, 3) = -QVector4D::dotProduct (cam_trans.row (i), d4); + } + } + + bool invertable = false; + m = m.inverted (&invertable); + if (! invertable) { + return; + } + + QVector4D sol = m.map (-cam_trans.column (3) - cam_trans.column (2) * ztarget); + if (sol.w () > 0.01 /*skip weird solutions*/) { + scale *= sol.w (); + displacement = QVector3D (sol.x (), sol.y (), ztarget); + } +} + } diff --git a/src/plugins/tools/view_25d/lay_plugin/layD25ViewUtils.h b/src/plugins/tools/view_25d/lay_plugin/layD25ViewUtils.h index c875c771d..b4bc140ea 100644 --- a/src/plugins/tools/view_25d/lay_plugin/layD25ViewUtils.h +++ b/src/plugins/tools/view_25d/lay_plugin/layD25ViewUtils.h @@ -74,6 +74,18 @@ hit_point_with_cuboid (const QVector3D &line, const QVector3D &line_dir, const Q LAY_PLUGIN_PUBLIC std::pair camera_normal (const QMatrix4x4 &camera_trans, double x, double y); +/** + * @brief Normalizes a scene transformation + * + * Scene transformations consist of a scaling and displacement. Both are + * interchangeable to some extent under the presence of a perspective + * transformation (further away makes the scene smaller). This normalization + * tries to find a displacement which has "ztarget" target value for z. Without normalization + * the scene tends to "move away" with respect to z. + */ +LAY_PLUGIN_PUBLIC void +normalize_scene_trans (const QMatrix4x4 &cam_trans, QVector3D &displacement, double &scale, double ztarget = 0.0); + } #endif diff --git a/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc b/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc index dadf2d297..04f965992 100644 --- a/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc +++ b/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.cc @@ -34,6 +34,7 @@ #include #include +#include #include "math.h" @@ -44,14 +45,13 @@ namespace lay D25ViewWidget::D25ViewWidget (QWidget *parent) : QOpenGLWidget (parent), - m_shapes_program (0), m_dragging (false), m_rotating (false), m_cam_azimuth (0.0), m_cam_elevation (0.0) + m_shapes_program (0), m_dragging (false), m_rotating (false), m_cam_azimuth (0.0), m_cam_elevation (0.0), m_top_view (false) { QSurfaceFormat format; format.setDepthBufferSize (24); format.setSamples (4); // more -> widget extends beyond boundary! setFormat (format); - m_cam_position = QVector3D (0.0, 0.0, 4.0); // @@@ m_scale_factor = 1.0; m_focus_dist = 0.0; } @@ -183,48 +183,87 @@ D25ViewWidget::initializeGL () } } -static QVector3D cam_direction (double azimuth, double elevation) -{ - // positive azimuth: camera looks left - // positive elevation: camera looks up - double y = sin (elevation * M_PI / 180.0); - double r = cos (elevation * M_PI / 180.0); - double x = r * sin (azimuth * M_PI / 180.0); - double z = r * cos (azimuth * M_PI / 180.0); - return QVector3D (x, y, -z); -} - void D25ViewWidget::wheelEvent (QWheelEvent *event) { double px = (event->pos ().x () - width () / 2) * 2.0 / width (); double py = -(event->pos ().y () - height () / 2) * 2.0 / height (); - double f = exp (event->angleDelta ().y () * (1.0 / (90 * 8))); - // compute vector of line of sight std::pair ray = camera_normal (m_cam_trans, px, py); // by definition the ray goes through the camera position - std::pair hp = hit_point_with_scene (m_cam_position, ray.second); + float focal_length = 2.0; + QVector3D hp = hit_point_with_scene (cam_position () + focal_length * ray.second, ray.second); - QVector3D pm = m_cam_trans.map(hp.second); - printf("@@@ mouse=%g,%g back=%g,%g\n", px, py, pm.x(), pm.y()); - printf("@@@ before: hp=%g,%g,%g d=%g,%g,%g s=%g\n", hp.second.x(), hp.second.y(), hp.second.z(), m_displacement.x(), m_displacement.y(), m_displacement.z(), m_scale_factor); fflush(stdout); + if (false /*@@@*/ && (event->modifiers () & Qt::ShiftModifier)) { - m_displacement = hp.second * (1.0 - f) + m_displacement * f; - m_scale_factor *= f; + // "Shift" is closeup + + double f = event->angleDelta ().y () * (1.0 / (90 * 8)); + m_displacement += -f * cam_position ().length () * ray.second; + + } else { + + // No shift is zoom + + double f = exp (event->angleDelta ().y () * (1.0 / (90 * 8))); + + QVector3D initial_displacement = m_displacement; + QVector3D displacement = m_displacement; + + displacement = hp * (1.0 - f) + displacement * f; + m_scale_factor *= f; + + // normalize the scene translation so the scene does not "flee" + + QMatrix4x4 ct; + ct.rotate (-cam_elevation (), 1.0, 0.0, 0.0); + ct.rotate (cam_azimuth (), 0.0, 1.0, 0.0); + ct.translate (-cam_position ()); + + initial_displacement = ct.map (initial_displacement); + displacement = ct.map (displacement); + + lay::normalize_scene_trans (m_cam_trans, displacement, m_scale_factor, initial_displacement.z ()); + + m_displacement = ct.inverted ().map (displacement); + + } update_cam_trans (); } -std::pair +void +D25ViewWidget::keyPressEvent (QKeyEvent *event) +{ + if (event->key () == Qt::Key_Shift) { + m_top_view = true; + update_cam_trans (); + } +} + +void +D25ViewWidget::keyReleaseEvent (QKeyEvent *event) +{ + if (event->key () == Qt::Key_Shift) { + m_top_view = false; + update_cam_trans (); + } +} + +QVector3D D25ViewWidget::hit_point_with_scene (const QVector3D &line, const QVector3D &line_dir) { QVector3D corner = QVector3D (m_bbox.left (), m_zmin, -(m_bbox.bottom () + m_bbox.height ())) * m_scale_factor + m_displacement; QVector3D dim = QVector3D (m_bbox.width (), m_zmax - m_zmin, m_bbox.height ()) * m_scale_factor; - return lay::hit_point_with_cuboid (line, line_dir, corner, dim); + std::pair hp = lay::hit_point_with_cuboid (line, line_dir, corner, dim); + if (! hp.first) { + return line; + } else { + return hp.second; + } } void @@ -238,20 +277,19 @@ D25ViewWidget::mousePressEvent (QMouseEvent *event) } m_start_pos = event->pos (); - m_start_cam_position = m_cam_position; - m_start_cam_azimuth = m_cam_azimuth; - m_start_cam_elevation = m_cam_elevation; + m_start_cam_position = cam_position (); + m_start_cam_azimuth = cam_azimuth (); + m_start_cam_elevation = cam_elevation (); m_start_displacement = m_displacement; m_focus_dist = 2.0; if (m_dragging || m_rotating) { - QVector3D cd = cam_direction (m_start_cam_azimuth, m_start_cam_elevation); - std::pair hp = hit_point_with_scene (m_cam_position, cd); - if (hp.first) { - m_focus_dist = std::max (m_focus_dist, double ((m_cam_position - hp.second).length ())); - } + QVector3D cd = cam_direction (); + QVector3D cp = cam_position (); + QVector3D hp = hit_point_with_scene (cp + m_focus_dist * cd, cd); + m_focus_dist = std::max (m_focus_dist, double ((cp - hp).length ())); } } @@ -275,9 +313,9 @@ D25ViewWidget::mouseMoveEvent (QMouseEvent *event) double dx = d.x () * f; double dy = -d.y () * f; - QVector3D xv (cos (m_start_cam_azimuth * M_PI / 180.0), 0.0, -sin (m_start_cam_azimuth * M_PI / 180.0)); - double re = sin (m_start_cam_elevation * M_PI / 180.0); - QVector3D yv (-re * xv.z (), cos (m_start_cam_elevation * M_PI / 180.0), re * xv.x ()); + QVector3D xv (cos (cam_azimuth () * M_PI / 180.0), 0.0, -sin (cam_azimuth () * M_PI / 180.0)); + double re = sin (cam_elevation () * M_PI / 180.0); + QVector3D yv (-re * xv.z (), cos (cam_elevation () * M_PI / 180.0), re * xv.x ()); QVector3D drag = xv * dx + yv * dy; m_displacement = m_start_displacement + drag; @@ -286,6 +324,8 @@ D25ViewWidget::mouseMoveEvent (QMouseEvent *event) } else if (m_rotating) { + // @@@ needs redo ... + // @@@ consider m_top_view double focus_dist = 4.0; // @@@ QPoint d = event->pos () - m_start_pos; @@ -296,30 +336,64 @@ D25ViewWidget::mouseMoveEvent (QMouseEvent *event) m_cam_elevation = m_start_cam_elevation + ay; m_cam_azimuth = m_start_cam_azimuth + ax; - m_cam_position = (cam_direction (m_cam_azimuth, m_cam_elevation) * -focus_dist) + cam_direction (m_start_cam_azimuth, m_start_cam_elevation) * focus_dist + m_start_cam_position; - update_cam_trans (); } } +QVector3D +D25ViewWidget::cam_direction () const +{ + double azimuth = cam_azimuth (); + double elevation = cam_elevation (); + + // positive azimuth: camera looks left + // positive elevation: camera looks up + double y = sin (elevation * M_PI / 180.0); + double r = cos (elevation * M_PI / 180.0); + double x = r * sin (azimuth * M_PI / 180.0); + double z = r * cos (azimuth * M_PI / 180.0); + return QVector3D (x, y, -z); +} + +QVector3D +D25ViewWidget::cam_position () const +{ + double focus_dist = 4.0; + return cam_direction () * -focus_dist; +} + +double +D25ViewWidget::cam_azimuth () const +{ + return m_cam_azimuth; +} + +double +D25ViewWidget::cam_elevation () const +{ + return m_top_view ? -90.0 : m_cam_elevation; +} + void D25ViewWidget::update_cam_trans () { -printf("@@@ e=%g a=%g x,y,z=%g,%g,%g d=%g,%g,%g s=%g f=%g\n", m_cam_elevation, m_cam_azimuth, m_cam_position.x(), m_cam_position.y(), m_cam_position.z(), m_displacement.x(), m_displacement.y(), m_displacement.z(), m_scale_factor, m_focus_dist); fflush(stdout); + QVector3D cp = cam_position (); + +printf("@@@ e=%g a=%g x,y,z=%g,%g,%g d=%g,%g,%g s=%g f=%g\n", cam_elevation (), cam_azimuth (), cp.x(), cp.y(), cp.z(), m_displacement.x(), m_displacement.y(), m_displacement.z(), m_scale_factor, m_focus_dist); fflush(stdout); QMatrix4x4 t; // finally add perspective t.perspective (60.0f, float (width ()) / float (height ()), 0.1f, 100.0f); // third: elevation - t.rotate (-m_cam_elevation, 1.0, 0.0, 0.0); + t.rotate (-cam_elevation (), 1.0, 0.0, 0.0); // second: azimuth - t.rotate (m_cam_azimuth, 0.0, 1.0, 0.0); + t.rotate (cam_azimuth (), 0.0, 1.0, 0.0); // first: translate the origin into the cam's position - t.translate (-m_cam_position); + t.translate (-cam_position ()); m_cam_trans = t; diff --git a/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.h b/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.h index 26a0653ba..68bc1a468 100644 --- a/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.h +++ b/src/plugins/tools/view_25d/lay_plugin/layD25ViewWidget.h @@ -57,6 +57,8 @@ public: D25ViewWidget (QWidget *parent); ~D25ViewWidget (); + void keyPressEvent (QKeyEvent *event); + void keyReleaseEvent (QKeyEvent *event); void wheelEvent (QWheelEvent *event); void mousePressEvent (QMouseEvent *event); void mouseReleaseEvent (QMouseEvent *event); @@ -70,9 +72,9 @@ private: QOpenGLShaderProgram *m_shapes_program, *m_gridplane_program; QMatrix4x4 m_cam_trans; bool m_dragging, m_rotating; - QVector3D m_cam_position; double m_scale_factor; double m_cam_azimuth, m_cam_elevation; + bool m_top_view; QVector3D m_displacement; double m_focus_dist; QPoint m_start_pos; @@ -101,7 +103,11 @@ private: void render_layout (D25ViewWidget::chunks_type &chunks, const db::Layout &layout, const db::Cell &cell, unsigned int layer, double zstart, double zstop); void render_polygon (D25ViewWidget::chunks_type &chunks, const db::Polygon &poly, double dbu, double zstart, double zstop); void render_wall (D25ViewWidget::chunks_type &chunks, const db::Edge &poly, double dbu, double zstart, double zstop); - std::pair hit_point_with_scene (const QVector3D &line, const QVector3D &line_dir); + QVector3D hit_point_with_scene(const QVector3D &line, const QVector3D &line_dir); + double cam_elevation () const; + double cam_azimuth () const; + QVector3D cam_position () const; + QVector3D cam_direction () const; }; } diff --git a/src/plugins/tools/view_25d/unit_tests/layD25ViewUtilsTests.cc b/src/plugins/tools/view_25d/unit_tests/layD25ViewUtilsTests.cc index e5cf71c65..4d9daa5b6 100644 --- a/src/plugins/tools/view_25d/unit_tests/layD25ViewUtilsTests.cc +++ b/src/plugins/tools/view_25d/unit_tests/layD25ViewUtilsTests.cc @@ -166,3 +166,65 @@ TEST(5_CameraNormal) p = matrix.map (ray.first + ray.second * 1000.0); EXPECT_EQ (v2s_2d (p), "0,1"); } + +TEST(6_NormalizeSceneTrans) +{ + QMatrix4x4 cam; + cam.perspective (60.0f, 1.5, 0.1f, 100.0f); + cam.rotate (22.0, 1.0, 0.0, 0.0); + cam.rotate (-15.0, 0.0, 1.0, 0.0); + cam.translate (QVector3D (0.0, 0.0, 4.0)); + + double scale = 0.1; + QVector3D displacement (-0.5, 0.2, 2.0); + + QMatrix4x4 scene1; + scene1.translate (displacement); + scene1.scale (scale); + + QVector3D v1 = (cam * scene1).map (QVector3D (1.0, -1.0, 2.0)); + v1.setZ (0); + QVector3D v2 = (cam * scene1).map (QVector3D (0.0, 0.0, 5.0)); + v2.setZ (0); + QVector3D v3 = (cam * scene1).map (QVector3D (-1.0, 0.0, 1.0)); + v3.setZ (0); + + lay::normalize_scene_trans (cam, displacement, scale); + + QMatrix4x4 scene2; + scene2.translate (displacement); + scene2.scale (scale); + + EXPECT_EQ (tl::sprintf ("%.4f", scale), "0.0667"); + + QVector3D u1 = (cam * scene2).map (QVector3D (1.0, -1.0, 2.0)); + u1.setZ (0); + QVector3D u2 = (cam * scene2).map (QVector3D (0.0, 0.0, 5.0)); + u2.setZ (0); + QVector3D u3 = (cam * scene2).map (QVector3D (-1.0, 0.0, 1.0)); + u3.setZ (0); + + EXPECT_EQ ((u1 - v1).length () < 1e-4, true); + EXPECT_EQ ((u2 - v2).length () < 1e-4, true); + EXPECT_EQ ((u3 - v3).length () < 1e-4, true); + + lay::normalize_scene_trans (cam, displacement, scale, 1.0); + + QMatrix4x4 scene3; + scene3.translate (displacement); + scene3.scale (scale); + + EXPECT_EQ (tl::sprintf ("%.4f", scale), "0.0833"); + EXPECT_EQ (tl::to_string (displacement.z ()), "1"); + + QVector3D uu1 = (cam * scene2).map (QVector3D (1.0, -1.0, 2.0)); + uu1.setZ (0); + QVector3D uu2 = (cam * scene2).map (QVector3D (0.0, 0.0, 5.0)); + uu2.setZ (0); + QVector3D uu3 = (cam * scene2).map (QVector3D (-1.0, 0.0, 1.0)); + uu3.setZ (0); + + EXPECT_EQ ((uu1 - v1).length () < 1e-4, true); + EXPECT_EQ ((uu2 - v2).length () < 1e-4, true); + EXPECT_EQ ((uu3 - v3).length () < 1e-4, true); +}