Skip to content

Commit

Permalink
Added unit tests for looping spline constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
Elliott Mahler committed Jan 24, 2017
1 parent 5b8f620 commit a14a09a
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 8 deletions.
2 changes: 1 addition & 1 deletion spline_library/spline.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class SplineLoopingImpl: public Spline<InterpolationType, floating_t>
bool isLooping(void) const override { return true; }

size_t segmentCount(void) const override { return common.segmentCount(); }
size_t segmentForT(floating_t t) const override { return common.segmentForT(t); }
size_t segmentForT(floating_t t) const override { return common.segmentForT(SplineCommon::wrapGlobalT(t, maxT)); }
floating_t segmentT(size_t segmentIndex) const override { return common.segmentT(segmentIndex); }
floating_t segmentArcLength(size_t segmentIndex, floating_t a, floating_t b) const override { return common.segmentLength(segmentIndex, a, b); }

Expand Down
3 changes: 2 additions & 1 deletion spline_library/splines/cubic_hermite_spline.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ class LoopingCubicHermiteSpline final : public SplineLoopingImpl<CubicHermiteSpl
std::vector<floating_t> knots = SplineCommon::computeLoopingTValues(points, alpha, 0);

//pre-arrange the data needed for interpolation
std::vector<typename CubicHermiteSplineCommon<InterpolationType, floating_t>::CubicHermiteSplinePoint> positionData(points.size());
std::vector<typename CubicHermiteSplineCommon<InterpolationType, floating_t>::CubicHermiteSplinePoint> positionData(points.size() + 1);
for(int i = 0; i < points.size(); i++)
{
positionData[i].position = points[i];
Expand Down Expand Up @@ -331,6 +331,7 @@ class LoopingCubicHermiteSpline final : public SplineLoopingImpl<CubicHermiteSpl
positionData[i].tangent = tangents[i];
}
positionData[size] = positionData[0];
knots[size] = paddedKnots[size + padding];

common = CubicHermiteSplineCommon<InterpolationType, floating_t>(std::move(positionData), std::move(knots));
}
Expand Down
2 changes: 1 addition & 1 deletion spline_library/splines/natural_spline.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class LoopingNaturalSpline final : public SplineLoopingImpl<NaturalSplineCommon,
std::vector<InterpolationType> inputVector(size);
for(size_t i = 0; i < size; i++)
{
InterpolationType neighborDelta = 3 * (deltaPoint.at(i) - deltaPoint.at((i - 1 + size) % size));
InterpolationType neighborDelta = floating_t(3) * (deltaPoint.at(i) - deltaPoint.at((i - 1 + size) % size));
inputVector[i] = neighborDelta;
}

Expand Down
3 changes: 2 additions & 1 deletion spline_library/splines/quintic_hermite_spline.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ class LoopingQuinticHermiteSpline final : public SplineLoopingImpl<QuinticHermit
std::vector<floating_t> knots = SplineCommon::computeLoopingTValues(points, alpha, 0);

//pre-arrange the data needed for interpolation
std::vector<typename QuinticHermiteSplineCommon<InterpolationType, floating_t>::QuinticHermiteSplinePoint> positionData(numSegments + 1);
std::vector<typename QuinticHermiteSplineCommon<InterpolationType, floating_t>::QuinticHermiteSplinePoint> positionData(points.size() + 1);
for(size_t i = 0; i < points.size(); i++)
{
positionData[i].position = points.at(i);
Expand Down Expand Up @@ -434,6 +434,7 @@ class LoopingQuinticHermiteSpline final : public SplineLoopingImpl<QuinticHermit
positionData[i].curvature = curves[i];
}
positionData[size] = positionData[0];
knots[size] = paddedKnots[size + padding];

common = QuinticHermiteSplineCommon<InterpolationType, floating_t>(std::move(positionData), std::move(knots));
}
Expand Down
6 changes: 3 additions & 3 deletions spline_library/utils/spline_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ std::vector<floating_t> SplineCommon::computeLoopingTValues(
{
size_t size = points.size();
floating_t maxT = floating_t(size);
std::vector<floating_t> tValues(size + padding * 2);
std::vector<floating_t> tValues(size + padding * 2 + 1);

//compute the t values each point
tValues[padding] = 0;
Expand All @@ -164,8 +164,8 @@ std::vector<floating_t> SplineCommon::computeLoopingTValues(
//we calculate the padding by basically wraping the difference in T values
for(size_t i = 0; i < padding; i++)
{
tValues[i] = tValues[i + size + 1] - maxT;
tValues[i + size + padding + 1] = tValues[i + padding] + maxT;
tValues[i] = tValues[i + size] - maxT;
tValues[i + size + padding + 1] = tValues[i + padding + 1] + maxT;
}

return tValues;
Expand Down
31 changes: 31 additions & 0 deletions test/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,37 @@ class SplineCreator



//several functions that create instances of looping splines
static SplinePtr createLoopingUniformCR(std::vector<T> data) {
return std::make_shared<LoopingUniformCRSpline<T, floating_t>>(data);
}
static SplinePtr createLoopingCatmullRom(std::vector<T> data, floating_t alpha) {
return std::make_shared<LoopingCubicHermiteSpline<T, floating_t>>(data, alpha);
}
static SplinePtr createLoopingCubicHermite(std::vector<T> data, floating_t alpha) {
auto tangents = makeTangents(data);
return std::make_shared<LoopingCubicHermiteSpline<T, floating_t>>(data, tangents, alpha);
}
static SplinePtr createLoopingQuinticCatmullRom(std::vector<T> data, floating_t alpha) {
return std::make_shared<LoopingQuinticHermiteSpline<T, floating_t>>(data, alpha);
}
static SplinePtr createLoopingQuinticHermite(std::vector<T> data, floating_t alpha) {
auto tangents = makeTangents(data);
auto curves = makeTangents(tangents);
return std::make_shared<LoopingQuinticHermiteSpline<T, floating_t>>(data, tangents, curves, alpha);
}
static SplinePtr createLoopingNatural(std::vector<T> data, floating_t alpha) {
return std::make_shared<LoopingNaturalSpline<T, floating_t>>(data, alpha);
}
static SplinePtr createLoopingUniformBSpline(std::vector<T> data) {
return std::make_shared<LoopingUniformCubicBSpline<T, floating_t>>(data);
}
static SplinePtr createLoopingGenericBSpline(std::vector<T> data, size_t degree) {
return std::make_shared<LoopingGenericBSpline<T, floating_t>>(data, degree);
}



//several functions that generate data points
static std::vector<T> generateRandomData(size_t size, unsigned seed=10) {
std::minstd_rand gen;
Expand Down
81 changes: 81 additions & 0 deletions test/testspline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,87 @@ void TestSpline::testMethods(void)
QCOMPARE(spline->segmentForT(spline->getMaxT() * 2), expectedSegments - 1);
}


void TestSpline::testMethods_Looping_data(void)
{
QTest::addColumn<std::shared_ptr<Spline<Vector2>>>("spline");
QTest::addColumn<float>("alpha");
QTest::addColumn<size_t>("expectedSegments");

//our data will just be points on a straight line
//this makes the total arc length of this line (data[size-1] - data[0]).length() so it'll be easy to verify
auto data = TestDataFloat::generateRandomData(10);

QTest::newRow("uniformCR") << TestDataFloat::createLoopingUniformCR(data) << 0.0f << data.size();
QTest::newRow("catmullRom") << TestDataFloat::createLoopingCatmullRom(data, 0.0f) << 0.0f << data.size();
QTest::newRow("catmullRomAlpha") << TestDataFloat::createLoopingCatmullRom(data, 0.5f) << 0.5f << data.size();

QTest::newRow("cubicHermite") << TestDataFloat::createLoopingCubicHermite(data, 0.0f) << 0.0f << data.size();
QTest::newRow("cubicHermiteAlpha") << TestDataFloat::createLoopingCubicHermite(data, 0.5f) << 0.5f << data.size();

QTest::newRow("quinticCatmullRom") << TestDataFloat::createLoopingQuinticCatmullRom(data, 0.0f) << 0.0f << data.size();
QTest::newRow("quinticCatmullRomAlpha") << TestDataFloat::createLoopingQuinticCatmullRom(data, 0.5f) << 0.5f << data.size();

QTest::newRow("quinticHermite") << TestDataFloat::createLoopingQuinticHermite(data, 0.0f) << 0.0f << data.size();
QTest::newRow("quinticHermiteAlpha") << TestDataFloat::createLoopingQuinticHermite(data, 0.5f) << 0.5f << data.size();

QTest::newRow("natural") << TestDataFloat::createLoopingNatural(data, 0.0f) << 0.0f << data.size();
QTest::newRow("naturalAlpha") << TestDataFloat::createLoopingNatural(data, 0.5f) << 0.5f << data.size();

QTest::newRow("uniformB") << TestDataFloat::createLoopingUniformBSpline(data) << 0.0f << data.size();

QTest::newRow("genericBCubic") << TestDataFloat::createLoopingGenericBSpline(data, 3) << 0.0f << data.size();
QTest::newRow("genericBQuintic") << TestDataFloat::createLoopingGenericBSpline(data, 5) << 0.0f << data.size();
}

void TestSpline::testMethods_Looping(void)
{
QFETCH(std::shared_ptr<Spline<Vector2>>, spline);
QFETCH(float, alpha);
QFETCH(size_t, expectedSegments);

//test the methods that require no input
float maxT = spline->getMaxT();
QCOMPARE(maxT, float(expectedSegments));

size_t segmentCount = spline->segmentCount();
QCOMPARE(segmentCount, expectedSegments);

bool looping = spline -> isLooping();
QCOMPARE(looping, true);

//test the "segmentT" method to make sure it returns the expected values
std::vector<float> expectedT = SplineCommon::computeLoopingTValues(spline->getOriginalPoints(), alpha, 0);

for(size_t i = 0; i < spline->segmentCount(); i++) {
QCOMPARE(spline->segmentT(i), expectedT[i]);
}

//test the "segment for T" method to make sure it returns the segment index we expect
for(size_t i = 0; i < spline->segmentCount(); i++) {
float beginT = expectedT[i];
QCOMPARE(spline->segmentForT(beginT), i);

float halfwayT = (expectedT[i] + expectedT[i + 1]) / 2;
QCOMPARE(spline->segmentForT(halfwayT), i);
}

//make sure out-of-range T values in segmentForT return correct results
QCOMPARE(spline->segmentForT(-3), spline->segmentForT(maxT - 3));
QCOMPARE(spline->segmentForT(spline->getMaxT() + 2), spline->segmentForT(2));

//verify that getPosition calls wrap around from the end to the beginning
Vector2 frontPosition = spline->getPosition(0);

float dt = .0001f;
Vector2 backPosition = spline->getPosition(maxT-dt) + spline->getTangent(maxT-dt).tangent*dt;

QCOMPARE(frontPosition[0], backPosition[0]);
QCOMPARE(frontPosition[1], backPosition[1]);
}



void TestSpline::testDerivatives_data(void)
{
QTest::addColumn<std::shared_ptr<Spline<Vector2>>>("spline");
Expand Down
6 changes: 5 additions & 1 deletion test/testspline.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ class TestSpline : public QObject

private slots:

//test each spline's basic methods like maxT, segmentCount
//test each spline's basic methods like constructors, maxT, segmentCount
void testMethods_data(void);
void testMethods(void);

//test each looping spline's basic methods like constructors, maxT, segmentCount
void testMethods_Looping_data(void);
void testMethods_Looping(void);

//use numeric integration to verify the first, second, and third derivatives
void testDerivatives_data(void);
void testDerivatives(void);
Expand Down

0 comments on commit a14a09a

Please sign in to comment.