/* Copyright (c) 2012-2016, Stanislaw Halik * 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 #include #include #include #include #include #include #include constexpr int spline::value_count; void spline::setTrackingActive(bool blnActive) { activep = blnActive; } spline::spline() : spline(0, 0, "") { } void spline::removeAllPoints() { QMutexLocker l(&_mutex); s->points = points_t(); update_interp_data(); } void spline::setMaxInput(qreal max_input) { QMutexLocker l(&_mutex); max_x = max_input; } void spline::setMaxOutput(qreal max_output) { QMutexLocker l(&_mutex); max_y = max_output; } qreal spline::maxInput() const { QMutexLocker l(&_mutex); return max_x; } qreal spline::maxOutput() const { QMutexLocker l(&_mutex); return max_y; } // todo try non-recursive mtx spline::spline(qreal maxx, qreal maxy, const QString& name) : data(value_count, -1.f), _mutex(QMutex::Recursive), max_x(maxx), max_y(maxy), activep(false) { set_bundle(options::make_bundle(name)); } float spline::getValue(double x) { QMutexLocker foo(&_mutex); float q = float(x * precision(s->points)); int xi = (int)q; float yi = getValueInternal(xi); float yiplus1 = getValueInternal(xi+1); float f = (q-xi); float ret = yiplus1 * f + yi * (1.0f - f); // at least do a linear interpolation. last_input_value.setX(std::fabs(x)); last_input_value.setY(double(std::fabs(ret))); return ret; } bool spline::getLastPoint(QPointF& point ) { QMutexLocker foo(&_mutex); point = last_input_value; return activep; } float spline::getValueInternal(int x) { float sign = x < 0 ? -1 : 1; x = 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& points, int i) { const int sz = points.size(); if (i < 0 || sz == 0) return QPointF(0, 0); if (i < sz) return points[i]; return points[sz - 1]; } 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; if (points.size() == 0) points.append(QPointF(max_x, max_y)); std::stable_sort(points.begin(), points.end(), sort_fn); const double mult = precision(points); const double mult_ = mult * 30; for (unsigned i = 0; i < value_count; i++) data[i] = -1; if (points.size() == 1) { const double x = points[0].x(); const double y = points[0].y(); const int max = clamp(int(x * precision(points)), 0, 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.push_front(QPointF(0, 0)); for (int i = 0; i < points.size(); i++) { const QPointF p0 = ensure_in_bounds(points, i - 1); const QPointF p1 = ensure_in_bounds(points, i); 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(); // 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 = int(.5 * ((2 * p1_x) + (-p0_x + p2_x) * t + (2 * p0_x - 5 * p1_x + 4 * p2_x - p3_x) * t2 + (-p0_x + 3 * p1_x - 3 * p2_x + p3_x) * t3) * mult); const float y = float(.5 * ((2 * p1_y) + (-p0_y + p2_y) * t + (2 * p0_y - 5 * p1_y + 4 * p2_y - p3_y) * t2 + (-p0_y + 3 * p1_y - 3 * p2_y + p3_y) * 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::removePoint(int i) { QMutexLocker foo(&_mutex); points_t points = s->points; if (i >= 0 && i < points.size()) { points.erase(points.begin() + i); s->points = points; update_interp_data(); } } void spline::addPoint(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; update_interp_data(); } void spline::movePoint(int idx, QPointF pt) { QMutexLocker foo(&_mutex); points_t points = s->points; if (idx >= 0 && idx < points.size()) { 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; update_interp_data(); } } QList spline::getPoints() const { QMutexLocker foo(&_mutex); return s->points; } void spline::reload() { if (s && s->b) s->b->reload(); } void spline::save() { if (s && s->b) s->b->save(); } void spline::set_bundle(bundle b) { if (b) s = std::unique_ptr(new settings(b)); else s = std::unique_ptr(new settings(options::make_bundle(""))); last_input_value = QPointF(0, 0); activep = false; update_interp_data(); } int spline::precision(const QList& points) const { if (points.size()) return int(value_count / clamp(points[points.size() - 1].x(), 1, max_x)); return value_count / clamp(int(max_x), 1, value_count); }