summaryrefslogtreecommitdiffhomepage
path: root/spline/spline.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'spline/spline.cpp')
-rw-r--r--spline/spline.cpp469
1 files changed, 469 insertions, 0 deletions
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()
+{
+}
+
+}