diff options
-rw-r--r-- | src/animation/backend/backend.pri | 11 | ||||
-rw-r--r-- | src/animation/backend/bezierevaluator.cpp | 238 | ||||
-rw-r--r-- | src/animation/backend/bezierevaluator_p.h | 92 | ||||
-rw-r--r-- | src/animation/backend/fcurve.cpp | 83 | ||||
-rw-r--r-- | src/animation/backend/fcurve_p.h | 91 | ||||
-rw-r--r-- | src/animation/backend/functionrangefinder.cpp | 173 | ||||
-rw-r--r-- | src/animation/backend/functionrangefinder_p.h | 97 | ||||
-rw-r--r-- | src/animation/backend/keyframe_p.h | 86 | ||||
-rw-r--r-- | tests/auto/animation/animation.pro | 5 | ||||
-rw-r--r-- | tests/auto/animation/bezierevaluator/bezierevaluator.pro | 10 | ||||
-rw-r--r-- | tests/auto/animation/bezierevaluator/tst_bezierevaluator.cpp | 280 | ||||
-rw-r--r-- | tests/auto/animation/fcurve/fcurve.pro | 10 | ||||
-rw-r--r-- | tests/auto/animation/fcurve/tst_fcurve.cpp | 100 | ||||
-rw-r--r-- | tests/auto/animation/functionrangefinder/functionrangefinder.pro | 11 | ||||
-rw-r--r-- | tests/auto/animation/functionrangefinder/tst_functionrangefinder.cpp | 173 |
15 files changed, 1457 insertions, 3 deletions
diff --git a/src/animation/backend/backend.pri b/src/animation/backend/backend.pri index 41857c253..4a38a0d45 100644 --- a/src/animation/backend/backend.pri +++ b/src/animation/backend/backend.pri @@ -6,8 +6,15 @@ HEADERS += \ $$PWD/handle_types_p.h \ $$PWD/handler_p.h \ $$PWD/nodefunctor_p.h \ - $$PWD/managers_p.h + $$PWD/managers_p.h \ + $$PWD/keyframe_p.h \ + $$PWD/fcurve_p.h \ + $$PWD/bezierevaluator_p.h \ + $$PWD/functionrangefinder_p.h SOURCES += \ $$PWD/animationclip.cpp \ - $$PWD/handler.cpp + $$PWD/handler.cpp \ + $$PWD/fcurve.cpp \ + $$PWD/bezierevaluator.cpp \ + $$PWD/functionrangefinder.cpp diff --git a/src/animation/backend/bezierevaluator.cpp b/src/animation/backend/bezierevaluator.cpp new file mode 100644 index 000000000..6ed8b1aa2 --- /dev/null +++ b/src/animation/backend/bezierevaluator.cpp @@ -0,0 +1,238 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "bezierevaluator_p.h" +#include <private/keyframe_p.h> +#include <QtCore/qglobal.h> +#include <QtCore/qdebug.h> + +#include <cmath> + +QT_BEGIN_NAMESPACE + +namespace { + +inline double qCbrt(double x) +{ + // Android is just broken and doesn't define cbrt in std namespace +#if defined(Q_OS_ANDROID) + if (x > 0.0) + return std::pow(x, 1.0 / 3.0); + else if (x < 0.0) + return -std::pow(-x, 1.0 / 3.0); + else + return 0.0; +#else + return std::cbrt(x); +#endif +} + +} // anonymous + +namespace Qt3DAnimation { +namespace Animation { + +/*! + \internal + + Evaluates the value of the cubic bezier at time \a time. + This requires first finding the value of the bezier parameter, u, + corresponding to the requested time which should itself be + sandwiched by the provided times and keyframes. + + Once u is found, substitute this back into the cubic Bezier + equation using the y components of the keyframe control points. + */ +float BezierEvaluator::valueForTime(float time) const +{ + const float u = parameterForTime(time); + + // Calulate powers of u and (1-u) that we need + const float u2 = u * u; + const float u3 = u2 * u; + const float mu = 1.0f - u; + const float mu2 = mu * mu; + const float mu3 = mu2 * mu; + + // The cubic Bezier control points + const float p0 = m_keyframe0.value; + const float p1 = m_keyframe0.rightControlPoint.y(); + const float p2 = m_keyframe1.leftControlPoint.y(); + const float p3 = m_keyframe1.value; + + // Evaluate the cubic Bezier function + return p0 * mu3 + 3.0f * p1 * mu2 * u + 3.0f * p2 * mu * u2 + p3 * u3; +} + +/*! + Calculates the value of the Bezier parameter, u, for the + requested time which is the x coordinate of the Keyframes. + + Given 4 ordered control points p0, p1, p2, and p3, the cubic + Bezier equation is: + + x(u) = (1-u)^3 p0 + 3 (1-u)^2 u p1 + 3 (1-u) u^2 p2 + u^3 p3 + + To find the value of u that corresponds with a given x + value (time in the case of keyframes), we can expand the + above equation, and then collect terms to arrive at: + + 0 = a u^3 + b u^2 + c u + d + + where + + a = p3 - p0 + 3 (p1 - p2) + b = 3 (p0 - 2 p1 + p2) + c = 3 (p1 - p0) + d = p0 - x(u) + + We can then use findCubicRoots to locate the single root of + this cubic equation found in the range [0,1] used for this + section of the FCurve. This works because the FCurve ensures + that the function it represents via the Bezier control points + in the Keyframes is single valued. (as a function of time). + Time, therefore must be single valued on the interval and + therefore have a single root for any given time in the interval + covered by the Keyframes. + */ +float BezierEvaluator::parameterForTime(float time) const +{ + Q_ASSERT(time >= m_time0); + Q_ASSERT(time <= m_time1); + + const float p0 = m_time0; + const float p1 = m_keyframe0.rightControlPoint.x(); + const float p2 = m_keyframe1.leftControlPoint.x(); + const float p3 = m_time1; + + const float coeffs[4] = { + p0 - time, // d + 3.0f * (p1 - p0), // c + 3.0f * (p0 - 2.0f * p1 + p2), // b + p3 - p0 + 3.0f * (p1 - p2) // a + }; + + float roots[3]; + const int numberOfRoots = findCubicRoots(coeffs, roots); + for (int i = 0; i < numberOfRoots; ++i) { + if (roots[i] >= 0 && roots[i] <= 1) + return roots[i]; + } + + qWarning() << "Failed to find root of cubic bezier at time" << time + << "with coeffs: a =" << coeffs[3] << "b =" << coeffs[2] + << "c =" << coeffs[1] << "d =" << coeffs[0]; + return 0.0f; +} + +/*! + \internal + + Finds the roots of the cubic equation ax^3 + bx^2 + cx + d = 0 for + real coefficients and returns the number of roots. The roots are + put into the \a roots array. The coefficients should be passed in + as coeffs[0] = d, coeffs[1] = c, coeffs[2] = b, coeffs[3] = a. + */ +int BezierEvaluator::findCubicRoots(const float coeffs[4], float roots[3]) +{ + // See https://en.wikipedia.org/wiki/Cubic_function#General_solution_to_the_cubic_equation_with_real_coefficients + // for a description. We depress the general cubic to a form that can more easily be solved. Solve it and then + // substitue the results back to get the roots of the original cubic. + int numberOfRoots; + const double oneThird = 1.0 / 3.0; + const double piByThree = M_PI / 3.0; + + // Put cubic into normal format: x^3 + Ax^2 + Bx + C = 0 + const double A = coeffs[ 2 ] / coeffs[ 3 ]; + const double B = coeffs[ 1 ] / coeffs[ 3 ]; + const double C = coeffs[ 0 ] / coeffs[ 3 ]; + + // Substitute x = y - A/3 to eliminate quadratic term (depressed form): + // x^3 + px + q = 0 + const double Asq = A * A; + const double p = oneThird * (-oneThird * Asq + B); + const double q = 1.0 / 2.0 * (2.0 / 27.0 * A * Asq - oneThird * A * B + C); + + // Use Cardano's formula + const double pCubed = p * p * p; + const double discriminant = q * q + pCubed; + + if (qIsNull(discriminant)) { + if (qIsNull(q)) { + // One repeated triple root + roots[0] = 0.0; + numberOfRoots = 1; + } else { + // One single and one double root + double u = qCbrt(-q); + roots[0] = 2.0 * u; + roots[1] = -u; + numberOfRoots = 2; + } + } else if (discriminant < 0) { + // Three real solutions + double phi = oneThird * std::acos(-q / std::sqrt(-pCubed)); + double t = 2.0 * std::sqrt(-p); + + roots[0] = t * std::cos(phi); + roots[1] = -t * std::cos(phi + piByThree); + roots[2] = -t * std::cos(phi - piByThree); + numberOfRoots = 3; + } else { + // One real solution + double sqrtDisc = std::sqrt(discriminant); + double u = qCbrt(sqrtDisc - q); + double v = -qCbrt(sqrtDisc + q); + + roots[0] = u + v; + numberOfRoots = 1; + } + + // Substitute back in + const double sub = oneThird * A; + for (int i = 0; i < numberOfRoots; ++i) + roots[i] -= sub; + + return numberOfRoots; +} + +} // namespace Animation +} // namespace Qt3DAnimation + +QT_END_NAMESPACE diff --git a/src/animation/backend/bezierevaluator_p.h b/src/animation/backend/bezierevaluator_p.h new file mode 100644 index 000000000..5291405b3 --- /dev/null +++ b/src/animation/backend/bezierevaluator_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef BEZIEREVALUATOR_P_H +#define BEZIEREVALUATOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DAnimation { +namespace Animation { + +struct Keyframe; + +class Q_AUTOTEST_EXPORT BezierEvaluator +{ +public: + explicit BezierEvaluator(float time0, const Keyframe &keyframe0, + float time1, const Keyframe &keyframe1) + : m_time0(time0) + , m_time1(time1) + , m_keyframe0(keyframe0) + , m_keyframe1(keyframe1) + { + } + + float valueForTime(float time) const; + float parameterForTime(float time) const; + + static int findCubicRoots(const float coefficients[], float roots[3]); + +private: + float m_time0; + float m_time1; + const Keyframe &m_keyframe0; + const Keyframe &m_keyframe1; +}; + +} // namespace Animation +} // namespace Qt3DAnimation + +QT_END_NAMESPACE + +#endif // BEZIEREVALUATOR_P_H diff --git a/src/animation/backend/fcurve.cpp b/src/animation/backend/fcurve.cpp new file mode 100644 index 000000000..95e3d1568 --- /dev/null +++ b/src/animation/backend/fcurve.cpp @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "fcurve_p.h" +#include <private/bezierevaluator_p.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DAnimation { +namespace Animation { + +FCurve::FCurve() + : m_rangeFinder(m_localTimes) +{ +} + +float FCurve::evaluateAtTime(float localTime) const +{ + // Find keyframes that sandwich the requested localTime + int keyframe0 = m_rangeFinder.findLowerBound(localTime); + + BezierEvaluator evaluator(m_localTimes[keyframe0], m_keyframes[keyframe0], + m_localTimes[keyframe0 + 1], m_keyframes[keyframe0 + 1]); + return evaluator.valueForTime(localTime); +} + +float FCurve::startTime() const +{ + if (!m_localTimes.isEmpty()) + return m_localTimes.first(); + return 0.0f; +} + +float FCurve::endTime() const +{ + if (!m_localTimes.isEmpty()) + return m_localTimes.last(); + return 0.0f; +} + +void FCurve::appendKeyframe(float localTime, const Keyframe &keyframe) +{ + m_localTimes.append(localTime); + m_keyframes.append(keyframe); +} + +} // namespace Animation +} // namespace Qt3DAnimation + +QT_END_NAMESPACE diff --git a/src/animation/backend/fcurve_p.h b/src/animation/backend/fcurve_p.h new file mode 100644 index 000000000..88990fb81 --- /dev/null +++ b/src/animation/backend/fcurve_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DANIMATION_ANIMATION_FCURVE_P_H +#define QT3DANIMATION_ANIMATION_FCURVE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "keyframe_p.h" +#include "functionrangefinder_p.h" +#include <QtCore/qvector.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DAnimation { +namespace Animation { + +class Q_AUTOTEST_EXPORT FCurve +{ +public: + FCurve(); + + int keyframeCount() const { return m_localTimes.size(); } + void appendKeyframe(float localTime, const Keyframe &keyframe); + void clearKeyframes() { m_localTimes.clear(); m_keyframes.clear(); } + + const float &localTime(int index) const { return m_localTimes[index]; } + float &localTime(int index) { return m_localTimes[index]; } + const Keyframe &keyframe(int index) const { return m_keyframes[index]; } + Keyframe &keyframe(int index) { return m_keyframes[index]; } + + float startTime() const; + float endTime() const; + + float evaluateAtTime(float localTime) const; + +private: + QVector<float> m_localTimes; + QVector<Keyframe> m_keyframes; + + FunctionRangeFinder m_rangeFinder; +}; + +} // namespace Animation +} // namespace Qt3DAnimation + +QT_END_NAMESPACE + +#endif // QT3DANIMATION_ANIMATION_FCURVE_P_H diff --git a/src/animation/backend/functionrangefinder.cpp b/src/animation/backend/functionrangefinder.cpp new file mode 100644 index 000000000..168b835ad --- /dev/null +++ b/src/animation/backend/functionrangefinder.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "functionrangefinder_p.h" + +QT_BEGIN_NAMESPACE + +namespace Qt3DAnimation { +namespace Animation { + +/*! + \internal + \class FunctionRangeFinder finds the lower bound index of a range that encloses a function value + + Given a vector of function values (typically abscissa values of some other function), this + class can find the lower bound index of a range that encloses the requested value. This is + very useful for finding the two points that sandwich a value to which you later wish to + interpolate for example. + */ + +/*! + \internal + \fn findLowerBound + + Finds the lower bound index of a range that encloses the requested value. + + We use a technique which tries to be better than a simple bisection. Often when + performing interpolations, subsequent points are correlated with earlier calls. + This is especially true with time based lookups. If two calls are determined to + be correlated, then the next subsequent call will use the hunt function to search + close to the last returned value first. The hunt algorithms searches outward in + increasing step sizes until a sandwiching range is found. Traditional bisection + is then used to refine this result. + + If the previous results are uncorrelated, a simple bisection is used. + */ + +FunctionRangeFinder::FunctionRangeFinder(const QVector<float> &x) + : m_x(x) + , m_previousLowerBound(0) + , m_correlated(0) + , m_rangeSize(2) + , m_correlationThreshold(1) + , m_ascending(true) +{ + updateAutomaticCorrelationThreshold(); + if (!m_x.isEmpty()) + m_ascending = (m_x.last() >= m_x.first()); +} + +/*! + \internal + Locates the lower bound of a range that encloses \a x by a bisection method. +*/ +int FunctionRangeFinder::locate(float x) const +{ + if (m_x.size() < 2 || m_rangeSize < 2 || m_rangeSize > m_x.size()) + return -1; + + int jLower = 0; + int jUpper = m_x.size() - 1; + while (jUpper - jLower > 1) { + int jMid = (jUpper + jLower) >> 1; + if ((x >= m_x[jMid]) == m_ascending) + jLower = jMid; + else + jUpper = jMid; + } + + m_correlated = std::abs(jLower - m_previousLowerBound) <= m_correlationThreshold; + m_previousLowerBound = jLower; + + return std::max(0, std::min(m_x.size() - m_rangeSize, jLower - ((m_rangeSize - 2) >> 1))); +} + +/*! + \internal + Hunts outward from the previous result in increasing step sizes then refines via bisection. + */ +int FunctionRangeFinder::hunt(float x) const +{ + if (m_x.size() < 2 || m_rangeSize < 2 || m_rangeSize > m_x.size()) + return -1; + + int jLower = m_previousLowerBound; + int jMid; + int jUpper; + if (jLower < 0 || jLower > (m_x.size() - 1)) { + jLower = 0; + jUpper = m_x.size() - 1; + } else { + int increment = 1; + if ((x >= m_x[jLower]) == m_ascending) { + for (;;) { + jUpper = jLower + increment; + if (jUpper >= m_x.size() - 1) { + jUpper = m_x.size() - 1; + break; + } else if ((x < m_x[jUpper]) == m_ascending) { + break; + } else { + jLower = jUpper; + increment += increment; + } + } + } else { + jUpper = jLower; + for (;;) { + jLower = jLower - increment; + if (jLower <= 0) { + jLower = 0; + break; + } else if ((x >= m_x[jLower]) == m_ascending) { + break; + } else { + jUpper = jLower; + increment += increment; + } + } + } + } + + while (jUpper - jLower > 1) { + jMid = (jUpper + jLower) >> 1; + if ((x >= m_x[jMid]) == m_ascending) + jLower = jMid; + else + jUpper = jMid; + } + + m_correlated = std::abs(jLower - m_previousLowerBound) <= m_correlationThreshold; + m_previousLowerBound = jLower; + + return std::max(0, std::min(m_x.size() - m_rangeSize, jLower - ((m_rangeSize - 2) >> 1))); +} + +} // namespace Animation +} // namespace Qt3DAnimation + +QT_END_NAMESPACE diff --git a/src/animation/backend/functionrangefinder_p.h b/src/animation/backend/functionrangefinder_p.h new file mode 100644 index 000000000..bea9a2e2f --- /dev/null +++ b/src/animation/backend/functionrangefinder_p.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DANIMATION_ANIMATION_FUNCTIONRANGEFINDER_P_H +#define QT3DANIMATION_ANIMATION_FUNCTIONRANGEFINDER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qvector.h> + +#include <cmath> +#include <cstdlib> + +QT_BEGIN_NAMESPACE + +namespace Qt3DAnimation { +namespace Animation { + +class Q_AUTOTEST_EXPORT FunctionRangeFinder +{ +public: + FunctionRangeFinder(const QVector<float> &x); + + inline int findLowerBound(float x) const { return m_correlated ? hunt(x) : locate(x); } + + int rangeSize() const { return m_rangeSize; } + void setRangeSize(int rangeSize) { m_rangeSize = rangeSize; } + + bool isAscending() const { return m_ascending; } + void setAscending(bool ascending) { m_ascending = ascending; } + + int correlationThreshold() const { return m_correlationThreshold; } + void updateAutomaticCorrelationThreshold() + { + m_correlationThreshold = std::max(1, int(std::pow(float(m_x.size()), 0.25))); + } + +private: + int locate(float x) const; + int hunt(float x) const; + + const QVector<float> &m_x; + mutable int m_previousLowerBound; + mutable bool m_correlated; + int m_rangeSize; + int m_correlationThreshold; + bool m_ascending; +}; + +} // namespace Animation +} // namespace Qt3DAnimation + +QT_END_NAMESPACE + +#endif // QT3DANIMATION_ANIMATION_FUNCTIONRANGEFINDER_P_H diff --git a/src/animation/backend/keyframe_p.h b/src/animation/backend/keyframe_p.h new file mode 100644 index 000000000..550c9fde0 --- /dev/null +++ b/src/animation/backend/keyframe_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT3DANIMATION_ANIMATION_KEYFRAME_P_H +#define QT3DANIMATION_ANIMATION_KEYFRAME_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qvector2d.h> + +QT_BEGIN_NAMESPACE + +namespace Qt3DAnimation { +namespace Animation { + +struct Keyframe +{ + enum Interpolation { + Constant, + Linear, + Bezier + // TODO: Add other easing types + }; + + inline bool operator==(const Keyframe &rhs) const + { + return value == rhs.value + && leftControlPoint == rhs.leftControlPoint + && rightControlPoint == rhs.rightControlPoint + && interpolation == rhs.interpolation; + } + + float value; // Value (time is stored separately in FCurve) + QVector2D leftControlPoint; // Bezier control point (time, value) + QVector2D rightControlPoint; // Bezier control point (time, value) + Interpolation interpolation; // Method to use for evaluation between this Keyframe and the next +}; + +} // namespace Animation +} // namespace Qt3DAnimation + +QT_END_NAMESPACE + +#endif // QT3DANIMATION_ANIMATION_KEYFRAME_P_H diff --git a/tests/auto/animation/animation.pro b/tests/auto/animation/animation.pro index 735b8c585..918dedae4 100644 --- a/tests/auto/animation/animation.pro +++ b/tests/auto/animation/animation.pro @@ -7,5 +7,8 @@ SUBDIRS += \ qtConfig(private_tests) { SUBDIRS += \ animationclip \ - qabstractclipblendnode + qabstractclipblendnode \ + fcurve \ + functionrangefinder \ + bezierevaluator } diff --git a/tests/auto/animation/bezierevaluator/bezierevaluator.pro b/tests/auto/animation/bezierevaluator/bezierevaluator.pro new file mode 100644 index 000000000..954b6be2c --- /dev/null +++ b/tests/auto/animation/bezierevaluator/bezierevaluator.pro @@ -0,0 +1,10 @@ +TEMPLATE = app + +TARGET = tst_bezierevaluator + +QT += core-private 3dcore 3dcore-private 3danimation 3danimation-private testlib + +CONFIG += testcase + +SOURCES += tst_bezierevaluator.cpp + diff --git a/tests/auto/animation/bezierevaluator/tst_bezierevaluator.cpp b/tests/auto/animation/bezierevaluator/tst_bezierevaluator.cpp new file mode 100644 index 000000000..746af8d55 --- /dev/null +++ b/tests/auto/animation/bezierevaluator/tst_bezierevaluator.cpp @@ -0,0 +1,280 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QTest> +#include <Qt3DAnimation/private/bezierevaluator_p.h> +#include <Qt3DAnimation/private/keyframe_p.h> +#include <QtCore/qvector.h> + +#include <cmath> + +Q_DECLARE_METATYPE(Qt3DAnimation::Animation::Keyframe) + +using namespace Qt3DAnimation::Animation; + +class tst_BezierEvaluator : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void checkFindCubicRoots_data() + { + // Test data verified on Wolfram Alpha with snippets such as: + // Plot[x^3-5x^2+x+3,{x,-3,6}] + // Solve[x^3-5x^2+x+3,x] + // If you need more, try these at https://www.wolframalpha.com/ + + QTest::addColumn<float>("a"); + QTest::addColumn<float>("b"); + QTest::addColumn<float>("c"); + QTest::addColumn<float>("d"); + QTest::addColumn<int>("rootCount"); + QTest::addColumn<QVector<float>>("roots"); + + float a = 1.0f; + float b = 0.0f; + float c = 0.0f; + float d = 0.0f; + int rootCount = 1; + QVector<float> roots = { 0.0f }; + QTest::newRow("a=1, b=0, c=0, d=0") << a << b << c << d << rootCount << roots; + roots.clear(); + + a = 1.0f; + b = -1.0f; + c = 1.0f; + d = -1.0f; + rootCount = 1; + roots.resize(1); + roots[0] = 1.0f; + QTest::newRow("a=1, b=-1, c=1, d=-1") << a << b << c << d << rootCount << roots; + roots.clear(); + + a = 1.0f; + b = -2.0f; + c = 1.0f; + d = -1.0f; + rootCount = 1; + roots.resize(1); + roots[0] = 1.7548776f; + QTest::newRow("a=1, b=-2, c=1, d=-1") << a << b << c << d << rootCount << roots; + roots.clear(); + + a = 1.0f; + b = -5.0f; + c = 1.0f; + d = 3.0f; + rootCount = 3; + roots.resize(3); + roots[0] = 2.0f + std::sqrt(7.0f); + roots[1] = 1.0f; + roots[2] = 2.0f - std::sqrt(7.0f); + QTest::newRow("a=1, b=-5, c=1, d=3") << a << b << c << d << rootCount << roots; + roots.clear(); + } + + void checkFindCubicRoots() + { + QFETCH(float, a); + QFETCH(float, b); + QFETCH(float, c); + QFETCH(float, d); + QFETCH(int, rootCount); + QFETCH(QVector<float>, roots); + + float coeffs[4]; + coeffs[0] = d; + coeffs[1] = c; + coeffs[2] = b; + coeffs[3] = a; + + float results[3]; + const int foundRootCount = BezierEvaluator::findCubicRoots(coeffs, results); + + QCOMPARE(foundRootCount, rootCount); + for (int i = 0; i < rootCount; ++i) + QCOMPARE(results[i], roots[i]); + } + + void checkParameterForTime_data() + { + QTest::addColumn<float>("t0"); + QTest::addColumn<Keyframe>("kf0"); + QTest::addColumn<float>("t1"); + QTest::addColumn<Keyframe>("kf1"); + QTest::addColumn<QVector<float>>("times"); + QTest::addColumn<QVector<float>>("bezierParamters"); + + float t0 = 0.0f; + Keyframe kf0{0.0f, {-5.0f, 0.0f}, {5.0f, 0.0f}, Keyframe::Bezier}; + float t1 = 50.0f; + Keyframe kf1{5.0f, {45.0f, 5.0f}, {55.0f, 5.0f}, Keyframe::Bezier}; + const int count = 21; + QVector<float> times = (QVector<float>() + << 0.0f + << 1.00375f + << 2.48f + << 4.37625f + << 6.64f + << 9.21875f + << 12.06f + << 15.11125f + << 18.32f + << 21.63375f + << 25.0f + << 28.36625f + << 31.68f + << 34.88875f + << 37.94f + << 40.78125f + << 43.36f + << 45.62375f + << 47.52f + << 48.99625f + << 50.0f); + + QVector<float> bezierParameters; + float deltaU = 1.0f / float(count - 1); + for (int i = 0; i < count; ++i) + bezierParameters.push_back(float(i) * deltaU); + + QTest::newRow("t=0 to t=50, default easing") << t0 << kf0 + << t1 << kf1 + << times << bezierParameters; + } + + void checkParameterForTime() + { + // GIVEN + QFETCH(float, t0); + QFETCH(Keyframe, kf0); + QFETCH(float, t1); + QFETCH(Keyframe, kf1); + QFETCH(QVector<float>, times); + QFETCH(QVector<float>, bezierParamters); + + // WHEN + BezierEvaluator bezier(t0, kf0, t1, kf1); + + // THEN + for (int i = 0; i < times.size(); ++i) { + const float time = times[i]; + const float u = bezier.parameterForTime(time); + QCOMPARE(u, bezierParamters[i]); + } + } + + void checkValueForTime_data() + { + QTest::addColumn<float>("t0"); + QTest::addColumn<Keyframe>("kf0"); + QTest::addColumn<float>("t1"); + QTest::addColumn<Keyframe>("kf1"); + QTest::addColumn<QVector<float>>("times"); + QTest::addColumn<QVector<float>>("values"); + + float t0 = 0.0f; + Keyframe kf0{0.0f, {-5.0f, 0.0f}, {5.0f, 0.0f}, Keyframe::Bezier}; + float t1 = 50.0f; + Keyframe kf1{5.0f, {45.0f, 5.0f}, {55.0f, 5.0f}, Keyframe::Bezier}; + QVector<float> times = (QVector<float>() + << 0.0f + << 1.00375f + << 2.48f + << 4.37625f + << 6.64f + << 9.21875f + << 12.06f + << 15.11125f + << 18.32f + << 21.63375f + << 25.0f + << 28.36625f + << 31.68f + << 34.88875f + << 37.94f + << 40.78125f + << 43.36f + << 45.62375f + << 47.52f + << 48.99625f + << 50.0f); + + QVector<float> values = (QVector<float>() + << 0.0f + << 0.03625f + << 0.14f + << 0.30375f + << 0.52f + << 0.78125f + << 1.08f + << 1.40875f + << 1.76f + << 2.12625f + << 2.5f + << 2.87375f + << 3.24f + << 3.59125f + << 3.92f + << 4.21875f + << 4.48f + << 4.69625f + << 4.86f + << 4.96375f + << 5.0f); + + QTest::newRow("t=0, value=0 to t=50, value=5, default easing") << t0 << kf0 + << t1 << kf1 + << times << values; + } + + void checkValueForTime() + { + // GIVEN + QFETCH(float, t0); + QFETCH(Keyframe, kf0); + QFETCH(float, t1); + QFETCH(Keyframe, kf1); + QFETCH(QVector<float>, times); + QFETCH(QVector<float>, values); + + // WHEN + BezierEvaluator bezier(t0, kf0, t1, kf1); + + // THEN + for (int i = 0; i < times.size(); ++i) { + const float time = times[i]; + const float value = bezier.valueForTime(time); + QCOMPARE(value, values[i]); + } + } +}; + +QTEST_APPLESS_MAIN(tst_BezierEvaluator) + +#include "tst_bezierevaluator.moc" diff --git a/tests/auto/animation/fcurve/fcurve.pro b/tests/auto/animation/fcurve/fcurve.pro new file mode 100644 index 000000000..7b202d815 --- /dev/null +++ b/tests/auto/animation/fcurve/fcurve.pro @@ -0,0 +1,10 @@ +TEMPLATE = app + +TARGET = tst_fcurve + +QT += core-private 3dcore 3dcore-private 3danimation 3danimation-private testlib + +CONFIG += testcase + +SOURCES += tst_fcurve.cpp + diff --git a/tests/auto/animation/fcurve/tst_fcurve.cpp b/tests/auto/animation/fcurve/tst_fcurve.cpp new file mode 100644 index 000000000..8cd8b9537 --- /dev/null +++ b/tests/auto/animation/fcurve/tst_fcurve.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QTest> +#include <private/fcurve_p.h> + +using namespace Qt3DAnimation::Animation; + +class tst_FCurve : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void checkDefaultConstruction() + { + // WHEN + FCurve fcurve; + + // THEN + QCOMPARE(fcurve.keyframeCount(), 0); + QCOMPARE(fcurve.startTime(), 0.0f); + QCOMPARE(fcurve.endTime(), 0.0f); + } + + void checkAddingKeyframes() + { + // GIVEN + FCurve fcurve; + + // WHEN + const Keyframe kf0{0.0f, {-5.0f, 0.0f}, {5.0f, 0.0f}, Keyframe::Bezier}; + fcurve.appendKeyframe(0.0f, kf0); + + // THEN + QCOMPARE(fcurve.keyframeCount(), 1); + QCOMPARE(fcurve.startTime(), 0.0f); + QCOMPARE(fcurve.endTime(), 0.0f); + + // WHEN + const Keyframe kf1{5.0f, {45.0f, 5.0f}, {55.0f, 5.0f}, Keyframe::Bezier}; + fcurve.appendKeyframe(50.0f, kf1); + + // THEN + QCOMPARE(fcurve.keyframeCount(), 2); + QCOMPARE(fcurve.startTime(), 0.0f); + QCOMPARE(fcurve.endTime(), 50.0f); + QCOMPARE(fcurve.keyframe(0), kf0); + QCOMPARE(fcurve.keyframe(1), kf1); + } + + void checkClearKeyframes() + { + // GIVEN + FCurve fcurve; + fcurve.appendKeyframe(0.0f, Keyframe{0.0f, {-5.0f, 0.0f}, {5.0f, 0.0f}, Keyframe::Bezier}); + fcurve.appendKeyframe(50.0f, Keyframe{5.0f, {45.0f, 5.0f}, {55.0f, 5.0f}, Keyframe::Bezier}); + + // WHEN + fcurve.clearKeyframes(); + + // THEN + QCOMPARE(fcurve.keyframeCount(), 0); + QCOMPARE(fcurve.startTime(), 0.0f); + QCOMPARE(fcurve.endTime(), 0.0f); + } + + void checkEvaluateAtTime() + { + + } +}; + +QTEST_APPLESS_MAIN(tst_FCurve) + +#include "tst_fcurve.moc" diff --git a/tests/auto/animation/functionrangefinder/functionrangefinder.pro b/tests/auto/animation/functionrangefinder/functionrangefinder.pro new file mode 100644 index 000000000..06b32037c --- /dev/null +++ b/tests/auto/animation/functionrangefinder/functionrangefinder.pro @@ -0,0 +1,11 @@ +TEMPLATE = app + +TARGET = tst_sandwichfinder + +QT += core-private 3dcore 3dcore-private 3danimation 3danimation-private testlib + +CONFIG += testcase + +SOURCES += \ + tst_functionrangefinder.cpp + diff --git a/tests/auto/animation/functionrangefinder/tst_functionrangefinder.cpp b/tests/auto/animation/functionrangefinder/tst_functionrangefinder.cpp new file mode 100644 index 000000000..3786bd58f --- /dev/null +++ b/tests/auto/animation/functionrangefinder/tst_functionrangefinder.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QTest> +#include <private/functionrangefinder_p.h> + +using namespace Qt3DAnimation::Animation; + +class tst_FunctionRangeFinder : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void checkDefaultConstruction() + { + // GIVEN + QVector<float> data; + + // WHEN + FunctionRangeFinder finder(data); + + // THEN + QCOMPARE(finder.rangeSize(), 2); + QCOMPARE(finder.isAscending(), true); + QCOMPARE(finder.correlationThreshold(), 1); + } + + void checkConstructionWithData_data() + { + QTest::addColumn<QVector<float>>("x"); + QTest::addColumn<int>("correlationThreshold"); + QTest::addColumn<bool>("ascending"); + + QVector<float> data = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f }; + int correlationThreshold = 1; + bool ascending = true; + QTest::newRow("10 entries, ascending") << data << correlationThreshold << ascending; + data.clear(); + + data.resize(10000); + for (int i = 0; i < 10000; ++i) + data[i] = float(i); + ascending = true; + correlationThreshold = std::pow(float(10000), 0.25f); + QTest::newRow("10k entries, ascending") << data << correlationThreshold << ascending; + data.clear(); + + data.resize(10000); + for (int i = 0; i < 10000; ++i) + data[10000 - i - 1] = float(i); + ascending = false; + correlationThreshold = std::pow(float(10000), 0.25f); + QTest::newRow("10k entries, descending") << data << correlationThreshold << ascending; + data.clear(); + } + + void checkConstructionWithData() + { + // GIVEN + QFETCH(QVector<float>, x); + QFETCH(int, correlationThreshold); + QFETCH(bool, ascending); + + // WHEN + FunctionRangeFinder finder(x); + + // THEN + QCOMPARE(finder.rangeSize(), 2); + QCOMPARE(finder.isAscending(), ascending); + QCOMPARE(finder.correlationThreshold(), correlationThreshold); + } + + void checkFindLowerBound_data() + { + QTest::addColumn<QVector<float>>("x"); + QTest::addColumn<QVector<float>>("needles"); + QTest::addColumn<QVector<int>>("lowerBounds"); + + QVector<float> data = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f, 10.0f }; + QVector<float> needles = { 2.5f }; + QVector<int> lowerBounds = { 1 }; + QTest::newRow("10 entries, ascending") << data << needles << lowerBounds; + data.clear(); + needles.clear(); + lowerBounds.clear(); + + data.resize(10000); + for (int i = 0; i < 10000; ++i) + data[i] = float(i); + + needles.push_back(23.75f); + lowerBounds.push_back(23); + + needles.push_back(500.03f); + lowerBounds.push_back(500); + + needles.push_back(500.2f); + lowerBounds.push_back(500); + + needles.push_back(501.4f); // Will trigger hunt codepath as previous test is correlated + lowerBounds.push_back(501); + + needles.push_back(3405.123f); + lowerBounds.push_back(3405); + + QTest::newRow("10k entries, ascending") << data << needles << lowerBounds; + data.clear(); + needles.clear(); + lowerBounds.clear(); + + data.resize(10); + for (int i = 0; i < 10; ++i) + data[10 - i - 1] = float(i); + + needles.push_back(8.1f); + lowerBounds.push_back(0); + + needles.push_back(7.2f); + lowerBounds.push_back(1); + + needles.push_back(0.5f); + lowerBounds.push_back(8); + + QTest::newRow("10 entries, descending") << data << needles << lowerBounds; + data.clear(); + } + + void checkFindLowerBound() + { + // GIVEN + QFETCH(QVector<float>, x); + QFETCH(QVector<float>, needles); + QFETCH(QVector<int>, lowerBounds); + FunctionRangeFinder finder(x); + + for (int i = 0; i < needles.size(); ++i) { + // WHEN + int result = finder.findLowerBound(needles[i]); + + // THEN + QCOMPARE(result, lowerBounds[i]); + } + } +}; + +QTEST_APPLESS_MAIN(tst_FunctionRangeFinder) + +#include "tst_functionrangefinder.moc" |