diff --git a/.pkg b/.pkg index a1d9ade..77e3410 100644 --- a/.pkg +++ b/.pkg @@ -6,6 +6,10 @@ url=git@github.com:motis-project/clipper.git branch=master commit=2abf72015b85f1cfd38706b21d4f28b8c359c7d5 +[Clipper2] + url=git@github.com:motis-project/Clipper2.git + branch=main + commit=f9c5eb6e14a59f6f5d65fbfb3564519a561cf4fd [concurrentqueue] url=git@github.com:motis-project/concurrentqueue.git branch=master diff --git a/CMakeLists.txt b/CMakeLists.txt index a1a66d6..e5543d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,13 @@ create_resource("${CMAKE_CURRENT_SOURCE_DIR}/profile/" "${tiles-profiles-res}" t include(cmake/clang-tidy.cmake) +# Clipper2 (fast polygon / rectangle clipping). Wired explicitly here because +# the motis pkg tool does not (yet) emit an add_subdirectory() for it. +if (NOT TARGET Clipper2) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../Clipper2 + ${CMAKE_CURRENT_BINARY_DIR}/Clipper2 EXCLUDE_FROM_ALL) +endif() + # --- tiles lib file(GLOB_RECURSE tiles-files src/db/*.cc @@ -39,7 +46,7 @@ target_compile_options(tiles PRIVATE ${TILES_WARNINGS}) target_include_directories(tiles PUBLIC include) target_link_libraries(tiles boost - clipper + Clipper2 fmt geo lmdb @@ -94,6 +101,12 @@ target_compile_options(tiles-benchmark PRIVATE ${TILES_WARNINGS}) target_include_directories(tiles-benchmark PUBLIC include) target_link_libraries(tiles-benchmark boost tiles lmdb geo) +# --- tier-index experiment exe +add_executable(tiles-index-experiment EXCLUDE_FROM_ALL src/index_experiment.cc) +set_property(TARGET tiles-index-experiment PROPERTY CXX_STANDARD 23) +target_include_directories(tiles-index-experiment PUBLIC include) +target_link_libraries(tiles-index-experiment boost tiles lmdb geo) + # --- test exe file(GLOB_RECURSE tiles-test-files test/*_test.cc diff --git a/src/fixed/algo/clip.cc b/src/fixed/algo/clip.cc index 6d44535..5ba8867 100644 --- a/src/fixed/algo/clip.cc +++ b/src/fixed/algo/clip.cc @@ -1,21 +1,30 @@ #include "tiles/fixed/algo/clip.h" +#include +#include +#include + #include "boost/geometry.hpp" -#include "clipper/clipper.hpp" +#include "clipper2/clipper.h" #include "utl/erase_if.h" #include "utl/verify.h" #include "tiles/util.h" -namespace cl = ClipperLib; +namespace c2 = Clipper2Lib; namespace tiles { -fixed_geometry clip(fixed_null const&, fixed_box const&) { - return fixed_null{}; -} +// Polygon-clip backend: +// 1 = Clipper2 RectClip64 (specialized, fast rectangle clipping) +// 0 = Clipper2 Clipper64 + PolyTree64 (general boolean, faithful to v1) +#ifndef TILES_CLIP_RECTCLIP +#define TILES_CLIP_RECTCLIP 1 +#endif + +fixed_geometry clip(fixed_null const&, fixed_box const&) { return fixed_null{}; } fixed_geometry clip(fixed_point const& in, fixed_box const& box) { fixed_point out; @@ -45,69 +54,163 @@ fixed_geometry clip(fixed_polyline const& in, fixed_box const& box) { } } -void to_fixed_polygon2(fixed_polygon& polygon, cl::PolyNodes const& nodes) { - auto const path_to_ring = [](auto const& path) { - utl::verify(!path.empty(), "path empty"); - fixed_ring ring; - ring.reserve(path.size() + 1); - for (auto const& pt : path) { - ring.emplace_back(pt.X, pt.Y); - } - ring.emplace_back(path[0].X, path[0].Y); - return ring; - }; - - for (auto const* outer : nodes) { - utl::verify(!outer->IsHole(), "outer ring is hole"); - fixed_simple_polygon simple; - simple.outer() = path_to_ring(outer->Contour); +namespace { - for (auto const* inner : outer->Childs) { - utl::verify(inner->IsHole(), "inner ring is no hole"); - simple.inners().emplace_back(path_to_ring(inner->Contour)); - - to_fixed_polygon2(polygon, inner->Childs); - } - - polygon.emplace_back(std::move(simple)); +fixed_ring to_ring(c2::Path64 const& path) { + fixed_ring ring; + if (path.empty()) { + return ring; + } + ring.reserve(path.size() + 1); + for (auto const& pt : path) { + ring.emplace_back(pt.x, pt.y); } + ring.emplace_back(path.front().x, path.front().y); // close ring + return ring; } -fixed_geometry clip(fixed_polygon const& in, fixed_box const& box) { - auto const clip = cl::Path{{box.min_corner().x(), box.min_corner().y()}, - {box.max_corner().x(), box.min_corner().y()}, - {box.max_corner().x(), box.max_corner().y()}, - {box.min_corner().x(), box.max_corner().y()}}; - cl::Paths subject; +c2::Paths64 to_subject(fixed_polygon const& in) { + c2::Paths64 subject; for (auto const& poly : in) { subject.emplace_back(); - for (auto const& pt : poly.outer()) { subject.back().emplace_back(pt.x(), pt.y()); } - subject.back().pop_back(); + if (!subject.back().empty()) { + subject.back().pop_back(); // Clipper2 paths are implicitly closed + } for (auto const& inner : poly.inners()) { subject.emplace_back(); for (auto const& pt : inner) { subject.back().emplace_back(pt.x(), pt.y()); } - subject.back().pop_back(); + if (!subject.back().empty()) { + subject.back().pop_back(); + } } } + return subject; +} - cl::Clipper clpr; - utl::verify(clpr.AddPaths(subject, cl::ptSubject, true), "AddPath1 failed"); - utl::verify(clpr.AddPath(clip, cl::ptClip, true), "AddPath2 failed"); +#if TILES_CLIP_RECTCLIP +// Rebuild the boost multi-polygon hierarchy from the flat ring set returned by +// RectClip. Ring role is determined by containment depth (even depth = outer +// ring, odd depth = hole of its immediate parent). This is orientation +// agnostic; boost::geometry::correct() normalizes winding afterwards. +void to_fixed_polygon(fixed_polygon& out, c2::Paths64 const& paths) { + auto const n = paths.size(); + + std::vector area(n, 0.0); + for (auto i = 0U; i < n; ++i) { + area[i] = std::abs(c2::Area(paths[i])); + } - cl::PolyTree solution; - clpr.Execute(cl::ctIntersection, solution, cl::pftEvenOdd, cl::pftEvenOdd); - if (solution.Childs.empty()) { - return fixed_null{}; + // parent[i] = index of the smallest-area ring strictly containing ring i. + std::vector parent(n, -1); + for (auto i = 0U; i < n; ++i) { + if (paths[i].empty()) { + continue; + } + auto const pt = paths[i].front(); + auto best = std::numeric_limits::max(); + for (auto j = 0U; j < n; ++j) { + if (i == j || paths[j].size() < 3 || area[j] <= area[i]) { + continue; + } + if (c2::PointInPolygon(pt, paths[j]) != + c2::PointInPolygonResult::IsInside) { + continue; + } + if (area[j] < best) { + best = area[j]; + parent[i] = static_cast(j); + } + } } + auto const depth = [&](std::size_t i) { + auto d = 0; + for (auto p = parent[i]; p != -1; p = parent[static_cast(p)]) { + ++d; + } + return d; + }; + + std::vector out_idx(n, -1); + for (auto i = 0U; i < n; ++i) { // outer rings (even depth) + if (paths[i].size() < 3 || (depth(i) & 1) != 0) { + continue; + } + fixed_simple_polygon simple; + simple.outer() = to_ring(paths[i]); + out_idx[i] = static_cast(out.size()); + out.emplace_back(std::move(simple)); + } + for (auto i = 0U; i < n; ++i) { // holes (odd depth) -> attach to parent outer + if (paths[i].size() < 3 || (depth(i) & 1) != 1) { + continue; + } + auto const p = parent[i]; + if (p >= 0 && out_idx[static_cast(p)] >= 0) { + out[static_cast(out_idx[static_cast(p)])] + .inners() + .emplace_back(to_ring(paths[i])); + } + } +} +#else +void to_fixed_polygon(fixed_polygon& polygon, c2::PolyPath64 const& parent) { + for (auto const& child : parent) { // top-level children are outer rings + auto const& outer = *child; + fixed_simple_polygon simple; + simple.outer() = to_ring(outer.Polygon()); + + for (auto const& grand_child : outer) { // children of an outer are holes + auto const& inner = *grand_child; + simple.inners().emplace_back(to_ring(inner.Polygon())); + to_fixed_polygon(polygon, inner); // islands nested inside holes + } + + polygon.emplace_back(std::move(simple)); + } +} +#endif + +} // namespace + +fixed_geometry clip(fixed_polygon const& in, fixed_box const& box) { + auto const subject = to_subject(in); + + auto const rect = c2::Rect64{box.min_corner().x(), box.min_corner().y(), + box.max_corner().x(), box.max_corner().y()}; + fixed_polygon out; - to_fixed_polygon2(out, solution.Childs); + +#if TILES_CLIP_RECTCLIP + auto const solution = c2::RectClip(rect, subject); + if (solution.empty()) { + return fixed_null{}; + } + to_fixed_polygon(out, solution); +#else + c2::Clipper64 clpr; + clpr.AddSubject(subject); + clpr.AddClip(c2::Paths64{c2::Path64{{rect.left, rect.top}, + {rect.right, rect.top}, + {rect.right, rect.bottom}, + {rect.left, rect.bottom}}}); + c2::PolyTree64 solution; + clpr.Execute(c2::ClipType::Intersection, c2::FillRule::EvenOdd, solution); + if (solution.Count() == 0) { + return fixed_null{}; + } + to_fixed_polygon(out, solution); +#endif + + if (out.empty()) { + return fixed_null{}; + } boost::geometry::correct(out); return out; diff --git a/src/import.cc b/src/import.cc index 60059f0..4622e10 100644 --- a/src/import.cc +++ b/src/import.cc @@ -4,6 +4,8 @@ #include "conf/configuration.h" #include "conf/options_parser.h" +#include "utl/progress_tracker.h" + #include "tiles/db/clear_database.h" #include "tiles/db/database_stats.h" #include "tiles/db/feature_pack.h" @@ -64,6 +66,10 @@ int run_tiles_import(int argc, char const** argv) { return 1; } + // standalone tiles-import lacks the progress tracker that motis' importer + // sets up; prepare_tiles/coastlines require an active (global) tracker. + utl::activate_progress_tracker("tiles-import"); + if (opt.has_any_task({"features"})) { check_profile(opt.osm_profile_); } diff --git a/src/mvt/tile_builder.cc b/src/mvt/tile_builder.cc index d6a9ea8..c0dd04f 100644 --- a/src/mvt/tile_builder.cc +++ b/src/mvt/tile_builder.cc @@ -82,30 +82,31 @@ struct layer_builder { has_geometry_ = true; ++features_written_; - std::string feature_buf; - pbf_builder feature_pb(feature_buf); + feature_buf_.clear(); + pbf_builder feature_pb(feature_buf_); encode_geometry(feature_pb, f.geometry_, spec_); feature_pb.add_uint64(ttm::Feature::optional_uint64_id, f.id_); write_metadata(feature_pb, f.meta_); - pb_.add_message(ttm::Layer::repeated_Feature_features, feature_buf); + pb_.add_message(ttm::Layer::repeated_Feature_features, feature_buf_); } void write_metadata(pbf_builder& pb, std::vector const& meta) { - std::vector t; + tags_.clear(); for (auto const& m : meta) { if (m.key_ == "layer" || boost::starts_with(m.key_, "__")) { continue; } - t.emplace_back(utl::get_or_create_index(meta_key_cache_, m.key_)); - t.emplace_back(utl::get_or_create_index(meta_value_cache_, m.value_)); + tags_.emplace_back(utl::get_or_create_index(meta_key_cache_, m.key_)); + tags_.emplace_back(utl::get_or_create_index(meta_value_cache_, m.value_)); } - pb.add_packed_uint32(ttm::Feature::packed_uint32_tags, begin(t), end(t)); + pb.add_packed_uint32(ttm::Feature::packed_uint32_tags, begin(tags_), + end(tags_)); } void aggregate_geometry() { @@ -203,6 +204,10 @@ struct layer_builder { std::string buf_; pbf_builder pb_; + // per-feature scratch, reused across features (cleared, capacity retained) + std::string feature_buf_; + std::vector tags_; + std::map meta_key_cache_; std::map meta_value_cache_; diff --git a/src/osm/load_coastlines.cc b/src/osm/load_coastlines.cc index a3efdfb..5b6d58a 100644 --- a/src/osm/load_coastlines.cc +++ b/src/osm/load_coastlines.cc @@ -5,7 +5,9 @@ #include #include -#include "clipper/clipper.hpp" +#include + +#include "clipper2/clipper.h" #include "utl/to_vec.h" @@ -23,17 +25,18 @@ #include "tiles/osm/load_shapefile.h" #include "tiles/util_parallel.h" -namespace cl = ClipperLib; +namespace c2 = Clipper2Lib; namespace tiles { -static_assert(sizeof(cl::cInt) == sizeof(fixed_coord_t), "coord type problem"); +static_assert(sizeof(std::int64_t) == sizeof(fixed_coord_t), + "coord type problem"); struct coastline { - coastline(fixed_box box, cl::Paths geo) : box_{box}, geo_{std::move(geo)} {} + coastline(fixed_box box, c2::Paths64 geo) : box_{box}, geo_{std::move(geo)} {} fixed_box box_; - cl::Paths geo_; + c2::Paths64 geo_; }; using coastline_ptr = std::shared_ptr; @@ -71,7 +74,7 @@ std::ostream& operator<<(std::ostream& os, fixed_box const& box) { << ")"; } -fixed_box bounding_box(cl::Paths const& geo) { +fixed_box bounding_box(c2::Paths64 const& geo) { auto min_x = std::numeric_limits::max(); auto min_y = std::numeric_limits::max(); auto max_x = std::numeric_limits::min(); @@ -79,10 +82,10 @@ fixed_box bounding_box(cl::Paths const& geo) { for (auto const& path : geo) { for (auto const& pt : path) { - min_x = std::min(min_x, static_cast(pt.X)); - min_y = std::min(min_y, static_cast(pt.Y)); - max_x = std::max(max_x, static_cast(pt.X)); - max_y = std::max(max_y, static_cast(pt.Y)); + min_x = std::min(min_x, static_cast(pt.x)); + min_y = std::min(min_y, static_cast(pt.y)); + max_x = std::max(max_x, static_cast(pt.x)); + max_y = std::max(max_y, static_cast(pt.y)); } } @@ -103,47 +106,49 @@ bool within(fixed_box const& outer, fixed_box const& inner) { outer.max_corner().y() >= inner.max_corner().y(); } -cl::Path box_to_path(fixed_box const& box) { +c2::Path64 box_to_path(fixed_box const& box) { return {{box.min_corner().x(), box.min_corner().y()}, {box.max_corner().x(), box.min_corner().y()}, {box.max_corner().x(), box.max_corner().y()}, {box.min_corner().x(), box.max_corner().y()}}; } -cl::Paths intersection(cl::Paths const& subject, cl::Path const& clip) { - cl::Clipper clpr; - utl::verify(clpr.AddPaths(subject, cl::ptSubject, true), "AddPath failed"); - utl::verify(clpr.AddPath(clip, cl::ptClip, true), "AddPaths failed"); +c2::Paths64 intersection(c2::Paths64 const& subject, c2::Path64 const& clip) { + c2::Clipper64 clpr; + clpr.AddSubject(subject); + clpr.AddClip(c2::Paths64{clip}); - cl::Paths solution; - utl::verify(clpr.Execute(cl::ctIntersection, solution, cl::pftEvenOdd, - cl::pftEvenOdd), - "Execute failed"); + c2::Paths64 solution; + utl::verify( + clpr.Execute(c2::ClipType::Intersection, c2::FillRule::EvenOdd, solution), + "Execute failed"); return solution; } -void to_fixed_polygon(fixed_polygon& polygon, cl::PolyNodes const& nodes) { - auto const path_to_ring = [](auto const& path) { +void to_fixed_polygon(fixed_polygon& polygon, c2::PolyPath64 const& parent) { + auto const path_to_ring = [](c2::Path64 const& path) { utl::verify(!path.empty(), "path empty"); fixed_ring ring; ring.reserve(path.size() + 1); for (auto const& pt : path) { - ring.emplace_back(pt.X, pt.Y); + ring.emplace_back(pt.x, pt.y); } - ring.emplace_back(path[0].X, path[0].Y); + ring.emplace_back(path.front().x, path.front().y); return ring; }; - for (auto const* outer : nodes) { - utl::verify(!outer->IsHole(), "outer ring is hole"); + for (auto const& outer_ptr : parent) { + auto const& outer = *outer_ptr; + utl::verify(!outer.IsHole(), "outer ring is hole"); fixed_simple_polygon simple; - simple.outer() = path_to_ring(outer->Contour); + simple.outer() = path_to_ring(outer.Polygon()); - for (auto const* inner : outer->Childs) { - utl::verify(inner->IsHole(), "inner ring is no hole"); - simple.inners().emplace_back(path_to_ring(inner->Contour)); + for (auto const& inner_ptr : outer) { + auto const& inner = *inner_ptr; + utl::verify(inner.IsHole(), "inner ring is no hole"); + simple.inners().emplace_back(path_to_ring(inner.Polygon())); - to_fixed_polygon(polygon, inner->Childs); + to_fixed_polygon(polygon, inner); } polygon.emplace_back(std::move(simple)); @@ -151,26 +156,26 @@ void to_fixed_polygon(fixed_polygon& polygon, cl::PolyNodes const& nodes) { } std::optional finalize_tile( - uint64_t const id, cl::Path const& draw_clip, cl::Path const& insert_clip, + uint64_t const id, c2::Path64 const& draw_clip, + c2::Path64 const& insert_clip, std::vector const& coastlines) { - cl::Clipper clpr; - clpr.AddPath(draw_clip, cl::ptSubject, true); + c2::Clipper64 clpr; + clpr.AddSubject(c2::Paths64{draw_clip}); for (auto const& coastline : coastlines) { - clpr.AddPaths(coastline->geo_, cl::ptClip, true); + clpr.AddClip(coastline->geo_); } - cl::PolyTree solution; - clpr.Execute(cl::ctDifference, solution, cl::pftEvenOdd, cl::pftEvenOdd); - utl::verify(!solution.Childs.empty(), "difference empty!"); + c2::PolyTree64 solution; + clpr.Execute(c2::ClipType::Difference, c2::FillRule::EvenOdd, solution); + utl::verify(solution.Count() != 0, "difference empty!"); - cl::Paths solution_paths; - cl::ClosedPathsFromPolyTree(solution, solution_paths); + auto const solution_paths = c2::PolyTreeToPaths64(solution); if (intersection(solution_paths, insert_clip).empty()) { return std::nullopt; } fixed_polygon polygon; - to_fixed_polygon(polygon, solution.Childs); + to_fixed_polygon(polygon, solution); boost::geometry::correct(polygon); return serialize_feature({id, @@ -251,13 +256,13 @@ void load_coastlines(tile_db_handle& db_handle, feature_shard& inserter, auto convert_path = [](auto const& in) { return utl::to_vec( - in, [](auto const& pt) { return cl::IntPoint{pt.x(), pt.y()}; }); + in, [](auto const& pt) { return c2::Point64{pt.x(), pt.y()}; }); }; { std::vector coastlines; auto coastline_handler = [&](fixed_simple_polygon geo) { - cl::Paths coastline; + c2::Paths64 coastline; coastline.emplace_back(convert_path(geo.outer())); for (auto const& inner : geo.inners()) { coastline.emplace_back(convert_path(inner)); diff --git a/src/util.cc b/src/util.cc index 72518a3..7ff5586 100644 --- a/src/util.cc +++ b/src/util.cc @@ -1,5 +1,6 @@ #include "tiles/util.h" +#include #include #include "zlib.h" @@ -26,28 +27,35 @@ std::string compress_gzip(std::string const& input) { /** Same as compress2, but with MAX_WBITS + 16 to indicate gzip header and trailer */ - z_stream zs{}; - - auto error = deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, - 8, Z_DEFAULT_STRATEGY); - - utl::verify(error == Z_OK, "deflateInit2 failed"); - - auto out_size = deflateBound(&zs, input.size()); + static int const level = [] { + auto const* e = std::getenv("TILES_GZIP_LEVEL"); + return e != nullptr ? std::atoi(e) : Z_BEST_COMPRESSION; + }(); + + // Reuse one deflate state per thread (deflateInit2 allocates ~256 KB of + // window/hash tables; doing it per tile is pure churn). deflateReset keeps + // the allocation and just rewinds the stream. + static thread_local z_stream* zs = [] { + auto* s = new z_stream{}; + utl::verify(deflateInit2(s, level, Z_DEFLATED, MAX_WBITS + 16, 8, + Z_DEFAULT_STRATEGY) == Z_OK, + "deflateInit2 failed"); + return s; + }(); + utl::verify(deflateReset(zs) == Z_OK, "deflateReset failed"); + + auto out_size = deflateBound(zs, input.size()); std::string buffer(out_size, '\0'); - zs.next_in = reinterpret_cast(const_cast(input.data())); - zs.avail_in = input.size(); - - zs.next_out = reinterpret_cast(buffer.data()); - zs.avail_out = buffer.size(); + zs->next_in = reinterpret_cast(const_cast(input.data())); + zs->avail_in = input.size(); - error = deflate(&zs, Z_FINISH); - utl::verify(error == Z_STREAM_END, "deflate failed"); + zs->next_out = reinterpret_cast(buffer.data()); + zs->avail_out = buffer.size(); - buffer.resize(zs.total_out); + utl::verify(deflate(zs, Z_FINISH) == Z_STREAM_END, "deflate failed"); - deflateEnd(&zs); + buffer.resize(zs->total_out); return buffer; } struct regex_matcher::impl { diff --git a/test/clipper_test.cc b/test/clipper_test.cc index e5f08b2..1e3dee0 100644 --- a/test/clipper_test.cc +++ b/test/clipper_test.cc @@ -2,33 +2,37 @@ #include -#include "clipper/clipper.hpp" +#include "clipper2/clipper.h" -namespace cl = ClipperLib; +namespace c2 = Clipper2Lib; -auto const subject = cl::Path{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; +auto const subject = c2::Path64{{0, 0}, {10, 0}, {10, 10}, {0, 10}}; TEST(clipper, orientation) { - ASSERT_TRUE(cl::Orientation(subject)); + ASSERT_TRUE(c2::IsPositive(subject)); auto s2 = subject; - cl::ReversePath(s2); - ASSERT_TRUE(!cl::Orientation(s2)); + std::reverse(begin(s2), end(s2)); + ASSERT_TRUE(!c2::IsPositive(s2)); } TEST(clipper, in_polygon) { - ASSERT_TRUE(cl::PointInPolygon(cl::IntPoint{5, 5}, subject) == 1); - ASSERT_TRUE(cl::PointInPolygon(cl::IntPoint{0, 0}, subject) == -1); - ASSERT_TRUE(cl::PointInPolygon(cl::IntPoint{15, 15}, subject) == 0); + ASSERT_TRUE(c2::PointInPolygon(c2::Point64{5, 5}, subject) == + c2::PointInPolygonResult::IsInside); + ASSERT_TRUE(c2::PointInPolygon(c2::Point64{0, 0}, subject) == + c2::PointInPolygonResult::IsOn); + ASSERT_TRUE(c2::PointInPolygon(c2::Point64{15, 15}, subject) == + c2::PointInPolygonResult::IsOutside); } TEST(clipper, intersection) { - auto const clip = cl::Path{{0, 0}, {5, 0}, {5, 5}, {0, 5}}; - auto solution = cl::Paths{}; + auto const clip = c2::Path64{{0, 0}, {5, 0}, {5, 5}, {0, 5}}; - cl::Clipper clpr; - clpr.AddPath(subject, cl::ptSubject, true); - clpr.AddPath(clip, cl::ptClip, true); - clpr.Execute(cl::ctIntersection, solution, cl::pftEvenOdd, cl::pftEvenOdd); + c2::Clipper64 clpr; + clpr.AddSubject(c2::Paths64{subject}); + clpr.AddClip(c2::Paths64{clip}); + + c2::Paths64 solution; + clpr.Execute(c2::ClipType::Intersection, c2::FillRule::EvenOdd, solution); ASSERT_TRUE(solution.size() == 1); ASSERT_TRUE(solution[0].size() == clip.size()); @@ -39,13 +43,14 @@ TEST(clipper, intersection) { } TEST(clipper, intersection_empty) { - auto const clip = cl::Path{{20, 20}, {22, 20}, {22, 22}, {20, 22}}; - auto solution = cl::Paths{}; + auto const clip = c2::Path64{{20, 20}, {22, 20}, {22, 22}, {20, 22}}; + + c2::Clipper64 clpr; + clpr.AddSubject(c2::Paths64{subject}); + clpr.AddClip(c2::Paths64{clip}); - cl::Clipper clpr; - clpr.AddPath(subject, cl::ptSubject, true); - clpr.AddPath(clip, cl::ptClip, true); - clpr.Execute(cl::ctIntersection, solution, cl::pftEvenOdd, cl::pftEvenOdd); + c2::Paths64 solution; + clpr.Execute(c2::ClipType::Intersection, c2::FillRule::EvenOdd, solution); ASSERT_TRUE(solution.empty()); }