Revision control

Copy as Markdown

// Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QTest>
#include <Qt3DAnimation/private/bezierevaluator_p.h>
#include <Qt3DAnimation/private/keyframe_p.h>
#include <QtCore/qlist.h>
#include <cmath>
Q_DECLARE_METATYPE(Qt3DAnimation::Animation::Keyframe)
using namespace Qt3DAnimation;
using namespace Qt3DAnimation::Animation;
static const QList<float> globalTimes = { 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 };
static const QList<float> globalValues = { 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 };
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<QList<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();
// quadratic equation
a = 0.0f;
b = 9.0f;
c = 11.0f;
d = 3.0f;
roots.resize(2);
roots[0] = -11.0f/18.0f + std::sqrt(13.0f) / 18.0f;
roots[1] = -11.0f/18.0f - std::sqrt(13.0f) / 18.0f;
QTest::newRow("a=0, b=9, c=11, d=3") << a << b << c << d << roots.size() << roots;
roots.clear();
// quadratic equation with discriminant = 0
a = 0.0f;
b = 1.0f;
c = 2.0f;
d = 1.0f;
roots.resize(1);
roots[0] = -1.f;
QTest::newRow("a=0, b=1, c=2, d=1") << a << b << c << d << roots.size() << roots;
roots.clear();
// quadratic equation with discriminant < 0
a = 0.0f;
b = 1.0f;
c = 4.0f;
d = 8.0f;
roots.resize(0);
QTest::newRow("a=0, b=1, c=4, d=8") << a << b << c << d << roots.size() << roots;
roots.clear();
// linear equation
a = 0.0f;
b = 0.0f;
c = 2.0f;
d = 1.0f;
roots.resize(1);
roots[0] = -0.5f;
QTest::newRow("a=0, b=0, c=2, d=1") << a << b << c << d << roots.size() << roots;
roots.clear();
// linear equation
a = 0.0f;
b = 0.0f;
c = 8.0f;
d = -5.0f;
roots.resize(1);
roots[0] = -d/c;
QTest::newRow("a=0, b=0, c=8, d=-5") << a << b << c << d << roots.size() << roots;
roots.clear();
// invalid equation
a = 0.0f;
b = 0.0f;
c = 0.0f;
d = -5.0f;
roots.resize(0);
QTest::newRow("a=0, b=0, c=0, d=-5") << a << b << c << d << roots.size() << roots;
roots.clear();
// Invalid equation
a = 0.0f;
b = 0.0f;
c = 0.0f;
d = 42.0f;
roots.resize(0);
QTest::newRow("a=0, b=0, c=0, d=42") << a << b << c << d << roots.size() << roots;
roots.clear();
// almost linear equation
a = 1.90735e-06f;
b = -2.86102e-06f;
c = 5.0;
d = 0.0;
roots.resize(1);
roots[0] = -d/c;
QTest::newRow("a=~0, b=~0, c=5, d=0") << a << b << c << d << roots.size() << roots;
roots.clear();
// case that produces a result just below zero, that should be evaluated as zero
a = -0.75f;
b = 0.75f;
c = 2.5;
d = 0.0;
roots.resize(3);
roots[0] = 2.39297f;
roots[1] = 0.f;
roots[2] = -1.39297f;
QTest::newRow("a=-0.75, b=0.75, c=2.5, d=0") << a << b << c << d << roots.size() << roots;
roots.clear();
// Case that produces a discriminant that is close enough to zero that it should be
// evaluated as zero.
// Expected roots = 0.0, ~1.5
a = -3.998f;
b = 5.997f;
c = 0.0f;
d = 0.0f;
roots.resize(2);
roots[0] = 1.5f;
roots[1] = 0.0f;
QTest::newRow("a=-3.998, b=5.997, c=0, d=0") << a << b << c << d << roots.size() << roots;
roots.clear();
}
void checkFindCubicRoots()
{
QFETCH(float, a);
QFETCH(float, b);
QFETCH(float, c);
QFETCH(float, d);
QFETCH(int, rootCount);
QFETCH(QList<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<QList<float>>("times");
QTest::addColumn<QList<float>>("bezierParamters");
{
float t0 = 0.0f;
Keyframe kf0{0.0f, {-5.0f, 0.0f}, {5.0f, 0.0f}, QKeyFrame::BezierInterpolation};
float t1 = 50.0f;
Keyframe kf1{5.0f, {45.0f, 5.0f}, {55.0f, 5.0f}, QKeyFrame::BezierInterpolation};
const int count = 21;
QList<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
<< globalTimes << bezierParameters;
}
{
// This test creates a case where the coefficients for finding
// the cubic roots will be a = 0, b = 0, c ~= 6.28557 d ~= -6.28557
// Because c ~= d, the answer should be one root = 1, but
// because of numerical imprecision, it will be slightly larger.
// We have a fuzzy check in parameterForTime that takes care of this.
float t0 = 3.71443009f;
Keyframe kf0{150.0f, {0.0f, 0.0f}, {5.80961999f, 150.0f}, QKeyFrame::BezierInterpolation};
float t1 = 10.0f;
Keyframe kf1{-150.0f, {7.904809959f, 150.0f}, {0.f, 0.f}, QKeyFrame::BezierInterpolation};
QList<float> times = { 10.f };
QList<float> results = { 1.0f };
QTest::newRow("t=0 to t=10, regression") << t0 << kf0
<< t1 << kf1
<< times << results;
}
}
void checkParameterForTime()
{
// GIVEN
QFETCH(float, t0);
QFETCH(Keyframe, kf0);
QFETCH(float, t1);
QFETCH(Keyframe, kf1);
QFETCH(QList<float>, times);
QFETCH(QList<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<QList<float>>("times");
QTest::addColumn<QList<float>>("values");
float t0 = 0.0f;
Keyframe kf0{0.0f, {-5.0f, 0.0f}, {5.0f, 0.0f}, QKeyFrame::BezierInterpolation};
float t1 = 50.0f;
Keyframe kf1{5.0f, {45.0f, 5.0f}, {55.0f, 5.0f}, QKeyFrame::BezierInterpolation};
QTest::newRow("t=0, value=0 to t=50, value=5, default easing") << t0 << kf0
<< t1 << kf1
<< globalTimes << globalValues;
}
void checkValueForTime()
{
// GIVEN
QFETCH(float, t0);
QFETCH(Keyframe, kf0);
QFETCH(float, t1);
QFETCH(Keyframe, kf1);
QFETCH(QList<float>, times);
QFETCH(QList<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"