| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <catch2/catch_test_macros.hpp> |
| | #include <catch2/catch_approx.hpp> |
| |
|
| | #include "rs_spline.h" |
| | #include "lc_splinehelper.h" |
| | #include "rs_vector.h" |
| | #include "rs_math.h" |
| |
|
| | using Catch::Approx; |
| | namespace { |
| | bool compareVector(const RS_Vector& va, const RS_Vector& vb, double tol = 1e-4) { |
| | return va.distanceTo(vb) <= tol; |
| | } |
| | } |
| |
|
| | TEST_CASE("LC_SplineHelper Knot Conversions", "[LC_SplineHelper]") { |
| | size_t unwrappedControlCount = 4; |
| | size_t splineDegree = 3; |
| |
|
| | SECTION("Convert Closed to Open - Uniform") { |
| | std::vector<double> closedKnots = {0.0,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0}; |
| | auto openKnots = LC_SplineHelper::convertClosedToOpenKnotVector(closedKnots, unwrappedControlCount, splineDegree); |
| | REQUIRE(openKnots.size() == 8); |
| | REQUIRE(openKnots[0] == Approx(0.0)); |
| | REQUIRE(openKnots[1] == Approx(1.0)); |
| | REQUIRE(openKnots[2] == Approx(2.0)); |
| | REQUIRE(openKnots[3] == Approx(3.0)); |
| | REQUIRE(openKnots[4] == Approx(4.0)); |
| | REQUIRE(openKnots[5] == Approx(5.0)); |
| | REQUIRE(openKnots[6] == Approx(6.0)); |
| | REQUIRE(openKnots[7] == Approx(7.0)); |
| | } |
| |
|
| | SECTION("Convert Closed to Open - Non-Uniform") { |
| | std::vector<double> closedKnots = {-3.0, -1.5, -0.5, 0.0, 1.0, 3.0, 5.0, 6.0, 7.5, 8.5, 10.0}; |
| | auto openKnots = LC_SplineHelper::convertClosedToOpenKnotVector(closedKnots, unwrappedControlCount, splineDegree); |
| | REQUIRE(openKnots.size() == 8); |
| | REQUIRE(openKnots[0] == Approx(0.0)); |
| | REQUIRE(openKnots[1] == Approx(1.5)); |
| | REQUIRE(openKnots[2] == Approx(3.0)); |
| | REQUIRE(openKnots[3] == Approx(4.5)); |
| | REQUIRE(openKnots[4] == Approx(6.5)); |
| | REQUIRE(openKnots[5] == Approx(8.0)); |
| | REQUIRE(openKnots[6] == Approx(9.0)); |
| | REQUIRE(openKnots[7] == Approx(10.5)); |
| | } |
| |
|
| |
|
| | SECTION("Convert Open to Closed - Non-Clamped Non-Uniform") { |
| | std::vector<double> openKnots = {0.0, 0.5, 1.0, 2.0, 3.0, 4.5, 6.0, 8.0}; |
| | auto closedKnots = LC_SplineHelper::convertOpenToClosedKnotVector(openKnots, unwrappedControlCount, splineDegree); |
| | REQUIRE(closedKnots.size() == 11); |
| | REQUIRE(closedKnots[0] == Approx(0.0)); |
| | REQUIRE(closedKnots[1] == Approx(0.5)); |
| | REQUIRE(closedKnots[2] == Approx(1.0)); |
| | REQUIRE(closedKnots[3] == Approx(2.0)); |
| | REQUIRE(closedKnots[4] == Approx(3.0)); |
| | REQUIRE(closedKnots[5] == Approx(3.5)); |
| | REQUIRE(closedKnots[6] == Approx(4.0)); |
| | REQUIRE(closedKnots[7] == Approx(5.0)); |
| | REQUIRE(closedKnots[8] == Approx(6.0)); |
| | REQUIRE(closedKnots[9] == Approx(6.5)); |
| | REQUIRE(closedKnots[10] == Approx(7.0)); |
| | } |
| |
|
| | SECTION("Convert Open to Closed - Edge Case: Invalid Period") { |
| | std::vector<double> invalidOpen = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; |
| | auto closedKnots = LC_SplineHelper::convertOpenToClosedKnotVector(invalidOpen, unwrappedControlCount, splineDegree); |
| | REQUIRE(closedKnots.empty()); |
| | } |
| |
|
| | SECTION("Normalize Knot Vector") { |
| | std::vector<double> inputKnots = {2.0, 3.0, 4.0}; |
| | std::vector<double> fallbackKnots = {0.0, 1.0}; |
| | auto normalizedKnots = LC_SplineHelper::getNormalizedKnotVector(inputKnots, 0.0, fallbackKnots); |
| | REQUIRE(normalizedKnots.size() == 3); |
| | REQUIRE(normalizedKnots[0] == Approx(0.0)); |
| | REQUIRE(normalizedKnots[1] == Approx(1.0)); |
| | REQUIRE(normalizedKnots[2] == Approx(2.0)); |
| | } |
| |
|
| | SECTION("Normalize Knot Vector - Fallback") { |
| | std::vector<double> inputKnots = {2.0}; |
| | std::vector<double> fallbackKnots = {0.0, 1.0, 2.0}; |
| | auto normalizedKnots = LC_SplineHelper::getNormalizedKnotVector(inputKnots, 0.0, fallbackKnots); |
| | REQUIRE(normalizedKnots == fallbackKnots); |
| | } |
| |
|
| | SECTION("Normalize Knot Vector - Edge Case: Empty Input") { |
| | std::vector<double> inputKnots = {}; |
| | std::vector<double> fallbackKnots = {0.0, 1.0}; |
| | auto normalizedKnots = LC_SplineHelper::getNormalizedKnotVector(inputKnots, 0.0, fallbackKnots); |
| | REQUIRE(normalizedKnots == fallbackKnots); |
| | } |
| |
|
| | SECTION("Clamp and Unclamp - Non-Uniform") { |
| | std::vector<double> uniformKnots = {0.0, 0.5, 1.0, 2.0, 3.0, 4.0, 4.5, 5.0}; |
| | size_t controlPointCount = 4; |
| | size_t splineOrder = 4; |
| | auto clampedKnots = LC_SplineHelper::clampKnotVector(uniformKnots, controlPointCount, splineOrder); |
| | REQUIRE(clampedKnots[0] == Approx(2.0)); |
| | REQUIRE(clampedKnots[1] == Approx(2.0)); |
| | REQUIRE(clampedKnots[2] == Approx(2.0)); |
| | REQUIRE(clampedKnots[3] == Approx(2.0)); |
| | REQUIRE(clampedKnots[4] == Approx(3.0)); |
| | REQUIRE(clampedKnots[5] == Approx(3.0)); |
| | REQUIRE(clampedKnots[6] == Approx(3.0)); |
| | REQUIRE(clampedKnots[7] == Approx(3.0)); |
| |
|
| | auto unclampedKnots = LC_SplineHelper::unclampKnotVector(clampedKnots, controlPointCount, splineOrder); |
| | REQUIRE(unclampedKnots[0] == Approx(-1.0)); |
| | REQUIRE(unclampedKnots[1] == Approx(0.0)); |
| | REQUIRE(unclampedKnots[2] == Approx(1.0)); |
| | REQUIRE(unclampedKnots[3] == Approx(2.0)); |
| | REQUIRE(unclampedKnots[4] == Approx(3.0)); |
| | REQUIRE(unclampedKnots[5] == Approx(4.0)); |
| | REQUIRE(unclampedKnots[6] == Approx(5.0)); |
| | REQUIRE(unclampedKnots[7] == Approx(6.0)); |
| | } |
| |
|
| | SECTION("Clamp and Unclamp - Uniform") { |
| | std::vector<double> uniformKnots = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}; |
| | size_t controlPointCount = 4; |
| | size_t splineOrder = 4; |
| | auto clampedKnots = LC_SplineHelper::clampKnotVector(uniformKnots, controlPointCount, splineOrder); |
| | REQUIRE(clampedKnots[0] == Approx(3.0)); |
| | REQUIRE(clampedKnots[1] == Approx(3.0)); |
| | REQUIRE(clampedKnots[2] == Approx(3.0)); |
| | REQUIRE(clampedKnots[3] == Approx(3.0)); |
| | REQUIRE(clampedKnots[4] == Approx(4.0)); |
| | REQUIRE(clampedKnots[5] == Approx(4.0)); |
| | REQUIRE(clampedKnots[6] == Approx(4.0)); |
| | REQUIRE(clampedKnots[7] == Approx(4.0)); |
| |
|
| | auto unclampedKnots = LC_SplineHelper::unclampKnotVector(clampedKnots, controlPointCount, splineOrder); |
| | REQUIRE(unclampedKnots[0] == Approx(0.0)); |
| | REQUIRE(unclampedKnots[1] == Approx(1.0)); |
| | REQUIRE(unclampedKnots[2] == Approx(2.0)); |
| | REQUIRE(unclampedKnots[3] == Approx(3.0)); |
| | REQUIRE(unclampedKnots[4] == Approx(4.0)); |
| | REQUIRE(unclampedKnots[5] == Approx(5.0)); |
| | REQUIRE(unclampedKnots[6] == Approx(6.0)); |
| | REQUIRE(unclampedKnots[7] == Approx(7.0)); |
| | } |
| |
|
| | SECTION("Clamp and Unclamp - Edge Case: Constant Knots") { |
| | std::vector<double> constantKnots = {5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0}; |
| | size_t controlPointCount = 4; |
| | size_t splineOrder = 4; |
| | auto clampedKnots = LC_SplineHelper::clampKnotVector(constantKnots, controlPointCount, splineOrder); |
| | REQUIRE(clampedKnots == constantKnots); |
| |
|
| | auto unclampedKnots = LC_SplineHelper::unclampKnotVector(clampedKnots, controlPointCount, splineOrder); |
| | REQUIRE(unclampedKnots[0] == Approx(2.0)); |
| | REQUIRE(unclampedKnots[1] == Approx(3.0)); |
| | REQUIRE(unclampedKnots[2] == Approx(4.0)); |
| | REQUIRE(unclampedKnots[3] == Approx(5.0)); |
| | REQUIRE(unclampedKnots[4] == Approx(5.0)); |
| | REQUIRE(unclampedKnots[5] == Approx(6.0)); |
| | REQUIRE(unclampedKnots[6] == Approx(7.0)); |
| | REQUIRE(unclampedKnots[7] == Approx(8.0)); |
| | } |
| | } |
| |
|
| | TEST_CASE("LC_SplineHelper Type Conversions", "[LC_SplineHelper]") { |
| | RS_SplineData splineData(3, false); |
| | splineData.type = RS_SplineData::SplineType::Standard; |
| | splineData.controlPoints = {RS_Vector(0,0), RS_Vector(1,1), RS_Vector(2,0), RS_Vector(3,1)}; |
| | splineData.weights = {1.0, 1.0, 1.0, 1.0}; |
| | splineData.knotslist = LC_SplineHelper::generateOpenUniformKnotVector(4, 4); |
| |
|
| | SECTION("Validation") { |
| | REQUIRE(LC_SplineHelper::validate(splineData)); |
| | } |
| |
|
| | SECTION("To Clamped Open From Standard") { |
| | LC_SplineHelper::toClampedOpenFromStandard(splineData); |
| | REQUIRE(splineData.type == RS_SplineData::SplineType::ClampedOpen); |
| | REQUIRE(splineData.knotslist[0] == splineData.knotslist[1]); |
| | REQUIRE(splineData.knotslist[0] == splineData.knotslist[2]); |
| | REQUIRE(splineData.knotslist[0] == splineData.knotslist[3]); |
| | REQUIRE(splineData.knotslist.back() == splineData.knotslist[splineData.knotslist.size() - 2]); |
| | REQUIRE(splineData.knotslist.back() == splineData.knotslist[splineData.knotslist.size() - 3]); |
| | REQUIRE(splineData.knotslist.back() == splineData.knotslist[splineData.knotslist.size() - 4]); |
| | } |
| |
|
| | SECTION("To Standard From Clamped Open") { |
| | splineData.type = RS_SplineData::SplineType::ClampedOpen; |
| | splineData.knotslist = LC_SplineHelper::clampKnotVector(splineData.knotslist, 4, 4); |
| | LC_SplineHelper::toStandardFromClampedOpen(splineData); |
| | REQUIRE(splineData.type == RS_SplineData::SplineType::Standard); |
| | REQUIRE(splineData.knotslist[0] != splineData.knotslist[3]); |
| | REQUIRE(splineData.knotslist.back() != splineData.knotslist[splineData.knotslist.size() - 4]); |
| | } |
| |
|
| | SECTION("To Wrapped Closed From Standard") { |
| | LC_SplineHelper::toWrappedClosedFromStandard(splineData); |
| | REQUIRE(splineData.type == RS_SplineData::SplineType::WrappedClosed); |
| | REQUIRE(splineData.controlPoints.size() == 7); |
| | REQUIRE(splineData.weights.size() == 7); |
| | REQUIRE(splineData.knotslist.size() == 11); |
| | REQUIRE(splineData.controlPoints[4] == splineData.controlPoints[0]); |
| | REQUIRE(splineData.weights[4] == splineData.weights[0]); |
| | REQUIRE(splineData.controlPoints[5] == splineData.controlPoints[1]); |
| | REQUIRE(splineData.weights[5] == splineData.weights[1]); |
| | REQUIRE(splineData.controlPoints[6] == splineData.controlPoints[2]); |
| | REQUIRE(splineData.weights[6] == splineData.weights[2]); |
| | } |
| |
|
| | SECTION("Update Knot Wrapping") { |
| | size_t unwrappedCount = 4; |
| | LC_SplineHelper::updateKnotWrapping(splineData, true, unwrappedCount); |
| | REQUIRE(splineData.knotslist.size() == 11); |
| | } |
| |
|
| | SECTION("To Wrapped Closed From Standard") { |
| | LC_SplineHelper::toWrappedClosedFromStandard(splineData); |
| | REQUIRE(splineData.type == RS_SplineData::SplineType::WrappedClosed); |
| | REQUIRE(splineData.controlPoints.size() == 7); |
| | REQUIRE(splineData.knotslist.size() == 11); |
| | } |
| |
|
| | SECTION("To Clamped Open From Standard") { |
| | LC_SplineHelper::toClampedOpenFromStandard(splineData); |
| | REQUIRE(splineData.type == RS_SplineData::SplineType::ClampedOpen); |
| | REQUIRE(splineData.knotslist[0] == Approx(splineData.knotslist[3])); |
| | REQUIRE(splineData.knotslist.back() == Approx(splineData.knotslist[splineData.knotslist.size() - 4])); |
| | } |
| |
|
| | SECTION("Round Trip Type Conversion") { |
| | auto originalKnots = splineData.knotslist; |
| | LC_SplineHelper::toClampedOpenFromStandard(splineData); |
| | LC_SplineHelper::toStandardFromClampedOpen(splineData); |
| | REQUIRE(splineData.knotslist == originalKnots); |
| | } |
| | } |
| |
|
| | TEST_CASE("LC_SplineHelper Knot Generators and Manipulations", "[LC_SplineHelper]") { |
| | SECTION("Generate Clamped Uniform Knot Vector") { |
| | auto clampedKnots = LC_SplineHelper::knot(4, 4); |
| | REQUIRE(clampedKnots.size() == 8); |
| | REQUIRE(clampedKnots[0] == Approx(0.0)); |
| | REQUIRE(clampedKnots[3] == Approx(0.0)); |
| | REQUIRE(clampedKnots[4] == Approx(1.0)); |
| | REQUIRE(clampedKnots[7] == Approx(1.0)); |
| | } |
| |
|
| | SECTION("Generate Clamped Uniform - Edge Case: Min Controls") { |
| | auto minClamped = LC_SplineHelper::knot(4, 4); |
| | REQUIRE(minClamped.size() == 8); |
| | } |
| |
|
| | SECTION("Generate Open Uniform Knot Vector") { |
| | auto openUniformKnots = LC_SplineHelper::generateOpenUniformKnotVector(4, 4); |
| | REQUIRE(openUniformKnots.size() == 8); |
| | REQUIRE(openUniformKnots[0] == Approx(0.0)); |
| | REQUIRE(openUniformKnots[1] == Approx(1.0)); |
| | REQUIRE(openUniformKnots[7] == Approx(7.0)); |
| | } |
| |
|
| | SECTION("Extend Knot Vector") { |
| | std::vector<double> knots = {0.0, 1.0, 3.0}; |
| | LC_SplineHelper::extendKnotVector(knots); |
| | REQUIRE(knots.size() == 4); |
| | REQUIRE(knots[3] > knots[2]); |
| | } |
| |
|
| | SECTION("Extend Knot Vector - Edge Case: Single Knot") { |
| | std::vector<double> singleKnot = {0.0}; |
| | LC_SplineHelper::extendKnotVector(singleKnot); |
| | REQUIRE(singleKnot.size() == 2); |
| | REQUIRE(singleKnot[1] == Approx(RS_TOLERANCE * 10)); |
| | } |
| |
|
| | SECTION("Insert Knot - Mid") { |
| | std::vector<double> knots = {0.0, 1.0, 2.0, 3.0}; |
| | LC_SplineHelper::insertKnot(knots, 2); |
| | REQUIRE(knots.size() == 5); |
| | REQUIRE(knots[2] == Approx(1.5)); |
| | } |
| |
|
| | SECTION("Insert Knot - Start") { |
| | std::vector<double> knots = {0.0, 1.0, 2.0}; |
| | LC_SplineHelper::insertKnot(knots, 0); |
| | REQUIRE(knots.size() == 4); |
| | REQUIRE(knots[0] == Approx(-1.0)); |
| | REQUIRE(knots[1] == Approx(0.0)); |
| | } |
| |
|
| | SECTION("Insert Knot - End") { |
| | std::vector<double> knots = {0.0, 1.0, 2.0}; |
| | LC_SplineHelper::insertKnot(knots, 3); |
| | REQUIRE(knots.size() == 4); |
| | REQUIRE(knots[3] == Approx(3.0)); |
| | } |
| |
|
| | SECTION("Insert Knot - Empty Vector") { |
| | std::vector<double> knots = {}; |
| | LC_SplineHelper::insertKnot(knots, 0); |
| | REQUIRE(knots.size() == 1); |
| | REQUIRE(knots[0] == Approx(0.0)); |
| | } |
| |
|
| | SECTION("Insert Knot - Small Difference") { |
| | std::vector<double> knots = {0.0, 1e-12, 1.0}; |
| | LC_SplineHelper::insertKnot(knots, 1); |
| | REQUIRE(knots.size() == 4); |
| | REQUIRE(knots[1] == Approx(1e-12 + 1e-10)); |
| | REQUIRE(knots[0] == Approx(0.0)); |
| | REQUIRE(knots[2] == Approx(1e-12)); |
| | REQUIRE(knots[3] == Approx(1.0)); |
| | } |
| |
|
| | SECTION("Insert Knot - Multiple at Same Position") { |
| | std::vector<double> knots = {0.0, 1.0, 2.0}; |
| | LC_SplineHelper::insertKnot(knots, 1); |
| | REQUIRE(knots[1] == Approx(0.5)); |
| | LC_SplineHelper::insertKnot(knots, 1); |
| | REQUIRE(knots[1] == Approx(0.25)); |
| | REQUIRE(knots[2] == Approx(0.5)); |
| | } |
| |
|
| | SECTION("Insert Knot - Beyond Size") { |
| | std::vector<double> knots = {0.0, 1.0}; |
| | LC_SplineHelper::insertKnot(knots, 5); |
| | REQUIRE(knots.size() == 3); |
| | REQUIRE(knots[2] == Approx(2.0)); |
| | } |
| |
|
| | SECTION("Insert Knot - Negative Knots") { |
| | std::vector<double> knots = {-2.0, -1.0, 0.0}; |
| | LC_SplineHelper::insertKnot(knots, 1); |
| | REQUIRE(knots.size() == 4); |
| | REQUIRE(knots[1] == Approx(-1.5)); |
| | } |
| |
|
| | SECTION("Remove Knot") { |
| | std::vector<double> knots = {0.0, 1.0, 2.0, 3.0}; |
| | LC_SplineHelper::removeKnot(knots, 1); |
| | REQUIRE(knots.size() == 3); |
| | REQUIRE(knots[1] == Approx(2.0)); |
| | } |
| |
|
| | SECTION("Remove Knot - Edge Case: Empty") { |
| | std::vector<double> knots = {}; |
| | LC_SplineHelper::removeKnot(knots, 0); |
| | REQUIRE(knots.empty()); |
| | } |
| |
|
| | SECTION("Remove Knot - Edge Case: Single Knot") { |
| | std::vector<double> knots = {0.0}; |
| | LC_SplineHelper::removeKnot(knots, 0); |
| | REQUIRE(knots.empty()); |
| | } |
| |
|
| | SECTION("Ensure Monotonic - With Duplicates") { |
| | std::vector<double> knots = {0.0, 1.0, 1.0, 2.0}; |
| | LC_SplineHelper::ensureMonotonic(knots); |
| | REQUIRE(knots[2] > knots[1]); |
| | REQUIRE(knots[2] == Approx(1.0 + RS_TOLERANCE * 10)); |
| | } |
| |
|
| | SECTION("Ensure Monotonic - Decreasing") { |
| | std::vector<double> knots = {0.0, 2.0, 1.0, 3.0}; |
| | LC_SplineHelper::ensureMonotonic(knots); |
| | REQUIRE(knots[2] > knots[1]); |
| | REQUIRE(knots[2] == Approx(2.0 + RS_TOLERANCE * 10)); |
| | } |
| |
|
| | SECTION("Ensure Monotonic - All Equal") { |
| | std::vector<double> knots = {1.0, 1.0, 1.0}; |
| | LC_SplineHelper::ensureMonotonic(knots); |
| | REQUIRE(knots[1] > knots[0]); |
| | REQUIRE(knots[2] > knots[1]); |
| | } |
| | } |
| |
|
| | TEST_CASE("RS_Spline Cubic Specific Tests", "[RS_Spline][degree3]") { |
| | RS_SplineData splineData(3, false); |
| | RS_Spline spline(nullptr, splineData); |
| |
|
| | } |
| |
|