summaryrefslogtreecommitdiffhomepage
path: root/spline
diff options
context:
space:
mode:
Diffstat (limited to 'spline')
-rw-r--r--spline/CMakeLists.txt2
-rw-r--r--spline/broken/qfunctionconfiguratorplugin.cpp111
-rw-r--r--spline/broken/qfunctionconfiguratorplugin.h36
-rw-r--r--spline/export.hpp27
-rw-r--r--spline/lang/nl_NL.ts4
-rw-r--r--spline/lang/ru_RU.ts4
-rw-r--r--spline/lang/stub.ts4
-rw-r--r--spline/spline-widget.cpp652
-rw-r--r--spline/spline-widget.hpp106
-rw-r--r--spline/spline.cpp469
-rw-r--r--spline/spline.hpp108
11 files changed, 1523 insertions, 0 deletions
diff --git a/spline/CMakeLists.txt b/spline/CMakeLists.txt
new file mode 100644
index 00000000..d252004b
--- /dev/null
+++ b/spline/CMakeLists.txt
@@ -0,0 +1,2 @@
+otr_module(spline NO-COMPAT BIN)
+target_link_libraries(opentrack-spline opentrack-options opentrack-compat)
diff --git a/spline/broken/qfunctionconfiguratorplugin.cpp b/spline/broken/qfunctionconfiguratorplugin.cpp
new file mode 100644
index 00000000..1a9da10a
--- /dev/null
+++ b/spline/broken/qfunctionconfiguratorplugin.cpp
@@ -0,0 +1,111 @@
+/* Copyright (c) 2011-2012 Stanislaw Halik <sthalik@misaki.pl>
+ * Adapted to FaceTrackNoIR by Wim Vriend.
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+#include "qfunctionconfigurator.h"
+
+#include <QtCore/QtPlugin>
+#include "qfunctionconfiguratorplugin.h"
+
+
+QFunctionConfiguratorPlugin::QFunctionConfiguratorPlugin(QObject *parent)
+ : QObject(parent)
+{
+ initialized = false;
+}
+
+void QFunctionConfiguratorPlugin::initialize(QDesignerFormEditorInterface *)
+{
+ if (initialized)
+ return;
+
+ initialized = true;
+}
+
+bool QFunctionConfiguratorPlugin::isInitialized() const
+{
+ return initialized;
+}
+
+QWidget *QFunctionConfiguratorPlugin::createWidget(QWidget *parent)
+{
+ return new QFunctionConfigurator(parent);
+}
+
+QString QFunctionConfiguratorPlugin::name() const
+{
+ return "QFunctionConfigurator";
+}
+
+QString QFunctionConfiguratorPlugin::group() const
+{
+ return "My Plugins";
+}
+
+QIcon QFunctionConfiguratorPlugin::icon() const
+{
+ return QIcon();
+}
+
+QString QFunctionConfiguratorPlugin::toolTip() const
+{
+ return QString();
+}
+
+QString QFunctionConfiguratorPlugin::whatsThis() const
+{
+ return QString();
+}
+
+bool QFunctionConfiguratorPlugin::isContainer() const
+{
+ return false;
+}
+
+QString QFunctionConfiguratorPlugin::domXml() const
+{
+ return "<widget class=\"QFunctionConfigurator\" name=\"qFunctionA\">\n"
+ " <property name=\"geometry\">\n"
+ " <rect>\n"
+ " <x>0</x>\n"
+ " <y>0</y>\n"
+ " <width>161</width>\n"
+ " <height>220</height>\n"
+ " </rect>\n"
+ " </property>\n"
+ " <property name=\"colorBezier\">\n"
+ " <color>\n"
+ " <red>255</red>\n"
+ " <green>170</green>\n"
+ " <blue>0</blue>\n"
+ " </color>\n"
+ " </property>\n"
+ " <property name=\"stringInputEGU\" stdset=\"0\">\n"
+ " <string>Input Yaw (degr.)</string>\n"
+ " </property>\n"
+ " <property name=\"stringOutputEGU\" stdset=\"0\">\n"
+ " <string>Output Yaw (degr.)</string>\n"
+ " </property>\n"
+ " <property name=\"maxInputEGU\" stdset=\"0\">\n"
+ " <number>50</number>\n"
+ " </property>\n"
+ " <property name=\"maxOutputEGU\" stdset=\"0\">\n"
+ " <number>180</number>\n"
+ " </property>\n"
+ " <property name=\"pixPerEGU_Input\" stdset=\"0\">\n"
+ " <number>2</number>\n"
+ " </property>\n"
+ " <property name=\"pixPerEGU_Output\" stdset=\"0\">\n"
+ " <number>1</number>\n"
+ " </property>\n"
+ "</widget>\n";
+}
+
+QString QFunctionConfiguratorPlugin::includeFile() const
+{
+ return "qfunctionconfigurator.h";
+}
+
+Q_EXPORT_PLUGIN2(qfunctionconfigurator, QFunctionConfiguratorPlugin)
diff --git a/spline/broken/qfunctionconfiguratorplugin.h b/spline/broken/qfunctionconfiguratorplugin.h
new file mode 100644
index 00000000..ca68e0e2
--- /dev/null
+++ b/spline/broken/qfunctionconfiguratorplugin.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2011-2012 Stanislaw Halik <sthalik@misaki.pl>
+ * Adapted to FaceTrackNoIR by Wim Vriend.
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+#ifndef QFUNCTIONCONFIGURATORPLUGIN_H
+#define QFUNCTIONCONFIGURATORPLUGIN_H
+
+#include <QDesignerCustomWidgetInterface>
+
+class QFunctionConfiguratorPlugin : public QObject, public QDesignerCustomWidgetInterface
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface" FILE "analogclock.json")
+ Q_INTERFACES(QDesignerCustomWidgetInterface)
+
+public:
+ QFunctionConfiguratorPlugin(QObject *parent = 0);
+
+ bool isContainer() const;
+ bool isInitialized() const;
+ QIcon icon() const;
+ QString domXml() const;
+ QString group() const;
+ QString includeFile() const;
+ QString name() const;
+ QString toolTip() const;
+ QString whatsThis() const;
+ QWidget *createWidget(QWidget *parent);
+ void initialize(QDesignerFormEditorInterface *core);
+private:
+ bool initialized;
+};
+
+#endif // QFUNCTIONCONFIGURATORPLUGIN_H
diff --git a/spline/export.hpp b/spline/export.hpp
new file mode 100644
index 00000000..4875b4c1
--- /dev/null
+++ b/spline/export.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#ifdef BUILD_spline_widget
+# ifdef _WIN32
+# define OPENTRACK_SPLINE_LINKAGE __declspec(dllexport)
+# else
+# define OPENTRACK_SPLINE_LINKAGE
+# endif
+
+# ifndef _MSC_VER
+# define OPENTRACK_SPLINE_EXPORT __attribute__ ((visibility ("default"))) OPENTRACK_SPLINE_LINKAGE
+# else
+# define OPENTRACK_SPLINE_EXPORT OPENTRACK_SPLINE_LINKAGE
+# endif
+#else
+ #ifdef _WIN32
+ # define OPENTRACK_SPLINE_LINKAGE __declspec(dllimport)
+ #else
+ # define OPENTRACK_SPLINE_LINKAGE
+ #endif
+
+ #ifndef _MSC_VER
+ # define OPENTRACK_SPLINE_EXPORT __attribute__ ((visibility ("default"))) OPENTRACK_SPLINE_LINKAGE
+ #else
+ # define OPENTRACK_SPLINE_EXPORT OPENTRACK_SPLINE_LINKAGE
+ #endif
+#endif
diff --git a/spline/lang/nl_NL.ts b/spline/lang/nl_NL.ts
new file mode 100644
index 00000000..9e739505
--- /dev/null
+++ b/spline/lang/nl_NL.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl_NL">
+</TS>
diff --git a/spline/lang/ru_RU.ts b/spline/lang/ru_RU.ts
new file mode 100644
index 00000000..f62cf2e1
--- /dev/null
+++ b/spline/lang/ru_RU.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+</TS>
diff --git a/spline/lang/stub.ts b/spline/lang/stub.ts
new file mode 100644
index 00000000..6401616d
--- /dev/null
+++ b/spline/lang/stub.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+</TS>
diff --git a/spline/spline-widget.cpp b/spline/spline-widget.cpp
new file mode 100644
index 00000000..c7dd5e5a
--- /dev/null
+++ b/spline/spline-widget.cpp
@@ -0,0 +1,652 @@
+/* Copyright (c) 2012-2016 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+#include "compat/util.hpp"
+#include "spline-widget.hpp"
+#include <QPainter>
+#include <QPainterPath>
+#include <QPaintEvent>
+#include <QPen>
+#include <QPixmap>
+#include <QList>
+#include <QPoint>
+#include <QString>
+#include <QRect>
+#include <QApplication>
+#include <QStyle>
+#include <QMouseEvent>
+
+#include <QDebug>
+
+#include <cmath>
+#include <algorithm>
+
+spline_widget::spline_widget(QWidget *parent) :
+ QWidget(parent),
+ _config(nullptr),
+ snap_x(0),
+ snap_y(0),
+ _x_step(10),
+ _y_step(10),
+ moving_control_point_idx(-1),
+ _draw_function(true),
+ _preview_only(false)
+{
+ setMouseTracking(true);
+ setFocusPolicy(Qt::ClickFocus);
+ setCursor(Qt::ArrowCursor);
+}
+
+spline_widget::~spline_widget()
+{
+ if (connection)
+ QObject::disconnect(connection);
+}
+
+void spline_widget::setConfig(spline* spl)
+{
+ if (spl != _config)
+ {
+ if (connection)
+ {
+ QObject::disconnect(connection);
+ connection = QMetaObject::Connection();
+ }
+
+ if (spl)
+ {
+ mem<spline::settings> s = spl->get_settings();
+ connection = connect(s.get(), &spline::settings::recomputed,
+ this, [this]() { reload_spline(); },
+ Qt::QueuedConnection);
+ }
+
+ _config = spl;
+ _background = QPixmap();
+ update_range();
+ }
+}
+
+QColor spline_widget::colorBezier() const
+{
+ return spline_color;
+}
+
+void spline_widget::setColorBezier(QColor color)
+{
+ spline_color = color;
+ update();
+}
+
+void spline_widget::force_redraw()
+{
+ _background = QPixmap();
+ update();
+}
+
+void spline_widget::set_preview_only(bool val)
+{
+ _preview_only = val;
+}
+
+bool spline_widget::is_preview_only() const
+{
+ return _preview_only;
+}
+
+void spline_widget::drawBackground()
+{
+ _background = QPixmap(width(), height());
+
+ QPainter painter(&_background);
+
+ painter.fillRect(rect(), palette().color(QPalette::Light));
+
+ 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);
+ painter.setFont(font);
+ QFontMetrics metrics(font);
+
+ QColor color__(176, 190, 209, 127);
+
+ if (!isEnabled())
+ color__ = QColor(70, 90, 100, 96);
+
+ const QPen pen(color__, 1, Qt::SolidLine, Qt::FlatCap);
+
+ const int ystep = _y_step, xstep = _x_step;
+ const qreal maxx = _config->max_input();
+ const qreal maxy = _config->max_output();
+
+ // horizontal grid
+ for (int i = 0; i <= maxy; i += ystep)
+ {
+ const int y = int(pixel_bounds.height() - i * c.y() + pixel_bounds.y());
+ drawLine(painter,
+ QPoint(pixel_bounds.x(), y),
+ QPoint(pixel_bounds.x() + pixel_bounds.width(), y),
+ pen);
+ painter.drawText(QRectF(10,
+ y - metrics.height()/2.,
+ pixel_bounds.left(),
+ metrics.height()),
+ QString::number(i));
+ }
+
+ // vertical grid
+ for (int i = 0; i <= maxx; i += xstep)
+ {
+ const int x = iround(pixel_bounds.x() + i * c.x());
+ drawLine(painter,
+ QPoint(x, pixel_bounds.y()),
+ QPoint(x, pixel_bounds.y() + pixel_bounds.height()),
+ pen);
+ const QString text = QString::number(i);
+ painter.drawText(QRectF(x - metrics.width(text)/2.,
+ pixel_bounds.height() + 10 + metrics.height(),
+ metrics.width(text),
+ metrics.height()),
+ text);
+ }
+}
+
+void spline_widget::drawFunction()
+{
+ _function = _background;
+ QPainter painter(&_function);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+
+ const points_t points = _config->get_points();
+
+ if (moving_control_point_idx >= 0 &&
+ moving_control_point_idx < points.size())
+ {
+ const QPen pen(Qt::white, 1, Qt::SolidLine, Qt::FlatCap);
+ const QPointF prev_ = point_to_pixel(QPointF(0, 0));
+ QPoint prev(iround(prev_.x()), iround(prev_.y()));
+ for (int i = 0; i < points.size(); i++)
+ {
+ const QPointF tmp_ = point_to_pixel(points[i]);
+ const QPoint tmp(iround(tmp_.x()), iround(tmp_.y()));
+ drawLine(painter, prev, tmp, pen);
+ prev = tmp;
+ }
+ }
+
+ const QColor color = progn
+ (
+ if (!isEnabled() && !_preview_only)
+ {
+ QColor color(spline_color);
+ const int avg = int(float(color.red() + color.green() + color.blue())/3);
+ return QColor(int(float(color.red() + avg) * .5f),
+ int(float(color.green() + avg) * .5f),
+ int(float(color.blue() + avg) * .5f),
+ 96);
+ }
+ else
+ {
+ QColor color(spline_color);
+ color.setAlphaF(color.alphaF() * .9);
+ return color;
+ }
+ );
+
+ painter.setPen(QPen(color, 1.75, Qt::SolidLine, Qt::FlatCap));
+
+//#define DEBUG_SPLINE
+#ifndef DEBUG_SPLINE
+ static constexpr double step_ = 3./3;
+
+ const double maxx = _config->max_input();
+ const double step = step_ / c.x();
+
+ QPainterPath path;
+
+ path.moveTo(point_to_pixel(QPointF(0, 0)));
+
+ for (double k = 0; k < maxx; k += step*3)
+ {
+ const float next_1(_config->get_value_no_save(k + step*1));
+ const float next_2(_config->get_value_no_save(k + step*2));
+ const float next_3(_config->get_value_no_save(k + step*3));
+
+ const QPointF b(point_to_pixel_(QPointF(k + step*1, qreal(next_1)))),
+ c(point_to_pixel_(QPointF(k + step*2, qreal(next_2)))),
+ d(point_to_pixel_(QPointF(k + step*3, qreal(next_3))));
+
+ path.cubicTo(b, c, d);
+ }
+
+ painter.drawPath(path);
+#else
+ static constexpr int line_length_pixels = 3;
+ const qreal max = _config->maxInput();
+ const qreal step = clamp(line_length_pixels / c.x(), 5e-2, max);
+ QPointF prev = point_to_pixel(QPoint(0, 0));
+ for (qreal i = 0; i < max; i += step)
+ {
+ const qreal val = qreal(_config->get_value_no_save(i));
+ const QPointF cur = point_to_pixel(QPointF(i, val));
+ painter.drawLine(prev, cur);
+ prev = cur;
+ }
+ {
+ const qreal maxx = _config->maxInput();
+ const qreal maxy = qreal(_config->get_value_no_save(maxx));
+ painter.drawLine(QPointF(prev), point_to_pixel_(QPointF(maxx, maxy)));
+ }
+#endif
+
+ const int alpha = !isEnabled() ? 64 : 120;
+ if (!_preview_only)
+ {
+ for (int i = 0; i < points.size(); i++)
+ {
+ drawPoint(painter,
+ point_to_pixel(points[i]),
+ QColor(200, 200, 210, alpha),
+ isEnabled() ? QColor(50, 100, 120, 200) : QColor(200, 200, 200, 96));
+ }
+ }
+}
+
+void spline_widget::paintEvent(QPaintEvent *e)
+{
+ if (!_config)
+ return;
+
+ QPainter p(this);
+
+ if (_background.isNull())
+ {
+ _draw_function = true;
+ drawBackground();
+ }
+
+ if (_draw_function)
+ {
+ _draw_function = false;
+ drawFunction();
+ }
+
+ p.drawPixmap(e->rect(), _function);
+
+ // If the Tracker is active, the 'Last Point' it requested is recorded.
+ // Show that point on the graph, with some lines to assist.
+ // This new feature is very handy for tweaking the curves!
+ QPointF last;
+ if (_config->get_last_value(last) && isEnabled())
+ drawPoint(p, point_to_pixel(last), QColor(255, 0, 0, 120));
+}
+
+void spline_widget::drawPoint(QPainter& painter, const QPointF& pos, const QColor& colBG, const QColor& border)
+{
+ painter.save();
+ painter.setPen(QPen(border, 1, Qt::SolidLine, Qt::PenCapStyle::FlatCap));
+ painter.setBrush(colBG);
+ painter.drawEllipse(QRectF(pos.x() - point_size,
+ pos.y() - point_size,
+ point_size*2, point_size*2));
+ painter.restore();
+}
+
+void spline_widget::drawLine(QPainter& painter, const QPoint& start, const QPoint& end, const QPen& pen)
+{
+ painter.save();
+ painter.setPen(pen);
+ painter.setBrush(Qt::NoBrush);
+ painter.drawLine(start, end);
+ painter.restore();
+}
+
+void spline_widget::mousePressEvent(QMouseEvent *e)
+{
+ if (!_config || !isEnabled() || !is_in_bounds(e->pos()))
+ {
+ clearFocus();
+ return;
+ }
+
+ const int point_pixel_closeness_limit = get_closeness_limit();
+
+ points_t points = _config->get_points();
+ if (e->button() == Qt::LeftButton)
+ {
+ bool bTouchingPoint = false;
+ moving_control_point_idx = -1;
+ for (int i = 0; i < points.size(); i++)
+ {
+ if (point_within_pixel(points[i], e->pos()))
+ {
+ bTouchingPoint = true;
+ moving_control_point_idx = int(i);
+ break;
+ }
+ }
+ if (!bTouchingPoint)
+ {
+ bool too_close = false;
+ const QPoint pos = e->pos();
+
+ for (int i = 0; i < points.size(); i++)
+ {
+ const QPointF pt = point_to_pixel(points[i]);
+ const qreal x = std::fabs(pt.x() - pos.x());
+ if (point_pixel_closeness_limit >= x)
+ {
+ too_close = true;
+ break;
+ }
+ }
+
+ if (!too_close)
+ {
+ _config->add_point(pixel_coord_to_point(e->pos()));
+ show_tooltip(e->pos());
+ }
+ }
+ }
+
+ if (e->button() == Qt::RightButton)
+ {
+ if (_config)
+ {
+ int found_pt = -1;
+ for (int i = 0; i < points.size(); i++)
+ {
+ if (point_within_pixel(points[i], e->pos()))
+ {
+ found_pt = i;
+ break;
+ }
+ }
+
+ if (found_pt != -1)
+ {
+ _config->remove_point(found_pt);
+ }
+ moving_control_point_idx = -1;
+ }
+ }
+ _draw_function = true;
+ update();
+}
+
+void spline_widget::mouseMoveEvent(QMouseEvent *e)
+{
+ if (_preview_only && _config)
+ {
+ show_tooltip(e->pos());
+ clearFocus();
+ return;
+ }
+
+ if (!_config || !isEnabled() || !isActiveWindow() || (moving_control_point_idx != -1 && !hasFocus()))
+ {
+ clearFocus();
+ return;
+ }
+
+ const int i = moving_control_point_idx;
+ const points_t points = _config->get_points();
+ const int sz = points.size();
+
+ if (i >= 0 && i < sz)
+ {
+ const int point_pixel_closeness_limit = get_closeness_limit();
+ bool overlap = false;
+
+ const QPoint pix = progn(
+ // takes snap into account
+ const QPointF pix_ = point_to_pixel(pixel_coord_to_point(e->pos()));
+ return QPoint(int(pix_.x()), int(pix_.y()));
+ );
+
+ QPointF new_pt = pixel_coord_to_point(e->pos());
+
+ if (i + 1 < points.size())
+ {
+ const QPointF other_pix = point_to_pixel(points[i+1]);
+ overlap |= other_pix.x() - pix.x() < point_pixel_closeness_limit;
+ }
+ if (i != 0)
+ {
+ const QPointF other_pix = point_to_pixel(points[i-1]);
+ overlap |= pix.x() - other_pix.x() < point_pixel_closeness_limit;
+ }
+
+ if (overlap)
+ new_pt.setX(points[i].x());
+
+ _config->move_point(i, new_pt);
+ _draw_function = true;
+
+ setCursor(Qt::ClosedHandCursor);
+ show_tooltip(pix, new_pt);
+ update();
+ }
+ else if (sz)
+ {
+ int i;
+ bool is_on_point = is_on_pt(e->pos(), &i);
+
+ if (is_on_point)
+ {
+ setCursor(Qt::CrossCursor);
+ show_tooltip(e->pos(), points[i]);
+ }
+ else
+ {
+ setCursor(Qt::ArrowCursor);
+ if (is_in_bounds(e->pos()))
+ show_tooltip(e->pos());
+ else
+ QToolTip::hideText();
+ }
+ }
+}
+
+void spline_widget::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (!_config || !isEnabled() || !isActiveWindow() || !hasFocus())
+ {
+ clearFocus();
+ return;
+ }
+
+ if (e->button() == Qt::LeftButton)
+ {
+ {
+ if (is_on_pt(e->pos(), nullptr))
+ setCursor(Qt::CrossCursor);
+ else
+ setCursor(Qt::ArrowCursor);
+ }
+ moving_control_point_idx = -1;
+ _draw_function = true;
+
+ if (is_in_bounds(e->pos()))
+ show_tooltip(e->pos());
+ else
+ QToolTip::hideText();
+
+ update();
+ }
+}
+
+void spline_widget::reload_spline()
+{
+ // don't recompute here as the value's about to be recomputed in the callee
+ update_range();
+}
+
+int spline_widget::get_closeness_limit()
+{
+ return std::max(iround(snap_x * c.x()), 1);
+}
+
+void spline_widget::show_tooltip(const QPoint& pos, const QPointF& value_)
+{
+ const QPointF value = QPoint(0, 0) == value_ ? pixel_coord_to_point(pos) : value_;
+
+ double x = value.x(), y = value.y();
+
+ if (_preview_only)
+ y = _config->get_value_no_save(x);
+
+ const int x_ = iround(x), y_ = iround(y);
+
+ using std::fabs;
+
+ if (fabs(x_ - x) < 1e-3)
+ x = x_;
+ if (fabs(y_ - y) < 1e-3)
+ y = y_;
+
+ const bool is_fusion = QStringLiteral("fusion") == QApplication::style()->objectName();
+ const int add_x = (is_fusion ? 25 : 0), add_y = (is_fusion ? 15 : 0);
+
+ const QPoint pix(int(pos.x()) + add_x, int(pos.y()) + add_y);
+
+ QToolTip::showText(mapToGlobal(pix),
+ QStringLiteral("value: %1x%2").arg(x).arg(y),
+ this,
+ rect(),
+ 0);
+}
+
+bool spline_widget::is_in_bounds(const QPoint& pos) const
+{
+ static constexpr int grace = point_size * 3;
+ static constexpr int bottom_grace = int(point_size * 1.5);
+ return (pos.x() + grace > pixel_bounds.left() &&
+ pos.x() - grace < pixel_bounds.right() &&
+ pos.y() + grace > pixel_bounds.top() &&
+ pos.y() - bottom_grace < pixel_bounds.bottom());
+}
+
+void spline_widget::update_range()
+{
+ if (!_config)
+ return;
+
+ const int w = width(), h = height();
+ const int mwl = 40, mhl = 20;
+ const int mwr = 15, mhr = 35;
+
+ pixel_bounds = QRect(mwl, mhl, (w - mwl - mwr), (h - mhl - mhr));
+ c = QPointF(pixel_bounds.width() / _config->max_input(), pixel_bounds.height() / _config->max_output());
+ _draw_function = true;
+
+ _background = QPixmap();
+ _function = QPixmap();
+
+ update();
+}
+
+bool spline_widget::point_within_pixel(const QPointF& pt, const QPoint &pixel)
+{
+ const QPointF tmp = pixel - point_to_pixel(pt);
+ return QPointF::dotProduct(tmp, tmp) < point_size * point_size;
+}
+
+void spline_widget::focusOutEvent(QFocusEvent* e)
+{
+ if (moving_control_point_idx != -1)
+ QToolTip::hideText();
+ moving_control_point_idx = -1;
+ _draw_function = true;
+ lower();
+ setCursor(Qt::ArrowCursor);
+ e->accept();
+}
+
+QPointF spline_widget::pixel_coord_to_point(const QPoint& point)
+{
+ qreal x = (point.x() - pixel_bounds.x()) / c.x();
+ qreal y = (pixel_bounds.height() - point.y() + pixel_bounds.y()) / c.y();
+
+ static constexpr int c = 1000;
+
+ if (snap_x > 0)
+ {
+ x += snap_x * .5;
+ x -= std::fmod(x, snap_x);
+ // truncate after few decimal places to reduce rounding errors.
+ // round upward to nearest.
+ x = int(x * c + .5/c) / double(c);
+ }
+ if (snap_y > 0)
+ {
+ y += snap_y * .5;
+ y -= std::fmod(y, snap_y);
+ // idem
+ y = int(y * c + .5/c) / double(c);
+ }
+
+ if (x < 0)
+ x = 0;
+ if (x > _config->max_input())
+ x = _config->max_input();
+
+ if (y < 0)
+ y = 0;
+ if (y > _config->max_output())
+ y = _config->max_output();
+
+ return QPointF(x, y);
+}
+
+QPointF spline_widget::point_to_pixel_(const QPointF& point)
+{
+ return QPointF(pixel_bounds.x() + point.x() * c.x(),
+ pixel_bounds.y() + pixel_bounds.height() - point.y() * c.y());
+}
+
+QPoint spline_widget::point_to_pixel(const QPointF& point)
+{
+ QPointF pt(point_to_pixel_(point));
+
+ return QPoint(iround(pt.x()), iround(pt.y()));
+}
+
+void spline_widget::resizeEvent(QResizeEvent *)
+{
+ update_range();
+}
+
+bool spline_widget::is_on_pt(const QPoint& pos, int* pt)
+{
+ if (!_config)
+ {
+ if (pt)
+ *pt = -1;
+ return false;
+ }
+
+ const points_t points = _config->get_points();
+
+ for (int i = 0; i < points.size(); i++)
+ {
+ if (point_within_pixel(points[i], pos))
+ {
+ if (pt)
+ *pt = i;
+ return true;
+ }
+ }
+
+ if (pt)
+ *pt = -1;
+ return false;
+}
diff --git a/spline/spline-widget.hpp b/spline/spline-widget.hpp
new file mode 100644
index 00000000..4d5a7680
--- /dev/null
+++ b/spline/spline-widget.hpp
@@ -0,0 +1,106 @@
+/* Copyright (c) 2012-2016 Stanislaw Halik <sthalik@misaki.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ */
+
+// Adapted to FaceTrackNoIR by Wim Vriend.
+
+#pragma once
+
+#include "spline.hpp"
+#include "api/plugin-api.hpp"
+#include "options/options.hpp"
+using namespace options;
+
+#include "export.hpp"
+
+#include <QWidget>
+#include <QRect>
+#include <QPoint>
+#include <QPointF>
+#include <QToolTip>
+#include <QShowEvent>
+#include <QFocusEvent>
+#include <QMetaObject>
+
+#include <QDebug>
+
+class OPENTRACK_SPLINE_EXPORT spline_widget final : public QWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(QColor colorBezier READ colorBezier WRITE setColorBezier)
+ Q_PROPERTY(bool is_preview_only READ is_preview_only WRITE set_preview_only)
+ Q_PROPERTY(int x_step READ x_step WRITE set_x_step)
+ Q_PROPERTY(int y_step READ y_step WRITE set_y_step)
+
+ using points_t = spline::points_t;
+public:
+ spline_widget(QWidget *parent = 0);
+ ~spline_widget();
+
+ spline* config();
+ void setConfig(spline* spl);
+
+ QColor colorBezier() const;
+ void setColorBezier(QColor color);
+
+ void force_redraw();
+ void set_preview_only(bool val);
+ bool is_preview_only() const;
+
+ double x_step() { return _x_step; }
+ double y_step() { return _y_step; }
+ void set_x_step(double val) { _x_step = val; }
+ void set_y_step(double val) { _y_step = val; }
+
+ void set_snap(double x, double y) { snap_x = x; snap_y = y; }
+ void get_snap(double& x, double& y) const { x = snap_x; y = snap_y; }
+public slots:
+ void reload_spline();
+protected slots:
+ void paintEvent(QPaintEvent *e) override;
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseMoveEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
+private:
+ int get_closeness_limit();
+ void show_tooltip(const QPoint& pos, const QPointF& value = QPointF(0, 0));
+ bool is_in_bounds(const QPoint& pos) const;
+
+ void drawBackground();
+ void drawFunction();
+ void drawPoint(QPainter& painter, const QPointF& pt, const QColor& colBG, const QColor& border = QColor(50, 100, 120, 200));
+ void drawLine(QPainter& painter, const QPoint& start, const QPoint& end, const QPen& pen);
+ bool point_within_pixel(const QPointF& pt, const QPoint& pixel);
+
+ void focusOutEvent(QFocusEvent*e) override;
+ void resizeEvent(QResizeEvent *) override;
+
+ bool is_on_pt(const QPoint& pos, int* pt = nullptr);
+ void update_range();
+ QPointF pixel_coord_to_point(const QPoint& point);
+
+ QPointF point_to_pixel_(const QPointF& point);
+ QPoint point_to_pixel(const QPointF& point);
+
+ QPointF c;
+ spline* _config;
+
+ QPixmap _background;
+ QPixmap _function;
+ QColor spline_color;
+
+ // bounds of the rectangle user can interact with
+ QRect pixel_bounds;
+
+ QMetaObject::Connection connection;
+
+ double snap_x, snap_y;
+ double _x_step, _y_step;
+ int moving_control_point_idx;
+ bool _draw_function, _preview_only;
+
+ static constexpr int point_size = 4;
+};
diff --git a/spline/spline.cpp b/spline/spline.cpp
new file mode 100644
index 00000000..2db23c14
--- /dev/null
+++ b/spline/spline.cpp
@@ -0,0 +1,469 @@
+/* Copyright (c) 2012-2016, Stanislaw Halik <sthalik@misaki.pl>
+
+ * Permission to use, copy, modify, and/or distribute this
+ * software for any purpose with or without fee is hereby granted,
+ * provided that the above copyright notice and this permission
+ * notice appear in all copies.
+ */
+
+#include "spline.hpp"
+
+#include <algorithm>
+#include <cmath>
+#include <memory>
+
+#include <QObject>
+#include <QMutexLocker>
+#include <QCoreApplication>
+#include <QPointF>
+#include <QSettings>
+#include <QString>
+
+#include <QDebug>
+
+constexpr int spline::value_count;
+
+spline::spline(qreal maxx, qreal maxy, const QString& name) :
+ s(nullptr),
+ data(value_count, -1.f),
+ _mutex(QMutex::Recursive),
+ max_x(maxx),
+ max_y(maxy),
+ activep(false),
+ validp(false)
+{
+ set_bundle(options::make_bundle(name));
+}
+
+spline::~spline()
+{
+ QMutexLocker l(&_mutex);
+
+ if (connection)
+ {
+ QObject::disconnect(connection);
+ connection = QMetaObject::Connection();
+ }
+}
+
+spline::spline() : spline(0, 0, "") {}
+
+void spline::set_tracking_active(bool value)
+{
+ activep = value;
+}
+
+bundle spline::get_bundle()
+{
+ return s->b;
+}
+
+void spline::clear()
+{
+ QMutexLocker l(&_mutex);
+ s->points = points_t();
+ validp = false;
+}
+
+void spline::set_max_input(qreal max_input)
+{
+ QMutexLocker l(&_mutex);
+ max_x = max_input;
+ recompute();
+}
+
+void spline::set_max_output(qreal max_output)
+{
+ QMutexLocker l(&_mutex);
+ max_y = max_output;
+ recompute();
+}
+
+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);
+
+ const float ret = get_value_no_save(x);
+ last_input_value.setX(std::fabs(x));
+ last_input_value.setY(double(std::fabs(ret)));
+ return ret;
+}
+
+float spline::get_value_no_save(double x) const
+{
+ return const_cast<spline&>(*this).get_value_no_save_internal(x);
+}
+
+float spline::get_value_no_save_internal(double x)
+{
+ QMutexLocker foo(&_mutex);
+
+ if (max_x > 0)
+ x = std::min(max_x, x);
+
+ float q = float(x * precision(s->points));
+ int xi = (int)q;
+ float yi = get_value_internal(xi);
+ float yiplus1 = get_value_internal(xi+1);
+ float f = (q-xi);
+ float ret = yiplus1 * f + yi * (1.0f - f); // at least do a linear interpolation.
+ return ret;
+}
+
+DEFUN_WARN_UNUSED bool spline::get_last_value(QPointF& point)
+{
+ QMutexLocker foo(&_mutex);
+ point = last_input_value;
+ return activep;
+}
+
+float spline::get_value_internal(int x)
+{
+ if (!validp)
+ {
+ update_interp_data();
+ validp = true;
+ }
+
+ float sign = x < 0 ? -1 : 1;
+ x = std::abs(x);
+ float ret;
+ ret = data[std::min(unsigned(x), unsigned(value_count)-1u)];
+ return ret * sign;
+}
+
+void spline::add_lone_point()
+{
+ points_t points;
+ points.push_back(QPointF(max_x, max_y));
+
+ s->points = points;
+}
+
+QPointF spline::ensure_in_bounds(const QList<QPointF>& points, double max_x, int i)
+{
+ const int sz = element_count(points, max_x);
+
+ if (i < 0 || sz == 0)
+ return QPointF(0, 0);
+
+ if (i < sz)
+ return points[i];
+
+ return points[sz - 1];
+}
+
+int spline::element_count(const QList<QPointF>& points, double max_x)
+{
+ if (!(max_x > 0))
+ return points.size();
+ else
+ {
+ const unsigned sz = points.size();
+ for (unsigned i = 0; i < sz; i++)
+ {
+ if (!(points[i].x() <= max_x))
+ return i;
+ }
+ return points.size();
+ }
+}
+
+bool spline::sort_fn(const QPointF& one, const QPointF& two)
+{
+ return one.x() <= two.x();
+}
+
+void spline::update_interp_data()
+{
+ points_t points = s->points;
+
+ int sz = element_count(points, max_x);
+
+ if (sz == 0)
+ points.prepend(QPointF(max_x, max_y));
+
+ std::stable_sort(points.begin(), points.begin() + sz, sort_fn);
+
+ const double mult = precision(points);
+ const double mult_ = mult * 30;
+
+ for (unsigned i = 0; i < value_count; i++)
+ data[i] = -1;
+
+ if (sz < 2)
+ {
+ if (points[0].x() - 1e-2 <= max_x)
+ {
+ const double x = points[0].x();
+ const double y = points[0].y();
+ const int max = clamp(int(x * precision(points)), 1, value_count-1);
+ for (int k = 0; k <= max; k++)
+ {
+ if (k < value_count)
+ data[unsigned(k)] = float(y * k / max);
+ }
+ }
+ }
+ else
+ {
+ if (points[0].x() > 1e-2 && points[0].x() <= max_x)
+ 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 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();
+
+ const double cx[4] = {
+ 2 * p1_x, // 1
+ -p0_x + p2_x, // t
+ 2 * p0_x - 5 * p1_x + 4 * p2_x - p3_x, // t^2
+ -p0_x + 3 * p1_x - 3 * p2_x + p3_x, // t3
+ };
+
+ const double cy[4] =
+ {
+ 2 * p1_y, // 1
+ -p0_y + p2_y, // t
+ 2 * p0_y - 5 * p1_y + 4 * p2_y - p3_y, // t^2
+ -p0_y + 3 * p1_y - 3 * p2_y + p3_y, // t3
+ };
+
+ // multiplier helps fill in all the x's needed
+ const unsigned end = std::min(unsigned(value_count), unsigned(p2_x * mult_));
+ const unsigned start = std::max(0u, unsigned(p1_x * mult));
+
+ for (unsigned j = start; j < end; j++)
+ {
+ const double t = (j - start) / (double) (end - start);
+ const double t2 = t*t;
+ const double t3 = t*t*t;
+
+ const int x = iround(.5 * mult * (cx[0] + cx[1] * t + cx[2] * t2 + cx[3] * t3));
+ const float y = float(.5 * (cy[0] + cy[1] * t + cy[2] * t2 + cy[3] * t3));
+
+ if (x >= 0 && x < value_count)
+ data[unsigned(x)] = y;
+ }
+ }
+ }
+
+ float last = 0;
+ for (unsigned i = 0; i < unsigned(value_count); i++)
+ {
+ if (data[i] < 0)
+ data[i] = last;
+ last = data[i];
+ }
+}
+
+void spline::remove_point(int i)
+{
+ QMutexLocker foo(&_mutex);
+
+ points_t points = s->points;
+ const int sz = element_count(points, max_x);
+
+ if (i >= 0 && i < sz)
+ {
+ points.erase(points.begin() + i);
+ s->points = points;
+ validp = false;
+ }
+}
+
+void spline::add_point(QPointF pt)
+{
+ QMutexLocker foo(&_mutex);
+
+ points_t points = s->points;
+ points.push_back(pt);
+ std::stable_sort(points.begin(), points.end(), sort_fn);
+ s->points = points;
+ validp = false;
+}
+
+void spline::add_point(double x, double y)
+{
+ add_point(QPointF(x, y));
+}
+
+void spline::move_point(int idx, QPointF pt)
+{
+ QMutexLocker foo(&_mutex);
+
+ points_t points = s->points;
+
+ const int sz = element_count(points, max_x);
+
+ if (idx >= 0 && idx < sz)
+ {
+ points[idx] = pt;
+ // we don't allow points to be reordered, but sort due to possible caller logic error
+ std::stable_sort(points.begin(), points.end(), sort_fn);
+ s->points = points;
+ validp = false;
+ }
+}
+
+QList<QPointF> spline::get_points() const
+{
+ QMutexLocker foo(&_mutex);
+ return s->points;
+}
+
+int spline::get_point_count() const
+{
+ QMutexLocker foo(&_mutex);
+ return element_count(s->points, max_x);;
+}
+
+void spline::reload()
+{
+ QMutexLocker foo(&_mutex);
+ s->b->reload();
+}
+
+void spline::save(QSettings& settings)
+{
+ QMutexLocker foo(&_mutex);
+ s->b->save_deferred(settings);
+}
+
+void spline::save()
+{
+ save(*group::ini_file());
+}
+
+void spline::set_bundle(bundle b)
+{
+ QMutexLocker l(&_mutex);
+
+ // gets called from ctor hence the need for nullptr checks
+ // the sentinel settings/bundle objects don't need any further branching once created
+ if (!s || s->b != b)
+ {
+ s = std::make_shared<settings>(b);
+
+ if (connection)
+ QObject::disconnect(connection);
+
+ 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);
+ recompute();
+ emit s->recomputed();
+ },
+ Qt::QueuedConnection);
+ }
+
+ recompute();
+
+ emit s->recomputed();
+ }
+}
+
+void spline::recompute()
+{
+ QMutexLocker foo(&_mutex);
+
+ QList<QPointF> list = s->points;
+
+ // storing to s->points fires bundle::changed and that leads to an infinite loop
+ // only store if we can't help it
+ std::stable_sort(list.begin(), list.end(), sort_fn);
+
+ if (list != s->points)
+ s->points = list;
+
+ const int sz = list.size();
+
+ QList<QPointF> ret_list;
+ ret_list.reserve(sz);
+
+ for (int i = 0; i < sz; i++)
+ {
+ QPointF& pt(list[i]);
+
+ const bool overlap = progn(
+ for (int j = 0; j < i; j++)
+ {
+ QPointF& pt2(list[j]);
+ const double dist_sq = (pt.x() - pt2.x())*(pt.x() - pt2.x());
+ static constexpr double overlap = .6;
+ if (dist_sq < overlap * overlap)
+ return true;
+ }
+ return false;
+ );
+ if (!overlap)
+ ret_list.push_back(pt);
+ }
+
+ if (ret_list != s->points)
+ s->points = ret_list;
+
+ last_input_value = QPointF(0, 0);
+ activep = false;
+ validp = false;
+}
+
+// the return value is only safe to use with no spline::set_bundle calls
+mem<spline::settings> spline::get_settings()
+{
+ QMutexLocker foo(&_mutex);
+ return s;
+}
+
+mem<const spline::settings> spline::get_settings() const
+{
+ QMutexLocker foo(&_mutex);
+ return s;
+}
+
+double spline::precision(const QList<QPointF>& points) const
+{
+ // this adjusts the memoized range to the largest X value. empty space doesn't take value_count discrete points.
+ const int sz = element_count(points, max_x);
+ if (sz)
+ return clamp(value_count / clamp(points[sz - 1].x(), 1., max_x), 0., double(value_count));
+
+ return value_count / clamp(max_x, 1., double(value_count));
+}
+
+namespace spline_detail {
+
+settings::settings(bundle b):
+ b(b ? b : make_bundle("")),
+ points(b, "points", QList<QPointF>())
+{}
+
+settings::~settings()
+{
+}
+
+}
diff --git a/spline/spline.hpp b/spline/spline.hpp
new file mode 100644
index 00000000..77b2bdf9
--- /dev/null
+++ b/spline/spline.hpp
@@ -0,0 +1,108 @@
+/* Copyright (c) 2012-2016, Stanislaw Halik <sthalik@misaki.pl>
+
+ * Permission to use, copy, modify, and/or distribute this
+ * software for any purpose with or without fee is hereby granted,
+ * provided that the above copyright notice and this permission
+ * notice appear in all copies.
+ */
+
+#pragma once
+
+#include "compat/qcopyable-mutex.hpp"
+#include "options/options.hpp"
+#include "compat/util.hpp"
+using namespace options;
+
+#include "export.hpp"
+
+#include <vector>
+#include <limits>
+#include <memory>
+
+#include <QObject>
+#include <QPointF>
+#include <QString>
+#include <QMetaObject>
+
+namespace spline_detail {
+
+class OPENTRACK_SPLINE_EXPORT settings final : public QObject
+{
+ Q_OBJECT
+public:
+ bundle b;
+ value<QList<QPointF>> points;
+ settings(bundle b);
+ ~settings() override;
+signals:
+ void recomputed() const;
+};
+
+}
+
+class OPENTRACK_SPLINE_EXPORT spline final
+{
+ double precision(const QList<QPointF>& points) const;
+ void update_interp_data();
+ float get_value_internal(int x);
+ void add_lone_point();
+ 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<QPointF>& points, double max_x, int i);
+ static int element_count(const QList<QPointF>& points, double max_x);
+
+ mem<spline_detail::settings> s;
+ QMetaObject::Connection connection;
+
+ std::vector<float> data;
+ using interp_data_t = decltype(data);
+
+ static constexpr int value_count = 4096;
+
+ MyMutex _mutex;
+ QPointF last_input_value;
+ qreal max_x, max_y;
+ volatile bool activep;
+ bool validp;
+
+public:
+ using settings = spline_detail::settings;
+
+ void reload();
+ void save(QSettings& s);
+ void save();
+ void set_bundle(bundle b);
+
+ qreal max_input() const;
+ qreal max_output() const;
+ spline();
+ spline(qreal maxx, qreal maxy, const QString& name);
+ ~spline();
+
+ spline& operator=(const spline&) = default;
+ spline(const spline&) = default;
+
+ float get_value(double x);
+ float get_value_no_save(double x) const;
+ DEFUN_WARN_UNUSED bool get_last_value(QPointF& point);
+ void remove_point(int i);
+ void clear();
+
+ void add_point(QPointF pt);
+ void add_point(double x, double y);
+ void move_point(int idx, QPointF pt);
+ QList<QPointF> 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 recompute();
+
+ mem<settings> get_settings();
+ mem<const settings> get_settings() const;
+
+ using points_t = decltype(s->points());
+ int get_point_count() const;
+};