diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e49822..afafa37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,10 @@ if (NOT DEFINED PROJECT_NAME) endif() project(CavalierContours VERSION 0.1) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) +enable_language(C) # Allow super-projects to override options if (POLICY CMP0077) @@ -18,6 +20,10 @@ endif () # Build options option(CAVC_HEADER_ONLY "C++ header only library of CavalierContours, if ON then no library is built" OFF) option(CAVC_BUILD_SHARED_LIB "Build the C API CavalierContours dynamic shared library (SET OFF for static library)" ON) +if (NOT_SUBPROJECT AND NOT CAVC_HEADER_ONLY) + include(GoogleTest) + enable_testing() +endif() if(CAVC_HEADER_ONLY) set(CAVC_CPP_HEADER_ONLY_LIB ${PROJECT_NAME}) @@ -67,7 +73,5 @@ if (NOT_SUBPROJECT AND NOT CAVC_HEADER_ONLY) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_subdirectory(examples) find_package(GTest REQUIRED) - include(GoogleTest) - enable_testing() add_subdirectory(tests) endif() diff --git a/include/cavc/polyline.hpp b/include/cavc/polyline.hpp index 3ccf10f..d0044e5 100644 --- a/include/cavc/polyline.hpp +++ b/include/cavc/polyline.hpp @@ -108,6 +108,11 @@ template AABB getExtents(Polyline const &pline) { Real endAngle = angle(arc.center, v2.pos()); Real sweepAngle = utils::deltaAngle(startAngle, endAngle); + // handle special case of half circle, or more than half circle + if ((sweepAngle > 0.0) && (v1.bulge() < 0.0)) { + sweepAngle = sweepAngle - 2.0 * utils::pi(); + } + Real arcXMin, arcYMin, arcXMax, arcYMax; // crosses PI/2 diff --git a/tests/tests/CMakeLists.txt b/tests/tests/CMakeLists.txt index 4b2e298..b2b02e2 100644 --- a/tests/tests/CMakeLists.txt +++ b/tests/tests/CMakeLists.txt @@ -26,6 +26,7 @@ macro(cavc_add_test name) gtest_add_tests(TARGET ${name} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) endmacro() +cavc_add_test(TEST_sample) cavc_add_test(TEST_cavc_pline) cavc_add_test(TEST_cavc_pline_function) cavc_add_test(TEST_cavc_parallel_offset) diff --git a/tests/tests/TEST_cavc_combine_plines.cpp b/tests/tests/TEST_cavc_combine_plines.cpp index 7355afc..a161a85 100644 --- a/tests/tests/TEST_cavc_combine_plines.cpp +++ b/tests/tests/TEST_cavc_combine_plines.cpp @@ -52,38 +52,46 @@ static std::vector createSimpleCases() { std::vector plineBVertexes = {{3, -10, 0}, {6, -10, 0}, {6, 10, 0}, {3, 10, 0}}; cavc_pline *plineA = plineFromVertexes(plineAVertexes, true); cavc_pline *plineB = plineFromVertexes(plineBVertexes, true); - // Union std::vector expectedRemaining; - expectedRemaining.emplace_back(10, 109.15381629282, 52.324068506275, 0, -10, 10, 10); std::vector expectedSubtracted; + + // Union + expectedRemaining.emplace_back(10, 109.15381629282, 52.324068506275, 0, -10, 10, 10); cases.emplace_back("circle_rectangle_union", 0, plineA, plineB, expectedRemaining, expectedSubtracted); - - // Exclude expectedRemaining.clear(); expectedSubtracted.clear(); - expectedRemaining.emplace_back(3, 19.816835628274, 20.757946197186, 0, -4, 3, 5.5825756949558); - expectedRemaining.emplace_back(3, 29.336980664548, 23.492343031178, 6, -3.8989794855664, 10, 6); + + // Exclude + expectedRemaining.emplace_back(3, 29.336980664548, 23.492343031178, 6, -3.8989794855664, 10, + 5.898979485566356); + expectedRemaining.emplace_back(3, 19.816835628274, 20.757946197186, 0, -3.582575694955841, 3, + 5.5825756949558); cases.emplace_back("circle_rectangle_exclude", 1, plineA, plineB, expectedRemaining, expectedSubtracted); - - // Intersect expectedRemaining.clear(); expectedSubtracted.clear(); + + // Intersect expectedRemaining.emplace_back(4, 29.386000046924, 25.091858029623, 3, -4, 6, 6); cases.emplace_back("circle_rectangle_intersect", 2, plineA, plineB, expectedRemaining, expectedSubtracted); + expectedRemaining.clear(); + expectedSubtracted.clear(); // XOR - expectedRemaining.clear(); - expectedRemaining.emplace_back(3, 19.816835628274, 20.757946197186, 0, -4, 3, 5.5825756949558); + + expectedRemaining.emplace_back(3, 19.816835628274, 20.757946197186, 0, -3.582575694955841, 3, + 5.5825756949558); expectedRemaining.emplace_back(4, -18.306999976538, 18.582818653767, 3, -10, 6, -3.5825756949558); - expectedRemaining.emplace_back(3, 29.336980664548, 23.492343031178, 6, -3.8989794855664, 10, 6); + expectedRemaining.emplace_back(3, 29.336980664548, 23.492343031178, 6, -3.8989794855664, 10, + 5.898979485566356); expectedRemaining.emplace_back(4, -12.306999976538, 14.582818653767, 3, 5.5825756949558, 6, 10); - expectedSubtracted.clear(); cases.emplace_back("circle_rectangle_xor", 3, plineA, plineB, expectedRemaining, expectedSubtracted); + expectedRemaining.clear(); + expectedSubtracted.clear(); } return cases; @@ -101,6 +109,7 @@ static std::vector createCoincidentCases() { {0.25, 0.295, -0.414214}, {0.255, 0.29, 0}, {0.255, 0.24, -0.414214}, {0.25, 0.235, 0}}; cavc_pline *plineA = plineFromVertexes(plineAVertexes, true); cavc_pline *plineB = plineFromVertexes(plineBVertexes, true); + // Union std::vector expectedRemaining; expectedRemaining.emplace_back(12, -0.032967809756574, 1.6071238962168, -0.255, -0.005, 0.255, @@ -108,35 +117,36 @@ static std::vector createCoincidentCases() { std::vector expectedSubtracted; cases.emplace_back("coincident_case1_union", 0, plineA, plineB, expectedRemaining, expectedSubtracted); - - // Exclude expectedRemaining.clear(); expectedSubtracted.clear(); + + // Exclude A from B expectedRemaining.emplace_back(4, -0.0023892699081699, 0.49570796326795, -0.105, -0.005, -0.095, 0.235); cases.emplace_back("coincident_case1_excludeAFromB", 1, plineA, plineB, expectedRemaining, expectedSubtracted); - expectedRemaining.clear(); expectedSubtracted.clear(); + + // Exclude B from A expectedRemaining.emplace_back(10, -0.030578539848405, 1.1314159329489, -0.255, 0.235, 0.255, 0.295); cases.emplace_back("coincident_case1_excludeBFromA", 1, plineB, plineA, expectedRemaining, expectedSubtracted); - - // Intersect expectedRemaining.clear(); expectedSubtracted.clear(); + + // Intersect cases.emplace_back("coincident_case1_intersect", 2, plineA, plineB, expectedRemaining, expectedSubtracted); + expectedRemaining.clear(); + expectedSubtracted.clear(); // XOR - expectedRemaining.clear(); expectedRemaining.emplace_back(4, -0.0023892699081699, 0.49570796326795, -0.105, -0.005, -0.095, 0.235); expectedRemaining.emplace_back(10, 0.030578539848405, 1.1314159329489, -0.255, 0.235, 0.255, 0.295); - expectedSubtracted.clear(); cases.emplace_back("coincident_case1_xor", 3, plineA, plineB, expectedRemaining, expectedSubtracted); } @@ -202,7 +212,7 @@ class cavc_combine_plinesTests : public t::TestWithParam INSTANTIATE_TEST_SUITE_P(simple_cases, cavc_combine_plinesTests, t::ValuesIn(simpleCases)); INSTANTIATE_TEST_SUITE_P(coincident_cases, cavc_combine_plinesTests, t::ValuesIn(coincidentCases)); -TEST_P(cavc_combine_plinesTests, DISABLED_combine_plines_test) { +TEST_P(cavc_combine_plinesTests, combine_plines_test) { CombinePlinesTestCase const &testCase = GetParam(); cavc_pline_list *remaining = nullptr; cavc_pline_list *subtracted = nullptr; @@ -218,6 +228,7 @@ TEST_P(cavc_combine_plinesTests, DISABLED_combine_plines_test) { cavc_pline *pline = cavc_pline_list_get(remaining, i); remainingProperties.emplace_back(pline); } + ASSERT_THAT(remainingProperties, t::UnorderedPointwise(EqIgnoreSignOfArea(), testCase.expectedRemaining)); diff --git a/tests/tests/TEST_cavc_pline_function.cpp b/tests/tests/TEST_cavc_pline_function.cpp index 422de81..d1b24ee 100644 --- a/tests/tests/TEST_cavc_pline_function.cpp +++ b/tests/tests/TEST_cavc_pline_function.cpp @@ -590,7 +590,7 @@ TEST_P(cavc_plineFunctionTests, cavc_get_winding_number) { ASSERT_THAT(windingNumberResults, t::Pointwise(t::Eq(), testCase.windingNumberResults)); } -TEST_P(cavc_plineFunctionTests, DISABLED_cavc_get_extents) { +TEST_P(cavc_plineFunctionTests, cavc_get_extents) { cavc_plineFunctionsTestCase const &testCase = GetParam(); if (testCase.skipExtentsTest()) { GTEST_SKIP(); @@ -692,7 +692,7 @@ TEST_P(cavc_plineFunctionTests, cavc_parallel_offset) { } } -TEST_P(cavc_plineFunctionTests, DISABLED_combine_with_self_invariants) { +TEST_P(cavc_plineFunctionTests, combine_with_self_invariants) { cavc_plineFunctionsTestCase const &testCase = GetParam(); if (!testCase.isClosed()) { GTEST_SKIP(); diff --git a/tests/tests/TEST_sample.cpp b/tests/tests/TEST_sample.cpp new file mode 100644 index 0000000..c0a4f8e --- /dev/null +++ b/tests/tests/TEST_sample.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include + +#include "c_api_include/cavaliercontours.h" +#include "c_api_test_helpers.hpp" +#include "cavc/polyline.hpp" + +// Use basic gtest rather than gmock to make debug easier +TEST(basic, basic_extent) { + cavc_real minX; + cavc_real minY; + cavc_real maxX; + cavc_real maxY; + // Quad Circle + std::vector plineAVertexes = {{1.0, 0.00, -0.4142135623730951}, {0.0, -1.0, 0.00}}; + cavc_pline *plineA = plineFromVertexes(plineAVertexes, true); + cavc_get_extents(plineA, &minX, &minY, &maxX, &maxY); + EXPECT_NEAR(minX, 0, 1e-15); + EXPECT_NEAR(minY, -1, 1e-15); + EXPECT_NEAR(maxX, 1, 1e-15); + EXPECT_NEAR(maxY, 0, 1e-15); + + // Half circle cw + std::vector plineBVertexes = {{1.0, 0.00, -1.00}, {0.0, 0.00, 0.00}}; + cavc_pline *plineB1 = plineFromVertexes(plineBVertexes, true); + cavc_get_extents(plineB1, &minX, &minY, &maxX, &maxY); + EXPECT_NEAR(minX, 0, 1e-15); + EXPECT_NEAR(minY, -0.5, 1e-15); + EXPECT_NEAR(maxX, 1, 1e-15); + EXPECT_NEAR(maxY, 0, 1e-15); + + // Half circle ccw + plineBVertexes.front().bulge = 1; + cavc_pline *plineB2 = plineFromVertexes(plineBVertexes, true); + cavc_get_extents(plineB2, &minX, &minY, &maxX, &maxY); + EXPECT_NEAR(minX, 0, 1e-15); + EXPECT_NEAR(minY, 0, 1e-15); + EXPECT_NEAR(maxX, 1, 1e-15); + EXPECT_NEAR(maxY, 0.5, 1e-15); + + // Half circle ccw + std::vector plineCVertexes = { + {0.0, 0.00, 1.00}, + {0.0, 1.00, 0.00}, + }; + cavc_pline *plineC1 = plineFromVertexes(plineCVertexes, true); + cavc_get_extents(plineC1, &minX, &minY, &maxX, &maxY); + EXPECT_NEAR(minX, 0, 1e-15); + EXPECT_NEAR(minY, 0, 1e-15); + EXPECT_NEAR(maxX, 0.5, 1e-15); + EXPECT_NEAR(maxY, 1, 1e-15); + + // Half circle cw + plineCVertexes.front().bulge = -1; + cavc_pline *plineC2 = plineFromVertexes(plineCVertexes, true); + cavc_get_extents(plineC2, &minX, &minY, &maxX, &maxY); + EXPECT_NEAR(minX, -0.5, 1e-15); + EXPECT_NEAR(minY, 0, 1e-15); + EXPECT_NEAR(maxX, 0, 1e-15); + EXPECT_NEAR(maxY, 1, 1e-15); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/tests/testhelpers.hpp b/tests/tests/testhelpers.hpp index 98c1fbc..b29a950 100644 --- a/tests/tests/testhelpers.hpp +++ b/tests/tests/testhelpers.hpp @@ -63,4 +63,31 @@ inline std::ostream &operator<<(std::ostream &os, PolylineProperties const &p) { << ", maxX: " << p.maxX << ", maxY: " << p.maxY << " }"; return os; } + +// Custom function to print differences +void PrintDiff(const PolylineProperties &expected, const PolylineProperties &actual) { + if (expected.vertexCount != actual.vertexCount) { + std::cout << "vertexCount: expected " << expected.vertexCount << ", actual " + << actual.vertexCount << "\n"; + } + if (!fuzzyEqual(expected.area, actual.area)) { + std::cout << "area: expected " << expected.area << ", actual " << actual.area << "\n"; + } + if (!fuzzyEqual(expected.pathLength, actual.pathLength)) { + std::cout << "pathLength: expected " << expected.pathLength << ", actual " << actual.pathLength + << "\n"; + } + if (!fuzzyEqual(expected.minX, actual.minX)) { + std::cout << "minX: expected " << expected.minX << ", actual " << actual.minX << "\n"; + } + if (!fuzzyEqual(expected.minY, actual.minY)) { + std::cout << "minY: expected " << expected.minY << ", actual " << actual.minY << "\n"; + } + if (!fuzzyEqual(expected.maxX, actual.maxX)) { + std::cout << "maxX: expected " << expected.maxX << ", actual " << actual.maxX << "\n"; + } + if (!fuzzyEqual(expected.maxY, actual.maxY)) { + std::cout << "maxY: expected " << expected.maxY << ", actual " << actual.maxY << "\n"; + } +} #endif // CAVC_TESTHELPERS_HPP