From 39e209983bd1f04fb0beefef754d7430c8b7fb9f Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Fri, 20 Oct 2017 17:53:21 +0200 Subject: logic, spline: more spline work/fixes - mapping window clamps are now value in spline - clamp won't mess up saved spline content - clean up artifacts on spline widget --- spline/CMakeLists.txt | 4 +- spline/axis-opts.cpp | 47 ++++++++++++ spline/axis-opts.hpp | 53 ++++++++++++++ spline/spline-widget.cpp | 33 +++++++-- spline/spline.cpp | 183 ++++++++++++++++++++++------------------------- spline/spline.hpp | 33 +++++---- 6 files changed, 235 insertions(+), 118 deletions(-) create mode 100644 spline/axis-opts.cpp create mode 100644 spline/axis-opts.hpp (limited to 'spline') diff --git a/spline/CMakeLists.txt b/spline/CMakeLists.txt index d252004b..7d427601 100644 --- a/spline/CMakeLists.txt +++ b/spline/CMakeLists.txt @@ -1,2 +1,2 @@ -otr_module(spline NO-COMPAT BIN) -target_link_libraries(opentrack-spline opentrack-options opentrack-compat) +otr_module(spline BIN) +target_link_libraries(opentrack-spline) diff --git a/spline/axis-opts.cpp b/spline/axis-opts.cpp new file mode 100644 index 00000000..52fe20f5 --- /dev/null +++ b/spline/axis-opts.cpp @@ -0,0 +1,47 @@ +#include "axis-opts.hpp" + +using max_clamp = axis_opts::max_clamp; + +static max_clamp get_max_x(Axis k) +{ + if (k == Axis(-1)) + return max_clamp::x1000; + if (k == Pitch) + return max_clamp::r90; + if (k >= Yaw) + return max_clamp::r180; + return max_clamp::t30; +} + +static max_clamp get_max_y(Axis k) +{ + if (k == Axis(-1)) + return max_clamp::x1000; + if (k == Axis::Pitch) + return max_clamp::o_r90; + if (k >= Axis::Yaw) + return max_clamp::o_r180; + return max_clamp::o_t75; +} + +axis_opts::axis_opts(QString pfx, Axis idx) : + b_settings_window(make_bundle("opentrack-ui")), + b_mapping_window(make_bundle("opentrack-mappings")), + zero(b_settings_window, n(pfx, "zero-pos"), 0), + src(b_settings_window, n(pfx, "source-index"), idx), + invert(b_settings_window, n(pfx, "invert-sign"), false), + altp(b_mapping_window, n(pfx, "alt-axis-sign"), false), + clamp_x_(b_mapping_window, n(pfx, "max-value"), get_max_x(idx)), + clamp_y_(b_mapping_window, n(pfx, "max-output-value"), get_max_y(idx)), + prefix_(pfx), + axis_(idx) +{} + +QString const& axis_opts::prefix() const { return prefix_; } + +Axis axis_opts::axis() const { return axis_; } + +QString axis_opts::n(QString pfx, QString name) +{ + return QString("%1-%2").arg(pfx, name); +} diff --git a/spline/axis-opts.hpp b/spline/axis-opts.hpp new file mode 100644 index 00000000..ad94236c --- /dev/null +++ b/spline/axis-opts.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "options/options.hpp" +using namespace options; +#include "api/plugin-api.hpp" + +#include "export.hpp" + +struct OTR_SPLINE_EXPORT axis_opts final +{ + enum max_clamp + { + r180 = 180, + r90 = 90, + r60 = 60, + r45 = 45, + r30 = 30, + r25 = 25, + r20 = 20, + r15 = 15, + r10 = 10, + + t100 = 100, + t30 = 30, + t20 = 20, + t15 = 15, + t10 = 10, + + o_r180 = -180, + o_r90 = -90, + o_t75 = -75, + + x1000 = 1000, + }; + + // note, these two bundles can be the same value with no issues + bundle b_settings_window, b_mapping_window; + value zero; + value src; + value invert, altp; + value clamp_x_, clamp_y_; + double max_clamp_x() const { return std::fabs(clamp_x_.to()); } + double max_clamp_y() const { return std::fabs(clamp_y_.to()); } + axis_opts(QString pfx, Axis idx); + + QString const& prefix() const; + Axis axis() const; +private: + static inline QString n(QString pfx, QString name); + + QString prefix_; + Axis axis_; +}; diff --git a/spline/spline-widget.cpp b/spline/spline-widget.cpp index 2aa5e6ac..aac4ef9c 100644 --- a/spline/spline-widget.cpp +++ b/spline/spline-widget.cpp @@ -60,12 +60,15 @@ void spline_widget::setConfig(spline* spl) if (spl) { update_range(); - _config->ensure_valid(_config->get_points()); + { + QList pts = _config->get_points(); + _config->ensure_valid(pts); + } std::shared_ptr s = spl->get_settings(); connection = connect(s.get(), &spline::settings::recomputed, this, [this]() { reload_spline(); }, - Qt::QueuedConnection); + Qt::QueuedConnection); } } @@ -104,10 +107,12 @@ void spline_widget::drawBackground() painter.fillRect(rect(), palette().background().color()); - QColor bg_color(112, 154, 209); - if (!isEnabled() && !_preview_only) - bg_color = QColor(176,176,180); - painter.fillRect(pixel_bounds, bg_color); + { + QColor bg_color(112, 154, 209); + if (!isEnabled() && !_preview_only) + bg_color = QColor(176,176,180); + painter.fillRect(pixel_bounds, bg_color); + } QFont font; font.setPointSize(8); @@ -300,6 +305,16 @@ void spline_widget::paintEvent(QPaintEvent *e) QPointF last; if (_config->get_last_value(last) && isEnabled()) drawPoint(p, point_to_pixel(last), QColor(255, 0, 0, 120)); + + const QColor bg = palette().background().color(); + + const QRect r1(pixel_bounds.left(), 0, width() - pixel_bounds.left(), pixel_bounds.top()), + r2(pixel_bounds.right(), 0, width() - pixel_bounds.right(), height()); + + // prevent topward artifacts the lazy way + p.fillRect(r1, bg); + // same for rightward artifacts + p.fillRect(r2, bg); } void spline_widget::drawPoint(QPainter& painter, const QPointF& pos, const QColor& colBG, const QColor& border) @@ -508,9 +523,13 @@ void spline_widget::mouseReleaseEvent(QMouseEvent *e) void spline_widget::reload_spline() { if (_config) - _config->ensure_valid(_config->get_points()); + { + QList pts = _config->get_points(); + _config->ensure_valid(pts); + } // don't recompute here as the value's about to be recomputed in the callee update_range(); + update(); } int spline_widget::get_closeness_limit() diff --git a/spline/spline.cpp b/spline/spline.cpp index f28cc98d..ef1e9c58 100644 --- a/spline/spline.cpp +++ b/spline/spline.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -25,16 +26,16 @@ constexpr int spline::value_count; -spline::spline(qreal maxx, qreal maxy, const QString& name) : +spline::spline(const QString& name, const QString& axis_name, Axis axis) : s(nullptr), data(value_count, -16), _mutex(QMutex::Recursive), - max_x(maxx), - max_y(maxy), + ctx(std::make_shared()), + axis(axis), activep(false), validp(false) { - set_bundle(options::make_bundle(name)); + set_bundle(options::make_bundle(name), axis_name, axis); } spline::~spline() @@ -44,11 +45,15 @@ spline::~spline() if (connection) { QObject::disconnect(connection); + QObject::disconnect(conn_maxx); + QObject::disconnect(conn_maxy); connection = QMetaObject::Connection(); + conn_maxx = QMetaObject::Connection(); + conn_maxy = QMetaObject::Connection(); } } -spline::spline() : spline(0, 0, "") {} +spline::spline() : spline("", "", Axis(-1)) {} void spline::set_tracking_active(bool value) { @@ -58,6 +63,7 @@ void spline::set_tracking_active(bool value) bundle spline::get_bundle() { + QMutexLocker l(&_mutex); // avoid logic errors due to changes in value data return s->b; } @@ -68,32 +74,6 @@ void spline::clear() validp = false; } -void spline::set_max_input(qreal max_input) -{ - QMutexLocker l(&_mutex); - max_x = max_input; - validp = false; -} - -void spline::set_max_output(qreal max_output) -{ - QMutexLocker l(&_mutex); - max_y = max_output; - validp = false; -} - -qreal spline::max_input() const -{ - QMutexLocker l(&_mutex); - return max_x; -} - -qreal spline::max_output() const -{ - QMutexLocker l(&_mutex); - return max_y; -} - float spline::get_value(double x) { QMutexLocker foo(&_mutex); @@ -113,9 +93,6 @@ float spline::get_value_no_save_internal(double x) { QMutexLocker foo(&_mutex); - if (max_x > 0) - x = std::fmin(max_x, x); - float q = float(x * bucket_size_coefficient(s->points)); int xi = (int)q; float yi = get_value_internal(xi); @@ -149,23 +126,20 @@ float spline::get_value_internal(int x) const float sign = signum(x); x = std::abs(x); const float ret_ = data[std::min(unsigned(x), unsigned(value_count)-1u)]; - float ret = sign * std::fmax(0, ret_); - if (max_y > 0) - ret = fmin(max_y, ret); - return ret; + return sign * clamp(ret_, 0, 1000); } void spline::add_lone_point() { points_t points; - points.push_back(QPointF(max_x, max_y)); + points.push_back(QPointF(s->opts.clamp_x_, s->opts.clamp_y_)); s->points = points; } -QPointF spline::ensure_in_bounds(const QList& points, double max_x, int i) +QPointF spline::ensure_in_bounds(const QList& points, int i) { - const int sz = element_count(points, max_x); + const int sz = points.size(); if (i < 0 || sz == 0) return QPointF(0, 0); @@ -176,20 +150,16 @@ QPointF spline::ensure_in_bounds(const QList& points, double max_x, int return points[sz - 1]; } -int spline::element_count(const QList& points, double max_x) +int spline::element_count(const QList& points, double max_input) { - if (!(max_x > 0)) - return points.size(); - else + const unsigned sz = points.size(); + for (unsigned k = 0; k < sz; k++) { - const unsigned sz = points.size(); - for (unsigned i = 0; i < sz; i++) - { - if (!(points[i].x() <= max_x)) - return i; - } - return points.size(); + const QPointF& pt = points[k]; + if (max_input > 1e-4 && pt.x() - 1e-2 > max_input) + return k; } + return sz; } bool spline::sort_fn(const QPointF& one, const QPointF& two) @@ -200,13 +170,13 @@ bool spline::sort_fn(const QPointF& one, const QPointF& two) void spline::update_interp_data() { points_t points = s->points; - ensure_valid(points); + const int sz = points.size(); - int sz = element_count(points, max_x); + const double maxx = max_input(); if (sz == 0) - points.prepend(QPointF(max_x, max_y)); + points.prepend(QPointF(maxx, max_output())); std::stable_sort(points.begin(), points.begin() + sz, sort_fn); @@ -218,7 +188,7 @@ void spline::update_interp_data() if (sz < 2) { - if (points[0].x() - 1e-2 <= max_x) + if (points[0].x() - 1e-2 < maxx) { const double x = points[0].x(); const double y = points[0].y(); @@ -232,15 +202,15 @@ void spline::update_interp_data() } else { - if (points[0].x() > 1e-2 && points[0].x() <= max_x) + if (points[0].x() > 1e-2 && points[0].x() <= maxx) points.push_front(QPointF(0, 0)); for (int i = 0; i < sz; i++) { - const QPointF p0 = ensure_in_bounds(points, max_x, i - 1); - const QPointF p1 = ensure_in_bounds(points, max_x, i + 0); - const QPointF p2 = ensure_in_bounds(points, max_x, i + 1); - const QPointF p3 = ensure_in_bounds(points, max_x, i + 2); + const QPointF p0 = ensure_in_bounds(points, i - 1); + const QPointF p1 = ensure_in_bounds(points, i + 0); + const QPointF p2 = ensure_in_bounds(points, i + 1); + const QPointF p3 = ensure_in_bounds(points, i + 2); const double p0_x = p0.x(), p1_x = p1.x(), p2_x = p2.x(), p3_x = p3.x(); const double p0_y = p0.y(), p1_y = p1.y(), p2_y = p2.y(), p3_y = p3.y(); @@ -293,7 +263,7 @@ void spline::remove_point(int i) QMutexLocker foo(&_mutex); points_t points = s->points; - const int sz = element_count(points, max_x); + const int sz = element_count(points, max_input()); if (i >= 0 && i < sz) { @@ -325,7 +295,7 @@ void spline::move_point(int idx, QPointF pt) points_t points = s->points; - const int sz = element_count(points, max_x); + const int sz = element_count(points, max_input()); if (idx >= 0 && idx < sz) { @@ -346,7 +316,7 @@ QList spline::get_points() const int spline::get_point_count() const { QMutexLocker foo(&_mutex); - return element_count(s->points, max_x); + return element_count(s->points, s->opts.clamp_x_); } void spline::reload() @@ -361,7 +331,18 @@ void spline::save() s->b->save(); } -void spline::set_bundle(bundle b) +void spline::invalidate_settings() +{ + // we're holding the mutex to allow signal disconnection in spline dtor + // before this slot gets called for the next time + + QMutexLocker l(&_mutex); + validp = false; + + emit s->recomputed(); +} + +void spline::set_bundle(bundle b, const QString& axis_name, Axis axis) { QMutexLocker l(&_mutex); @@ -369,34 +350,44 @@ void spline::set_bundle(bundle b) // the sentinel settings/bundle objects don't need any further branching once created if (!s || s->b != b) { - s = std::make_shared(b); + s = std::make_shared(b, axis_name, axis); if (connection) + { QObject::disconnect(connection); + QObject::disconnect(conn_maxx); + QObject::disconnect(conn_maxy); + } if (b) { connection = QObject::connect(b.get(), &bundle_::changed, - s.get(), [&]() - { - // we're holding the mutex to allow signal disconnection in spline dtor - // before this slot gets called for the next time - - // spline isn't a QObject and the connection context is incorrect - - QMutexLocker l(&_mutex); - validp = false; + s.get(), [&]() { invalidate_settings(); }); - emit s->recomputed(); - }, - Qt::QueuedConnection); + // this isn't strictly necessary for the spline but helps the widget + conn_maxx = QObject::connect(&s->opts.clamp_x_, base_value::signal_fun(), + ctx.get(), [&](double) { invalidate_settings(); }); + conn_maxy = QObject::connect(&s->opts.clamp_y_, base_value::signal_fun(), + ctx.get(), [&](double) { invalidate_settings(); }); } validp = false; } } -void spline::ensure_valid(const QList& the_points) +double spline::max_input() const +{ + QMutexLocker l(&_mutex); + return s ? s->opts.clamp_x_.to() : 0; +} + +double spline::max_output() const +{ + QMutexLocker l(&_mutex); + return s ? std::fabs(s->opts.clamp_y_.to()) : 0; +} + +void spline::ensure_valid(QList& the_points) { QMutexLocker foo(&_mutex); @@ -411,6 +402,8 @@ void spline::ensure_valid(const QList& the_points) QList ret_list; ret_list.reserve(sz); + const double maxx = max_input(), maxy = max_output(); + for (int i = 0; i < sz; i++) { QPointF& pt(list[i]); @@ -421,29 +414,22 @@ void spline::ensure_valid(const QList& the_points) const QPointF& pt2(list[j]); const QPointF tmp(pt - pt2); const double dist_sq = QPointF::dotProduct(tmp, tmp); - const double overlap = max_x / 500.; + const double overlap = maxx / 500.; if (dist_sq < overlap * overlap) return true; } return false; ); - const bool over_limit = progn( - bool ret = false; - if (pt.y() - 1e-2 > max_y) - { - pt.setY(max_y); - ret = true; - } - return ret; - ); - - if (!overlap && !over_limit) + if (!overlap) ret_list.push_back(pt); } if (ret_list != the_points) + { s->points = ret_list; + the_points = std::move(ret_list); + } last_input_value = QPointF(0, 0); activep = false; @@ -466,23 +452,26 @@ double spline::bucket_size_coefficient(const QList& points) const { static constexpr double eps = 1e-4; - if (unlikely(max_x < eps)) + const double maxx = max_input(); + + if (maxx < eps) return 0; // needed to fill the buckets up to the last control point. // space between that point and max_x doesn't matter. - const int sz = element_count(points, max_x); - const double last_x = sz ? points[sz - 1].x() : max_x; + const int sz = element_count(points, maxx); + const double last_x = sz ? points[sz - 1].x() : maxx; - return clamp((value_count-1) / clamp(last_x, eps, max_x), 0., (value_count-1)); + return clamp((value_count-1) / clamp(last_x, eps, maxx), 0., (value_count-1)); } namespace spline_detail { -settings::settings(bundle b): +settings::settings(bundle b, const QString& axis_name, Axis idx): b(b ? b : make_bundle("")), - points(b, "points", QList()) + points(b, "points", {}), + opts(axis_name, idx) {} settings::~settings() diff --git a/spline/spline.hpp b/spline/spline.hpp index cb2dc654..334e70d0 100644 --- a/spline/spline.hpp +++ b/spline/spline.hpp @@ -13,11 +13,14 @@ #include "compat/util.hpp" using namespace options; +#include "axis-opts.hpp" + #include "export.hpp" #include #include #include +#include #include #include @@ -29,10 +32,12 @@ namespace spline_detail { class OTR_SPLINE_EXPORT settings final : public QObject { Q_OBJECT + public: bundle b; value> points; - settings(bundle b); + axis_opts opts; + settings(bundle b, const QString& axis_name, Axis idx); ~settings() override; signals: void recomputed() const; @@ -49,11 +54,11 @@ class OTR_SPLINE_EXPORT spline final float get_value_no_save_internal(double x); static bool sort_fn(const QPointF& one, const QPointF& two); - static QPointF ensure_in_bounds(const QList& points, double max_x, int i); - static int element_count(const QList& points, double max_x); + static QPointF ensure_in_bounds(const QList& points, int i); + static int element_count(const QList& points, double max_input); std::shared_ptr s; - QMetaObject::Connection connection; + QMetaObject::Connection connection, conn_maxx, conn_maxy; std::vector data; using interp_data_t = decltype(data); @@ -62,21 +67,27 @@ class OTR_SPLINE_EXPORT spline final MyMutex _mutex; QPointF last_input_value; - qreal max_x, max_y; // XXX TODO move to value -sh 20171020 + std::shared_ptr ctx; + + Axis axis; + bool activep; bool validp; public: using settings = spline_detail::settings; + void invalidate_settings(); + void reload(); void save(); - void set_bundle(bundle b); + void set_bundle(bundle b, const QString& axis_name, Axis axis); + + double max_input() const; + double max_output() const; - qreal max_input() const; - qreal max_output() const; spline(); - spline(qreal maxx, qreal maxy, const QString& name); + spline(const QString& name, const QString& axis_name, Axis axis); ~spline(); spline& operator=(const spline&) = default; @@ -92,12 +103,10 @@ public: void add_point(double x, double y); void move_point(int idx, QPointF pt); QList get_points() const; - void set_max_input(qreal MaxInput); - void set_max_output(qreal MaxOutput); void set_tracking_active(bool value); bundle get_bundle(); - void ensure_valid(const QList& the_points); + void ensure_valid(QList& the_points); std::shared_ptr get_settings(); std::shared_ptr get_settings() const; -- cgit v1.2.3