├── images
└── title.png
├── .gitignore
├── testcases
├── geometry
│ ├── lines_inner_contour
│ │ └── in.svg
│ ├── lines03
│ │ └── in.svg
│ ├── lines02
│ │ └── in.svg
│ ├── lines_convex02
│ │ └── in.svg
│ ├── lines_overlapping_top
│ │ └── in.svg
│ ├── lines_overlapping_bottom
│ │ └── in.svg
│ ├── curves01
│ │ └── in.svg
│ ├── lines05
│ │ └── in.svg
│ ├── lines_overlapping_left
│ │ └── in.svg
│ ├── lines_overlapping_right
│ │ └── in.svg
│ ├── curves_special01
│ │ └── in.svg
│ ├── curves_special02
│ │ └── in.svg
│ ├── curves02
│ │ └── in.svg
│ ├── lines_selfintersecting
│ │ └── in.svg
│ ├── lines_overlapping
│ │ └── in.svg
│ ├── lines04
│ │ └── in.svg
│ ├── curves04
│ │ └── in.svg
│ ├── lines01
│ │ └── in.svg
│ ├── lines_overlapping_edge
│ │ └── in.svg
│ ├── lines_overlapping_point
│ │ └── in.svg
│ ├── lines_almost_overlapping
│ │ └── in.svg
│ ├── lines_convex01
│ │ └── in.svg
│ ├── curves_square_circle02
│ │ └── in.svg
│ ├── curves_square_circle04
│ │ └── in.svg
│ ├── curves_square_circle01
│ │ └── in.svg
│ ├── curves_square_circle03
│ │ └── in.svg
│ ├── curves_circles01
│ │ └── in.svg
│ ├── curves_circles02
│ │ └── in.svg
│ ├── curves06
│ │ └── in.svg
│ ├── curves_curved_rectangles01
│ │ └── in.svg
│ ├── curves_curved_rectangles02
│ │ └── in.svg
│ ├── curves_nontrivial03
│ │ └── in.svg
│ ├── curves03
│ │ └── in.svg
│ ├── curves_nontrivial02
│ │ └── in.svg
│ ├── curves05
│ │ └── in.svg
│ ├── curves_nontrivial01
│ │ └── in.svg
│ └── curves_nontrivial04
│ │ └── in.svg
└── various
│ └── bezier_ordering
│ └── in.svg
├── CMakeLists.txt
├── examples
├── example1.cpp
└── example2.cpp
├── scripts
├── update_testfile.py
├── add_testcase.py
└── single_header.py
├── THIRDPARTY.txt
├── test
├── acceptance.cpp
├── geometry_generation.hpp
├── svg_testcases.cpp
├── test_utilities.hpp
└── unit.cpp
├── include
├── contour_postprocessing.hpp
├── direct_solvers.hpp
├── polynomial_solver.hpp
├── sweep_point.hpp
├── svg_io.hpp
└── geometry_base.hpp
└── README.md
/images/title.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/verven/contourklip/HEAD/images/title.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # CLion work directory
2 | .idea/
3 |
4 | # CLion build directories
5 | cmake-build-*/
6 |
7 | # Exclude MacOS Finder files.
8 | .DS_Store
9 |
10 | # Don't keep generated svgs.
11 | *.svg
12 | !in.svg
--------------------------------------------------------------------------------
/testcases/geometry/lines_inner_contour/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines03/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines02/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_convex02/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_overlapping_top/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_overlapping_bottom/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves01/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines05/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_overlapping_left/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_overlapping_right/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_special01/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_special02/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves02/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_selfintersecting/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_overlapping/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines04/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves04/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines01/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_overlapping_edge/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_overlapping_point/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_almost_overlapping/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/lines_convex01/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_square_circle02/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_square_circle04/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_square_circle01/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_square_circle03/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_circles01/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_circles02/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.20)
2 | project(contourklip)
3 |
4 | set(CMAKE_CXX_STANDARD 20)
5 | set(BINARY ${CMAKE_PROJECT_NAME})
6 |
7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer")
8 |
9 | include_directories(include)
10 | include_directories(single_include)
11 | include_directories(test/thirdparty)
12 |
13 | add_executable(${BINARY}_unittst test/unit.cpp
14 | test/test_utilities.hpp
15 | # ${include_files}
16 | include/geometry_base.hpp
17 | include/bezier_utils.hpp
18 | include/svg_io.hpp
19 | include/sweep_point.hpp
20 | include/polyclip.hpp
21 | )
22 | add_executable(${BINARY}_acceptancetst test/acceptance.cpp)
23 | add_executable(${BINARY}_test_svg test/svg_testcases.cpp)
24 | add_executable(${BINARY}_example examples/example1.cpp)
25 | add_executable(${BINARY}_example2 examples/example2.cpp)
--------------------------------------------------------------------------------
/testcases/geometry/curves06/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_curved_rectangles01/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_curved_rectangles02/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_nontrivial03/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves03/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_nontrivial02/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/example1.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "contourklip.hpp"
3 |
4 | int main() {
5 | contourklip::Contour contour1{{0, 100}};
6 | contour1.push_back({50, 100});
7 | contour1.push_back({77.5, 100}, {100, 77.5}, {100, 50});
8 | contour1.push_back({100, 22.5}, {77.5, 0}, {50, 0});
9 | contour1.push_back({0, 0});
10 | contour1.close();
11 |
12 | contourklip::Contour contour2{{150, 25}};
13 | contour2.push_back({100, 25});
14 | contour2.push_back({72.3, 25}, {50, 47.3}, {50, 75});
15 | contour2.push_back({50, 102.5}, {72.3, 125}, {100, 125});
16 | contour2.push_back({150, 125});
17 | contour2.close();
18 |
19 | std::vector shape1{contour1};
20 | std::vector shape2{contour2};
21 | std::vector result{};
22 |
23 | if(contourklip::clip(shape1, shape2, result, contourklip::INTERSECTION)){
24 | std::cout << "clipping operation succeeded\n";
25 | }
26 |
27 | for (const auto &contour: result) {
28 | std::cout << "contour:\n";
29 | std::cout << contour;
30 | }
31 | return 0;
32 | }
--------------------------------------------------------------------------------
/scripts/update_testfile.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 |
4 | '''
5 | generates the file svg_testcases.hpp.
6 | '''
7 |
8 | TESTCASES_SVG_DIR = "testcases/geometry"
9 | TESTCASE_NAME = "test/svg_testcases.cpp"
10 |
11 | def construct_tst_case(name):
12 |
13 | return f'''TEST_CASE("{name}"){{\n do_test("{name}");\n}}\n\n'''
14 |
15 | def construct_tst_file():
16 | subfolders = [Path(f.path).name for f in os.scandir(TESTCASES_SVG_DIR) if f.is_dir() ]
17 | subfolders.sort(reverse=True)
18 |
19 | cases_str= ''.join([construct_tst_case(i) for i in subfolders])
20 |
21 | includes = '''#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN\n#include "test_utilities.hpp"\n#include "doctest.h"\n\n'''
22 |
23 | tst_start = 'TEST_SUITE_BEGIN("svg geometry testcases");\n\n'
24 | comment = "//This file was auto-generated from `scripts/update_testfile.py`.\n\n"
25 |
26 | tst_end = "TEST_SUITE_END;\n"
27 |
28 | return comment + includes + tst_start+ cases_str + tst_end
29 |
30 |
31 | write_p = Path(TESTCASE_NAME)
32 | with write_p.open("w", encoding="utf-8") as f:
33 | f.write(construct_tst_file())
34 |
35 |
--------------------------------------------------------------------------------
/testcases/geometry/curves05/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/testcases/geometry/curves_nontrivial01/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/THIRDPARTY.txt:
--------------------------------------------------------------------------------
1 | This project includes the following subcomponents that are subject to separate license terms.
2 |
3 | doctest
4 | https://github.com/doctest/doctest
5 | License: MIT
6 | the notice below can be obtained at https://github.com/doctest/doctest/blob/master/LICENSE.txt
7 |
8 | The MIT License (MIT)
9 |
10 | Copyright (c) 2016-2021 Viktor Kirilov
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
--------------------------------------------------------------------------------
/test/acceptance.cpp:
--------------------------------------------------------------------------------
1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
2 | #include "doctest.h"
3 | #include "polyclip.hpp"
4 | #include "test_utilities.hpp"
5 |
6 | TEST_CASE("tiny contour fuzzy only lines"){
7 | for (int seed = 1; seed < 10000; ++seed) {
8 | contourklip::Contour c1, c2;
9 | geometrygen::generate_contour(10, 2., 8, 1.5, seed, c1, {}, false);
10 | geometrygen::generate_contour(10, 2., 8, 1.5, seed + 1, c2, {0.25, 1}, false);
11 | test_ops(std::vector{c1}, std::vector{c2}, true);
12 | }
13 | }
14 |
15 | TEST_CASE("tiny contour fuzzy tests"){
16 | int k = 100;
17 | for (int seed = 1; seed < k; ++seed) {
18 | for (int seed2 = seed+1; seed2 < k; ++seed2) {
19 | contourklip::Contour c1, c2;
20 | geometrygen::generate_contour(5, 2., 8, 1.5, seed, c1, {}, true);
21 | geometrygen::generate_contour(5, 2., 8, 1.5, seed2, c2, {0.25, 1}, true);
22 | test_ops(std::vector{c1}, std::vector{c2}, true);
23 | }
24 | }
25 | }
26 |
27 | TEST_CASE("disallowed contour"){
28 | contourklip::Contour c1{};
29 | c1.push_back({0, 1});
30 | c1.push_back({0, 2});
31 | c1.push_back({-1, 2});
32 | c1.push_back({2, 2});
33 | c1.close();
34 |
35 | contourklip::Contour c2{};
36 | geometrygen::generate_rectangle({-5, 5}, {1, -2}, c2);
37 | std::vector out{};
38 | bool res = contourklip::clip(c2, c1, out, contourklip::DIFFERENCE);
39 |
40 | CHECK( ! res);
41 | }
42 |
43 | TEST_CASE("disallowed contour 2"){
44 | contourklip::Contour c1{};
45 | c1.push_back({0, 0});
46 | c1.push_back({0, 1});
47 | c1.push_back({2, 2}, {0, 2}, {2, 1});
48 | c1.push_back({2, 0});
49 | c1.close();
50 | std::vector out{};
51 | bool res = contourklip::clip(c1, {}, out, contourklip::DIFFERENCE);
52 |
53 | CHECK( ! res);
54 | }
--------------------------------------------------------------------------------
/testcases/various/bezier_ordering/in.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/example2.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "contourklip.hpp"
3 |
4 | int main() {
5 | contourklip::Contour contour1{{0, 100}};
6 | contour1.push_back({50, 100});
7 | contour1.push_back({77.5, 100}, {100, 77.5}, {100, 50});
8 | contour1.push_back({100, 22.5}, {77.5, 0}, {50, 0});
9 | contour1.push_back({0, 0});
10 | contour1.close();
11 | contourklip::Contour contour2{{150, 25}};
12 | contour2.push_back({100, 25});
13 | contour2.push_back({72.3, 25}, {50, 47.3}, {50, 75});
14 | contour2.push_back({50, 102.5}, {72.3, 125}, {100, 125});
15 | contour2.push_back({150, 125});
16 | contour2.close();
17 | std::vector shape1{contour1};
18 | std::vector shape2{contour2};
19 | std::vector result{};
20 |
21 | //callback for determining if a is on the left of the segment p0, p1.
22 | // here it just calls the default callback.
23 | auto above =
24 | [](const contourklip::Point2d &p0,
25 | const contourklip::Point2d &p1, const contourklip::Point2d &a) -> bool{
26 | return contourklip::detail::LeftOfLine{}(p0, p1, a);
27 | };
28 |
29 | // callback for determining if 3 points are collinear.
30 | // Again this just calls the default implementation.
31 | auto collinear =
32 | [](const contourklip::Point2d &p0,
33 | const contourklip::Point2d &p1, const contourklip::Point2d &p2) -> bool{
34 | return contourklip::detail::IsCollinear{}(p0, p1, p2);
35 | };
36 |
37 | contourklip::Config c;
38 | c.postprocess = false;
39 |
40 | contourklip::PolyClip clip{shape1, shape2, result,
41 | contourklip::INTERSECTION, c};
42 |
43 | clip.compute();
44 |
45 | if(clip.success()){
46 | std::cout << "clipping operation succeeded\n";
47 | }
48 | return 0;
49 | }
--------------------------------------------------------------------------------
/scripts/add_testcase.py:
--------------------------------------------------------------------------------
1 | import re
2 | import pathlib
3 | import argparse
4 |
5 | def svg_prefix(viewBox):
6 |
7 | return f'\n"
11 |
12 |
13 |
14 | def to_testcase_svg(svg_str):
15 | svg_str = svg_str.strip()
16 | path_re = ""
17 | path_re= re.compile(path_re)
18 | viewbox_re = r"svg(?:.*?)viewBox=\"(.*?)\"(?:.*?)"
19 | viewbox_re = re.compile(viewbox_re)
20 |
21 | viewbox_vals = re.search(viewbox_re, svg_str)
22 | if not viewbox_vals:
23 | print("couldn't infer svg dimensions")
24 | exit(1)
25 | t= viewbox_vals.group(1).strip().split()[-2:]
26 | height, width = int(t[0]), int(t[1])
27 |
28 | out= svg_prefix(viewbox_vals.group(1))
29 |
30 | for idx, p in enumerate(re.finditer(path_re, svg_str)):
31 | print(p.group(1), "\n\n")
32 | out += f'\n'
33 | # if idx ==1:
34 | # break
35 |
36 | return out +svg_suffix()
37 |
38 |
39 |
40 | if __name__ == "__main__":
41 |
42 | parser = argparse.ArgumentParser(description='Process an svg file so that it creates a testcase svg file.')
43 | parser.add_argument('filepath', metavar='file', type=str, help='the filepath to the svg file')
44 | args = parser.parse_args()
45 | p= args.filepath
46 |
47 | if not p.endswith(".svg"):
48 | print("the file is not an .svg file")
49 | exit(1)
50 | out_dir = pathlib.Path(p[:-4])
51 | out_dir.mkdir(parents=True, exist_ok=True)
52 | write_p = out_dir / "in.svg"
53 | with open(p) as file_in:
54 | svg_str = file_in.read()
55 | with write_p.open("w", encoding="utf-8") as f:
56 | f.write(to_testcase_svg(svg_str))
57 |
--------------------------------------------------------------------------------
/include/contour_postprocessing.hpp:
--------------------------------------------------------------------------------
1 | #ifndef CONTOURKLIP_CONTOUR_POSTPROCESSING_HPP
2 | #define CONTOURKLIP_CONTOUR_POSTPROCESSING_HPP
3 |
4 | #include