@sentryware/s2-node 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +45 -0
- package/.dockerignore +1 -0
- package/.gitmodules +3 -0
- package/CHANGELOG.md +33 -0
- package/LICENSE +201 -0
- package/README.md +147 -0
- package/binding.gyp +170 -0
- package/docker/Dockerfile.node20.test +8 -0
- package/docker/Dockerfile.node22.test +8 -0
- package/docker/Dockerfile.node24.test +8 -0
- package/index.d.ts +117 -0
- package/index.js +6 -0
- package/jest.config.js +184 -0
- package/package.json +43 -0
- package/publish-linux.sh +18 -0
- package/publish-osx.sh +19 -0
- package/src/builder.cc +84 -0
- package/src/builder.h +29 -0
- package/src/cell.cc +71 -0
- package/src/cell.h +26 -0
- package/src/cell_id.cc +210 -0
- package/src/cell_id.h +44 -0
- package/src/cell_union.cc +237 -0
- package/src/cell_union.h +34 -0
- package/src/earth.cc +185 -0
- package/src/earth.h +33 -0
- package/src/latlng.cc +132 -0
- package/src/latlng.h +28 -0
- package/src/loop.cc +51 -0
- package/src/loop.h +21 -0
- package/src/point.cc +69 -0
- package/src/point.h +23 -0
- package/src/polygon.cc +36 -0
- package/src/polygon.h +20 -0
- package/src/polyline.cc +186 -0
- package/src/polyline.h +34 -0
- package/src/region_coverer.cc +450 -0
- package/src/region_coverer.h +56 -0
- package/src/s2.cc +27 -0
- package/test/Cell.test.js +37 -0
- package/test/CellId.test.js +135 -0
- package/test/CellUnion.test.js +150 -0
- package/test/Earth.test.js +62 -0
- package/test/LatLng.test.js +45 -0
- package/test/Point.test.js +14 -0
- package/test/Polyline.test.js +78 -0
- package/test/RegionCoverer.test.js +301 -0
- package/test.sh +16 -0
- package/third_party/s2geometry/.travis.yml +163 -0
- package/third_party/s2geometry/AUTHORS +13 -0
- package/third_party/s2geometry/CONTRIBUTING.md +65 -0
- package/third_party/s2geometry/CONTRIBUTORS +30 -0
- package/third_party/s2geometry/LICENSE +202 -0
- package/third_party/s2geometry/NOTICE +5 -0
- package/third_party/s2geometry/README.md +127 -0
- package/third_party/s2geometry/doc/examples/point_index.cc +44 -0
- package/third_party/s2geometry/doc/examples/term_index.cc +99 -0
- package/third_party/s2geometry/doc/examples/term_index.py +101 -0
- package/third_party/s2geometry/src/python/coder.i +125 -0
- package/third_party/s2geometry/src/python/pywraps2_test.py +786 -0
- package/third_party/s2geometry/src/python/s2.i +37 -0
- package/third_party/s2geometry/src/python/s2_common.i +756 -0
- package/third_party/s2geometry/src/s2/_fp_contract_off.h +60 -0
- package/third_party/s2geometry/src/s2/base/casts.h +318 -0
- package/third_party/s2geometry/src/s2/base/commandlineflags.h +67 -0
- package/third_party/s2geometry/src/s2/base/integral_types.h +31 -0
- package/third_party/s2geometry/src/s2/base/log_severity.h +40 -0
- package/third_party/s2geometry/src/s2/base/logging.h +173 -0
- package/third_party/s2geometry/src/s2/base/mutex.h +61 -0
- package/third_party/s2geometry/src/s2/base/port.h +999 -0
- package/third_party/s2geometry/src/s2/base/spinlock.h +60 -0
- package/third_party/s2geometry/src/s2/base/stringprintf.cc +107 -0
- package/third_party/s2geometry/src/s2/base/stringprintf.h +53 -0
- package/third_party/s2geometry/src/s2/base/strtoint.cc +65 -0
- package/third_party/s2geometry/src/s2/base/strtoint.h +106 -0
- package/third_party/s2geometry/src/s2/base/timer.h +50 -0
- package/third_party/s2geometry/src/s2/encoded_s2cell_id_vector.cc +164 -0
- package/third_party/s2geometry/src/s2/encoded_s2cell_id_vector.h +110 -0
- package/third_party/s2geometry/src/s2/encoded_s2cell_id_vector_test.cc +232 -0
- package/third_party/s2geometry/src/s2/encoded_s2point_vector.cc +838 -0
- package/third_party/s2geometry/src/s2/encoded_s2point_vector.h +140 -0
- package/third_party/s2geometry/src/s2/encoded_s2point_vector_test.cc +344 -0
- package/third_party/s2geometry/src/s2/encoded_s2shape_index.cc +181 -0
- package/third_party/s2geometry/src/s2/encoded_s2shape_index.h +276 -0
- package/third_party/s2geometry/src/s2/encoded_s2shape_index_test.cc +244 -0
- package/third_party/s2geometry/src/s2/encoded_string_vector.cc +66 -0
- package/third_party/s2geometry/src/s2/encoded_string_vector.h +164 -0
- package/third_party/s2geometry/src/s2/encoded_string_vector_test.cc +69 -0
- package/third_party/s2geometry/src/s2/encoded_uint_vector.h +299 -0
- package/third_party/s2geometry/src/s2/encoded_uint_vector_test.cc +124 -0
- package/third_party/s2geometry/src/s2/id_set_lexicon.cc +81 -0
- package/third_party/s2geometry/src/s2/id_set_lexicon.h +199 -0
- package/third_party/s2geometry/src/s2/id_set_lexicon_test.cc +70 -0
- package/third_party/s2geometry/src/s2/mutable_s2shape_index.cc +1585 -0
- package/third_party/s2geometry/src/s2/mutable_s2shape_index.h +600 -0
- package/third_party/s2geometry/src/s2/mutable_s2shape_index_test.cc +589 -0
- package/third_party/s2geometry/src/s2/r1interval.h +220 -0
- package/third_party/s2geometry/src/s2/r1interval_test.cc +185 -0
- package/third_party/s2geometry/src/s2/r2.h +26 -0
- package/third_party/s2geometry/src/s2/r2rect.cc +93 -0
- package/third_party/s2geometry/src/s2/r2rect.h +234 -0
- package/third_party/s2geometry/src/s2/r2rect_test.cc +228 -0
- package/third_party/s2geometry/src/s2/s1angle.cc +54 -0
- package/third_party/s2geometry/src/s2/s1angle.h +336 -0
- package/third_party/s2geometry/src/s2/s1angle_test.cc +185 -0
- package/third_party/s2geometry/src/s2/s1chord_angle.cc +159 -0
- package/third_party/s2geometry/src/s2/s1chord_angle.h +369 -0
- package/third_party/s2geometry/src/s2/s1chord_angle_test.cc +207 -0
- package/third_party/s2geometry/src/s2/s1interval.cc +296 -0
- package/third_party/s2geometry/src/s2/s1interval.h +266 -0
- package/third_party/s2geometry/src/s2/s1interval_test.cc +469 -0
- package/third_party/s2geometry/src/s2/s2boolean_operation.cc +2391 -0
- package/third_party/s2geometry/src/s2/s2boolean_operation.h +501 -0
- package/third_party/s2geometry/src/s2/s2boolean_operation_test.cc +1400 -0
- package/third_party/s2geometry/src/s2/s2builder.cc +1828 -0
- package/third_party/s2geometry/src/s2/s2builder.h +1057 -0
- package/third_party/s2geometry/src/s2/s2builder_graph.cc +1084 -0
- package/third_party/s2geometry/src/s2/s2builder_graph.h +799 -0
- package/third_party/s2geometry/src/s2/s2builder_graph_test.cc +462 -0
- package/third_party/s2geometry/src/s2/s2builder_layer.h +50 -0
- package/third_party/s2geometry/src/s2/s2builder_test.cc +1329 -0
- package/third_party/s2geometry/src/s2/s2builderutil_closed_set_normalizer.cc +313 -0
- package/third_party/s2geometry/src/s2/s2builderutil_closed_set_normalizer.h +221 -0
- package/third_party/s2geometry/src/s2/s2builderutil_closed_set_normalizer_test.cc +261 -0
- package/third_party/s2geometry/src/s2/s2builderutil_find_polygon_degeneracies.cc +392 -0
- package/third_party/s2geometry/src/s2/s2builderutil_find_polygon_degeneracies.h +86 -0
- package/third_party/s2geometry/src/s2/s2builderutil_find_polygon_degeneracies_test.cc +182 -0
- package/third_party/s2geometry/src/s2/s2builderutil_graph_shape.h +57 -0
- package/third_party/s2geometry/src/s2/s2builderutil_lax_polygon_layer.cc +212 -0
- package/third_party/s2geometry/src/s2/s2builderutil_lax_polygon_layer.h +218 -0
- package/third_party/s2geometry/src/s2/s2builderutil_lax_polygon_layer_test.cc +367 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2point_vector_layer.cc +74 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2point_vector_layer.h +122 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2point_vector_layer_test.cc +167 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2polygon_layer.cc +191 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2polygon_layer.h +211 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2polygon_layer_test.cc +312 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2polyline_layer.cc +105 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2polyline_layer.h +174 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2polyline_layer_test.cc +220 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2polyline_vector_layer.cc +98 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2polyline_vector_layer.h +292 -0
- package/third_party/s2geometry/src/s2/s2builderutil_s2polyline_vector_layer_test.cc +233 -0
- package/third_party/s2geometry/src/s2/s2builderutil_snap_functions.cc +354 -0
- package/third_party/s2geometry/src/s2/s2builderutil_snap_functions.h +239 -0
- package/third_party/s2geometry/src/s2/s2builderutil_snap_functions_test.cc +716 -0
- package/third_party/s2geometry/src/s2/s2builderutil_testing.cc +37 -0
- package/third_party/s2geometry/src/s2/s2builderutil_testing.h +100 -0
- package/third_party/s2geometry/src/s2/s2builderutil_testing_test.cc +85 -0
- package/third_party/s2geometry/src/s2/s2cap.cc +347 -0
- package/third_party/s2geometry/src/s2/s2cap.h +286 -0
- package/third_party/s2geometry/src/s2/s2cap_test.cc +379 -0
- package/third_party/s2geometry/src/s2/s2cell.cc +552 -0
- package/third_party/s2geometry/src/s2/s2cell.h +249 -0
- package/third_party/s2geometry/src/s2/s2cell_id.cc +619 -0
- package/third_party/s2geometry/src/s2/s2cell_id.h +705 -0
- package/third_party/s2geometry/src/s2/s2cell_id_test.cc +633 -0
- package/third_party/s2geometry/src/s2/s2cell_index.cc +149 -0
- package/third_party/s2geometry/src/s2/s2cell_index.h +660 -0
- package/third_party/s2geometry/src/s2/s2cell_index_test.cc +411 -0
- package/third_party/s2geometry/src/s2/s2cell_test.cc +687 -0
- package/third_party/s2geometry/src/s2/s2cell_union.cc +515 -0
- package/third_party/s2geometry/src/s2/s2cell_union.h +399 -0
- package/third_party/s2geometry/src/s2/s2cell_union_test.cc +598 -0
- package/third_party/s2geometry/src/s2/s2centroids.cc +84 -0
- package/third_party/s2geometry/src/s2/s2centroids.h +87 -0
- package/third_party/s2geometry/src/s2/s2centroids_test.cc +82 -0
- package/third_party/s2geometry/src/s2/s2closest_cell_query.cc +123 -0
- package/third_party/s2geometry/src/s2/s2closest_cell_query.h +385 -0
- package/third_party/s2geometry/src/s2/s2closest_cell_query_base.h +841 -0
- package/third_party/s2geometry/src/s2/s2closest_cell_query_base_test.cc +63 -0
- package/third_party/s2geometry/src/s2/s2closest_cell_query_test.cc +412 -0
- package/third_party/s2geometry/src/s2/s2closest_edge_query.cc +106 -0
- package/third_party/s2geometry/src/s2/s2closest_edge_query.h +421 -0
- package/third_party/s2geometry/src/s2/s2closest_edge_query_base.h +946 -0
- package/third_party/s2geometry/src/s2/s2closest_edge_query_base_test.cc +59 -0
- package/third_party/s2geometry/src/s2/s2closest_edge_query_test.cc +505 -0
- package/third_party/s2geometry/src/s2/s2closest_edge_query_testing.h +91 -0
- package/third_party/s2geometry/src/s2/s2closest_point_query.cc +66 -0
- package/third_party/s2geometry/src/s2/s2closest_point_query.h +465 -0
- package/third_party/s2geometry/src/s2/s2closest_point_query_base.h +767 -0
- package/third_party/s2geometry/src/s2/s2closest_point_query_base_test.cc +63 -0
- package/third_party/s2geometry/src/s2/s2closest_point_query_test.cc +312 -0
- package/third_party/s2geometry/src/s2/s2contains_point_query.h +328 -0
- package/third_party/s2geometry/src/s2/s2contains_point_query_test.cc +159 -0
- package/third_party/s2geometry/src/s2/s2contains_vertex_query.cc +39 -0
- package/third_party/s2geometry/src/s2/s2contains_vertex_query.h +66 -0
- package/third_party/s2geometry/src/s2/s2contains_vertex_query_test.cc +67 -0
- package/third_party/s2geometry/src/s2/s2convex_hull_query.cc +198 -0
- package/third_party/s2geometry/src/s2/s2convex_hull_query.h +110 -0
- package/third_party/s2geometry/src/s2/s2convex_hull_query_test.cc +208 -0
- package/third_party/s2geometry/src/s2/s2coords.cc +146 -0
- package/third_party/s2geometry/src/s2/s2coords.h +459 -0
- package/third_party/s2geometry/src/s2/s2coords_internal.h +71 -0
- package/third_party/s2geometry/src/s2/s2coords_test.cc +218 -0
- package/third_party/s2geometry/src/s2/s2crossing_edge_query.cc +380 -0
- package/third_party/s2geometry/src/s2/s2crossing_edge_query.h +220 -0
- package/third_party/s2geometry/src/s2/s2crossing_edge_query_test.cc +382 -0
- package/third_party/s2geometry/src/s2/s2debug.cc +23 -0
- package/third_party/s2geometry/src/s2/s2debug.h +69 -0
- package/third_party/s2geometry/src/s2/s2distance_target.h +165 -0
- package/third_party/s2geometry/src/s2/s2earth.cc +52 -0
- package/third_party/s2geometry/src/s2/s2earth.h +268 -0
- package/third_party/s2geometry/src/s2/s2earth_test.cc +146 -0
- package/third_party/s2geometry/src/s2/s2edge_clipping.cc +462 -0
- package/third_party/s2geometry/src/s2/s2edge_clipping.h +183 -0
- package/third_party/s2geometry/src/s2/s2edge_clipping_test.cc +335 -0
- package/third_party/s2geometry/src/s2/s2edge_crosser.cc +85 -0
- package/third_party/s2geometry/src/s2/s2edge_crosser.h +343 -0
- package/third_party/s2geometry/src/s2/s2edge_crosser_test.cc +264 -0
- package/third_party/s2geometry/src/s2/s2edge_crossings.cc +515 -0
- package/third_party/s2geometry/src/s2/s2edge_crossings.h +138 -0
- package/third_party/s2geometry/src/s2/s2edge_crossings_internal.h +59 -0
- package/third_party/s2geometry/src/s2/s2edge_crossings_test.cc +246 -0
- package/third_party/s2geometry/src/s2/s2edge_distances.cc +419 -0
- package/third_party/s2geometry/src/s2/s2edge_distances.h +192 -0
- package/third_party/s2geometry/src/s2/s2edge_distances_test.cc +539 -0
- package/third_party/s2geometry/src/s2/s2edge_tessellator.cc +276 -0
- package/third_party/s2geometry/src/s2/s2edge_tessellator.h +101 -0
- package/third_party/s2geometry/src/s2/s2edge_tessellator_test.cc +492 -0
- package/third_party/s2geometry/src/s2/s2edge_vector_shape.h +85 -0
- package/third_party/s2geometry/src/s2/s2edge_vector_shape_test.cc +66 -0
- package/third_party/s2geometry/src/s2/s2error.cc +29 -0
- package/third_party/s2geometry/src/s2/s2error.h +147 -0
- package/third_party/s2geometry/src/s2/s2error_test.cc +31 -0
- package/third_party/s2geometry/src/s2/s2furthest_edge_query.cc +117 -0
- package/third_party/s2geometry/src/s2/s2furthest_edge_query.h +439 -0
- package/third_party/s2geometry/src/s2/s2furthest_edge_query_test.cc +487 -0
- package/third_party/s2geometry/src/s2/s2latlng.cc +90 -0
- package/third_party/s2geometry/src/s2/s2latlng.h +234 -0
- package/third_party/s2geometry/src/s2/s2latlng_rect.cc +727 -0
- package/third_party/s2geometry/src/s2/s2latlng_rect.h +434 -0
- package/third_party/s2geometry/src/s2/s2latlng_rect_bounder.cc +344 -0
- package/third_party/s2geometry/src/s2/s2latlng_rect_bounder.h +89 -0
- package/third_party/s2geometry/src/s2/s2latlng_rect_bounder_test.cc +306 -0
- package/third_party/s2geometry/src/s2/s2latlng_rect_test.cc +1030 -0
- package/third_party/s2geometry/src/s2/s2latlng_test.cc +165 -0
- package/third_party/s2geometry/src/s2/s2lax_loop_shape.cc +104 -0
- package/third_party/s2geometry/src/s2/s2lax_loop_shape.h +153 -0
- package/third_party/s2geometry/src/s2/s2lax_loop_shape_test.cc +101 -0
- package/third_party/s2geometry/src/s2/s2lax_polygon_shape.cc +348 -0
- package/third_party/s2geometry/src/s2/s2lax_polygon_shape.h +183 -0
- package/third_party/s2geometry/src/s2/s2lax_polygon_shape_test.cc +234 -0
- package/third_party/s2geometry/src/s2/s2lax_polyline_shape.cc +118 -0
- package/third_party/s2geometry/src/s2/s2lax_polyline_shape.h +124 -0
- package/third_party/s2geometry/src/s2/s2lax_polyline_shape_test.cc +62 -0
- package/third_party/s2geometry/src/s2/s2loop.cc +1509 -0
- package/third_party/s2geometry/src/s2/s2loop.h +711 -0
- package/third_party/s2geometry/src/s2/s2loop_measures.cc +313 -0
- package/third_party/s2geometry/src/s2/s2loop_measures.h +280 -0
- package/third_party/s2geometry/src/s2/s2loop_measures_test.cc +367 -0
- package/third_party/s2geometry/src/s2/s2loop_test.cc +1371 -0
- package/third_party/s2geometry/src/s2/s2max_distance_targets.cc +265 -0
- package/third_party/s2geometry/src/s2/s2max_distance_targets.h +241 -0
- package/third_party/s2geometry/src/s2/s2max_distance_targets_test.cc +367 -0
- package/third_party/s2geometry/src/s2/s2measures.cc +128 -0
- package/third_party/s2geometry/src/s2/s2measures.h +78 -0
- package/third_party/s2geometry/src/s2/s2measures_test.cc +135 -0
- package/third_party/s2geometry/src/s2/s2metrics.cc +122 -0
- package/third_party/s2geometry/src/s2/s2metrics.h +199 -0
- package/third_party/s2geometry/src/s2/s2metrics_test.cc +127 -0
- package/third_party/s2geometry/src/s2/s2min_distance_targets.cc +295 -0
- package/third_party/s2geometry/src/s2/s2min_distance_targets.h +273 -0
- package/third_party/s2geometry/src/s2/s2min_distance_targets_test.cc +239 -0
- package/third_party/s2geometry/src/s2/s2padded_cell.cc +162 -0
- package/third_party/s2geometry/src/s2/s2padded_cell.h +108 -0
- package/third_party/s2geometry/src/s2/s2padded_cell_test.cc +138 -0
- package/third_party/s2geometry/src/s2/s2point.h +38 -0
- package/third_party/s2geometry/src/s2/s2point_compression.cc +388 -0
- package/third_party/s2geometry/src/s2/s2point_compression.h +78 -0
- package/third_party/s2geometry/src/s2/s2point_compression_test.cc +305 -0
- package/third_party/s2geometry/src/s2/s2point_index.h +345 -0
- package/third_party/s2geometry/src/s2/s2point_index_test.cc +147 -0
- package/third_party/s2geometry/src/s2/s2point_region.cc +72 -0
- package/third_party/s2geometry/src/s2/s2point_region.h +76 -0
- package/third_party/s2geometry/src/s2/s2point_region_test.cc +100 -0
- package/third_party/s2geometry/src/s2/s2point_span.h +57 -0
- package/third_party/s2geometry/src/s2/s2point_test.cc +47 -0
- package/third_party/s2geometry/src/s2/s2point_vector_shape.h +127 -0
- package/third_party/s2geometry/src/s2/s2point_vector_shape_test.cc +59 -0
- package/third_party/s2geometry/src/s2/s2pointutil.cc +131 -0
- package/third_party/s2geometry/src/s2/s2pointutil.h +138 -0
- package/third_party/s2geometry/src/s2/s2pointutil_test.cc +157 -0
- package/third_party/s2geometry/src/s2/s2polygon.cc +1569 -0
- package/third_party/s2geometry/src/s2/s2polygon.h +934 -0
- package/third_party/s2geometry/src/s2/s2polygon_test.cc +3025 -0
- package/third_party/s2geometry/src/s2/s2polyline.cc +645 -0
- package/third_party/s2geometry/src/s2/s2polyline.h +379 -0
- package/third_party/s2geometry/src/s2/s2polyline_alignment.cc +414 -0
- package/third_party/s2geometry/src/s2/s2polyline_alignment.h +245 -0
- package/third_party/s2geometry/src/s2/s2polyline_alignment_internal.h +158 -0
- package/third_party/s2geometry/src/s2/s2polyline_alignment_test.cc +610 -0
- package/third_party/s2geometry/src/s2/s2polyline_measures.cc +42 -0
- package/third_party/s2geometry/src/s2/s2polyline_measures.h +53 -0
- package/third_party/s2geometry/src/s2/s2polyline_measures_test.cc +57 -0
- package/third_party/s2geometry/src/s2/s2polyline_simplifier.cc +187 -0
- package/third_party/s2geometry/src/s2/s2polyline_simplifier.h +109 -0
- package/third_party/s2geometry/src/s2/s2polyline_simplifier_test.cc +165 -0
- package/third_party/s2geometry/src/s2/s2polyline_test.cc +554 -0
- package/third_party/s2geometry/src/s2/s2predicates.cc +1486 -0
- package/third_party/s2geometry/src/s2/s2predicates.h +282 -0
- package/third_party/s2geometry/src/s2/s2predicates_internal.h +135 -0
- package/third_party/s2geometry/src/s2/s2predicates_test.cc +1427 -0
- package/third_party/s2geometry/src/s2/s2projections.cc +109 -0
- package/third_party/s2geometry/src/s2/s2projections.h +161 -0
- package/third_party/s2geometry/src/s2/s2projections_test.cc +78 -0
- package/third_party/s2geometry/src/s2/s2r2rect.cc +88 -0
- package/third_party/s2geometry/src/s2/s2r2rect.h +292 -0
- package/third_party/s2geometry/src/s2/s2r2rect_test.cc +312 -0
- package/third_party/s2geometry/src/s2/s2region.cc +26 -0
- package/third_party/s2geometry/src/s2/s2region.h +142 -0
- package/third_party/s2geometry/src/s2/s2region_coverer.cc +514 -0
- package/third_party/s2geometry/src/s2/s2region_coverer.h +356 -0
- package/third_party/s2geometry/src/s2/s2region_coverer_test.cc +509 -0
- package/third_party/s2geometry/src/s2/s2region_intersection.cc +84 -0
- package/third_party/s2geometry/src/s2/s2region_intersection.h +79 -0
- package/third_party/s2geometry/src/s2/s2region_term_indexer.cc +270 -0
- package/third_party/s2geometry/src/s2/s2region_term_indexer.h +299 -0
- package/third_party/s2geometry/src/s2/s2region_term_indexer_test.cc +209 -0
- package/third_party/s2geometry/src/s2/s2region_test.cc +370 -0
- package/third_party/s2geometry/src/s2/s2region_union.cc +90 -0
- package/third_party/s2geometry/src/s2/s2region_union.h +83 -0
- package/third_party/s2geometry/src/s2/s2region_union_test.cc +89 -0
- package/third_party/s2geometry/src/s2/s2shape.h +283 -0
- package/third_party/s2geometry/src/s2/s2shape_index.cc +321 -0
- package/third_party/s2geometry/src/s2/s2shape_index.h +781 -0
- package/third_party/s2geometry/src/s2/s2shape_index_buffered_region.cc +113 -0
- package/third_party/s2geometry/src/s2/s2shape_index_buffered_region.h +135 -0
- package/third_party/s2geometry/src/s2/s2shape_index_buffered_region_test.cc +162 -0
- package/third_party/s2geometry/src/s2/s2shape_index_measures.cc +92 -0
- package/third_party/s2geometry/src/s2/s2shape_index_measures.h +100 -0
- package/third_party/s2geometry/src/s2/s2shape_index_measures_test.cc +136 -0
- package/third_party/s2geometry/src/s2/s2shape_index_region.h +350 -0
- package/third_party/s2geometry/src/s2/s2shape_index_region_test.cc +161 -0
- package/third_party/s2geometry/src/s2/s2shape_index_test.cc +24 -0
- package/third_party/s2geometry/src/s2/s2shape_measures.cc +138 -0
- package/third_party/s2geometry/src/s2/s2shape_measures.h +95 -0
- package/third_party/s2geometry/src/s2/s2shape_measures_test.cc +139 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_build_polygon_boundaries.cc +120 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_build_polygon_boundaries.h +66 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_build_polygon_boundaries_test.cc +170 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_coding.cc +253 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_coding.h +283 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_coding_test.cc +54 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_contains_brute_force.cc +40 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_contains_brute_force.h +41 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_contains_brute_force_test.cc +55 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_count_edges.h +57 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_count_edges_test.cc +43 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_edge_iterator.cc +45 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_edge_iterator.h +72 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_edge_iterator_test.cc +116 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_get_reference_point.cc +107 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_get_reference_point.h +48 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_get_reference_point_test.cc +104 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_range_iterator.cc +58 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_range_iterator.h +65 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_range_iterator_test.cc +61 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_shape_edge.h +58 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_shape_edge_id.h +97 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_testing.cc +104 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_testing.h +36 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_visit_crossing_edge_pairs.cc +440 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_visit_crossing_edge_pairs.h +72 -0
- package/third_party/s2geometry/src/s2/s2shapeutil_visit_crossing_edge_pairs_test.cc +184 -0
- package/third_party/s2geometry/src/s2/s2testing.cc +464 -0
- package/third_party/s2geometry/src/s2/s2testing.h +385 -0
- package/third_party/s2geometry/src/s2/s2testing_test.cc +166 -0
- package/third_party/s2geometry/src/s2/s2text_format.cc +506 -0
- package/third_party/s2geometry/src/s2/s2text_format.h +289 -0
- package/third_party/s2geometry/src/s2/s2text_format_test.cc +417 -0
- package/third_party/s2geometry/src/s2/s2wedge_relations.cc +80 -0
- package/third_party/s2geometry/src/s2/s2wedge_relations.h +64 -0
- package/third_party/s2geometry/src/s2/s2wedge_relations_test.cc +89 -0
- package/third_party/s2geometry/src/s2/sequence_lexicon.h +296 -0
- package/third_party/s2geometry/src/s2/sequence_lexicon_test.cc +113 -0
- package/third_party/s2geometry/src/s2/strings/ostringstream.cc +35 -0
- package/third_party/s2geometry/src/s2/strings/ostringstream.h +105 -0
- package/third_party/s2geometry/src/s2/strings/serialize.cc +46 -0
- package/third_party/s2geometry/src/s2/strings/serialize.h +40 -0
- package/third_party/s2geometry/src/s2/third_party/absl/algorithm/algorithm.h +187 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/attributes.h +666 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/casts.h +189 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/config.h +462 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/dynamic_annotations.cc +129 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/dynamic_annotations.h +394 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/internal/atomic_hook.h +168 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/internal/identity.h +33 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/internal/inline_variable.h +117 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/internal/invoke.h +188 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/internal/raw_logging.cc +254 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/internal/raw_logging.h +205 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/internal/throw_delegate.cc +106 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/internal/throw_delegate.h +71 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/internal/unaligned_access.h +322 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/log_severity.h +77 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/macros.h +236 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/optimization.h +177 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/policy_checks.h +124 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/port.h +97 -0
- package/third_party/s2geometry/src/s2/third_party/absl/base/thread_annotations.h +277 -0
- package/third_party/s2geometry/src/s2/third_party/absl/container/fixed_array.h +523 -0
- package/third_party/s2geometry/src/s2/third_party/absl/container/inlined_vector.h +1453 -0
- package/third_party/s2geometry/src/s2/third_party/absl/container/internal/compressed_tuple.h +191 -0
- package/third_party/s2geometry/src/s2/third_party/absl/container/internal/container_memory.h +424 -0
- package/third_party/s2geometry/src/s2/third_party/absl/container/internal/layout.h +739 -0
- package/third_party/s2geometry/src/s2/third_party/absl/memory/memory.h +755 -0
- package/third_party/s2geometry/src/s2/third_party/absl/meta/type_traits.h +436 -0
- package/third_party/s2geometry/src/s2/third_party/absl/numeric/int128.cc +232 -0
- package/third_party/s2geometry/src/s2/third_party/absl/numeric/int128.h +656 -0
- package/third_party/s2geometry/src/s2/third_party/absl/numeric/int128_have_intrinsic.inc +3 -0
- package/third_party/s2geometry/src/s2/third_party/absl/numeric/int128_no_intrinsic.inc +3 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/ascii.cc +198 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/ascii.h +239 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/ascii_ctype.h +66 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/internal/bits.h +53 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/internal/memutil.cc +110 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/internal/memutil.h +146 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/internal/resize_uninitialized.h +72 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/match.cc +38 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/match.h +89 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/numbers.cc +909 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/numbers.h +187 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/str_cat.cc +240 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/str_cat.h +398 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/str_join.h +22 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/str_split.cc +47 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/str_split.h +43 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/string_view.cc +245 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/string_view.h +602 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/strip.cc +42 -0
- package/third_party/s2geometry/src/s2/third_party/absl/strings/strip.h +130 -0
- package/third_party/s2geometry/src/s2/third_party/absl/types/span.h +793 -0
- package/third_party/s2geometry/src/s2/third_party/absl/utility/utility.h +299 -0
- package/third_party/s2geometry/src/s2/util/bits/bit-interleave.cc +274 -0
- package/third_party/s2geometry/src/s2/util/bits/bit-interleave.h +53 -0
- package/third_party/s2geometry/src/s2/util/bits/bits.cc +155 -0
- package/third_party/s2geometry/src/s2/util/bits/bits.h +745 -0
- package/third_party/s2geometry/src/s2/util/coding/coder.cc +83 -0
- package/third_party/s2geometry/src/s2/util/coding/coder.h +553 -0
- package/third_party/s2geometry/src/s2/util/coding/nth-derivative.h +134 -0
- package/third_party/s2geometry/src/s2/util/coding/transforms.h +62 -0
- package/third_party/s2geometry/src/s2/util/coding/varint.cc +289 -0
- package/third_party/s2geometry/src/s2/util/coding/varint.h +476 -0
- package/third_party/s2geometry/src/s2/util/endian/endian.h +859 -0
- package/third_party/s2geometry/src/s2/util/gtl/btree.h +2471 -0
- package/third_party/s2geometry/src/s2/util/gtl/btree_container.h +411 -0
- package/third_party/s2geometry/src/s2/util/gtl/btree_map.h +79 -0
- package/third_party/s2geometry/src/s2/util/gtl/btree_set.h +73 -0
- package/third_party/s2geometry/src/s2/util/gtl/compact_array.h +653 -0
- package/third_party/s2geometry/src/s2/util/gtl/container_logging.h +291 -0
- package/third_party/s2geometry/src/s2/util/gtl/dense_hash_set.h +358 -0
- package/third_party/s2geometry/src/s2/util/gtl/densehashtable.h +1493 -0
- package/third_party/s2geometry/src/s2/util/gtl/hashtable_common.h +253 -0
- package/third_party/s2geometry/src/s2/util/gtl/layout.h +28 -0
- package/third_party/s2geometry/src/s2/util/gtl/legacy_random_shuffle.h +77 -0
- package/third_party/s2geometry/src/s2/util/hash/mix.h +76 -0
- package/third_party/s2geometry/src/s2/util/math/exactfloat/exactfloat.cc +832 -0
- package/third_party/s2geometry/src/s2/util/math/exactfloat/exactfloat.h +646 -0
- package/third_party/s2geometry/src/s2/util/math/mathutil.cc +75 -0
- package/third_party/s2geometry/src/s2/util/math/mathutil.h +189 -0
- package/third_party/s2geometry/src/s2/util/math/matrix3x3.h +574 -0
- package/third_party/s2geometry/src/s2/util/math/vector.h +569 -0
- package/third_party/s2geometry/src/s2/util/math/vector3_hash.h +54 -0
- package/third_party/s2geometry/src/s2/util/units/length-units.cc +21 -0
- package/third_party/s2geometry/src/s2/util/units/length-units.h +135 -0
- package/third_party/s2geometry/src/s2/util/units/physical-units.h +313 -0
- package/third_party/s2geometry/src/s2/value_lexicon.h +234 -0
- package/third_party/s2geometry/src/s2/value_lexicon_test.cc +121 -0
- package/third_party/s2geometry/third_party/cmake/FindGFlags.cmake +48 -0
- package/third_party/s2geometry/third_party/cmake/FindGlog.cmake +48 -0
|
@@ -0,0 +1,2391 @@
|
|
|
1
|
+
// Copyright 2017 Google Inc. All Rights Reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
|
|
16
|
+
// Author: ericv@google.com (Eric Veach)
|
|
17
|
+
//
|
|
18
|
+
// Boolean operations are implemented by constructing the boundary of the
|
|
19
|
+
// result and then using S2Builder to assemble the edges. The boundary is
|
|
20
|
+
// obtained by clipping each of the two input regions to the interior or
|
|
21
|
+
// exterior of the other region. For example, to compute the union of A and
|
|
22
|
+
// B, we clip the boundary of A to the exterior of B and the boundary of B to
|
|
23
|
+
// the exterior of A; the resulting set of edges defines the union of the two
|
|
24
|
+
// regions.
|
|
25
|
+
//
|
|
26
|
+
// We use exact predicates, but inexact constructions (e.g. computing the
|
|
27
|
+
// intersection point of two edges). Nevertheless, the following algorithm is
|
|
28
|
+
// guaranteed to be 100% robust, in that the computed boundary stays within a
|
|
29
|
+
// small tolerance (snap_radius + S2::kIntersectionError) of the exact
|
|
30
|
+
// result, and also preserves the correct topology (i.e., no crossing edges).
|
|
31
|
+
//
|
|
32
|
+
// Unfortunately this robustness cannot quite be achieved using the strategy
|
|
33
|
+
// outlined above (clipping the two input regions and assembling the
|
|
34
|
+
// resulting edges). Since computed intersection points are not exact, the
|
|
35
|
+
// input geometry passed to S2Builder might contain self-intersections, and
|
|
36
|
+
// these self-intersections cannot be eliminated reliably by snap rounding.
|
|
37
|
+
//
|
|
38
|
+
// So instead, we pass S2Builder the entire set of input edges where at least
|
|
39
|
+
// some portion of each edge belongs to the output boundary. We allow
|
|
40
|
+
// S2Builder to compute the intersection points and snap round the edges
|
|
41
|
+
// (which it does in a way that is guaranteed to preserve the input topology).
|
|
42
|
+
// Then once this is finished, we remove the portions of each edge that would
|
|
43
|
+
// have been clipped if we had done the clipping first. This step only
|
|
44
|
+
// involves deciding whether to keep or discard each edge in the output, since
|
|
45
|
+
// all intersection points have already been resolved, and therefore there is
|
|
46
|
+
// no risk of creating new self-intersections.
|
|
47
|
+
//
|
|
48
|
+
// This is implemented using the following classes:
|
|
49
|
+
//
|
|
50
|
+
// - S2BooleanOperation::Impl: the top-level class that clips each of
|
|
51
|
+
// the two regions to the other region.
|
|
52
|
+
//
|
|
53
|
+
// - CrossingProcessor: a class that processes edge crossings and maintains
|
|
54
|
+
// the necessary state in order to clip the boundary
|
|
55
|
+
// of one region to the interior or exterior of the
|
|
56
|
+
// other region.
|
|
57
|
+
//
|
|
58
|
+
// - EdgeClippingLayer: an S2Builder::Layer that removes graph edges that
|
|
59
|
+
// correspond to clipped portions of input edges, and
|
|
60
|
+
// passes the result to another layer for assembly.
|
|
61
|
+
//
|
|
62
|
+
// - GraphEdgeClipper: a helper class that does the actual work of the
|
|
63
|
+
// EdgeClippingLayer.
|
|
64
|
+
|
|
65
|
+
#include "s2/s2boolean_operation.h"
|
|
66
|
+
|
|
67
|
+
#include <algorithm>
|
|
68
|
+
#include <limits>
|
|
69
|
+
#include <memory>
|
|
70
|
+
#include <utility>
|
|
71
|
+
|
|
72
|
+
#include "s2/util/gtl/btree_map.h"
|
|
73
|
+
#include "s2/third_party/absl/memory/memory.h"
|
|
74
|
+
#include "s2/s2builder.h"
|
|
75
|
+
#include "s2/s2builder_layer.h"
|
|
76
|
+
#include "s2/s2builderutil_snap_functions.h"
|
|
77
|
+
#include "s2/s2contains_point_query.h"
|
|
78
|
+
#include "s2/s2crossing_edge_query.h"
|
|
79
|
+
#include "s2/s2edge_crosser.h"
|
|
80
|
+
#include "s2/s2edge_crossings.h"
|
|
81
|
+
#include "s2/s2measures.h"
|
|
82
|
+
#include "s2/s2predicates.h"
|
|
83
|
+
#include "s2/s2shape_index_measures.h"
|
|
84
|
+
#include "s2/s2shapeutil_visit_crossing_edge_pairs.h"
|
|
85
|
+
|
|
86
|
+
// TODO(ericv): Remove this debugging output at some point.
|
|
87
|
+
extern bool s2builder_verbose;
|
|
88
|
+
|
|
89
|
+
namespace { // Anonymous namespace for helper classes.
|
|
90
|
+
|
|
91
|
+
using absl::make_unique;
|
|
92
|
+
using std::make_pair;
|
|
93
|
+
using std::max;
|
|
94
|
+
using std::min;
|
|
95
|
+
using std::pair;
|
|
96
|
+
using std::swap;
|
|
97
|
+
using std::unique_ptr;
|
|
98
|
+
using std::vector;
|
|
99
|
+
|
|
100
|
+
using EdgeType = S2Builder::EdgeType;
|
|
101
|
+
using SnapFunction = S2Builder::SnapFunction;
|
|
102
|
+
using GraphOptions = S2Builder::GraphOptions;
|
|
103
|
+
using DegenerateEdges = GraphOptions::DegenerateEdges;
|
|
104
|
+
using DuplicateEdges = GraphOptions::DuplicateEdges;
|
|
105
|
+
using SiblingPairs = GraphOptions::SiblingPairs;
|
|
106
|
+
|
|
107
|
+
using Graph = S2Builder::Graph;
|
|
108
|
+
using EdgeId = Graph::EdgeId;
|
|
109
|
+
using VertexId = Graph::VertexId;
|
|
110
|
+
using InputEdgeId = Graph::InputEdgeId;
|
|
111
|
+
using InputEdgeIdSetId = Graph::InputEdgeIdSetId;
|
|
112
|
+
|
|
113
|
+
using PolygonModel = S2BooleanOperation::PolygonModel;
|
|
114
|
+
using PolylineModel = S2BooleanOperation::PolylineModel;
|
|
115
|
+
using Precision = S2BooleanOperation::Precision;
|
|
116
|
+
|
|
117
|
+
// A collection of special InputEdgeIds that allow the GraphEdgeClipper state
|
|
118
|
+
// modifications to be inserted into the list of edge crossings.
|
|
119
|
+
static const InputEdgeId kSetInside = -1;
|
|
120
|
+
static const InputEdgeId kSetInvertB = -2;
|
|
121
|
+
static const InputEdgeId kSetReverseA = -3;
|
|
122
|
+
|
|
123
|
+
// CrossingInputEdge represents an input edge B that crosses some other input
|
|
124
|
+
// edge A. It stores the input edge id of edge B and also whether it crosses
|
|
125
|
+
// edge A from left to right (or vice versa).
|
|
126
|
+
class CrossingInputEdge {
|
|
127
|
+
public:
|
|
128
|
+
// Indicates that input edge "input_id" crosses another edge (from left to
|
|
129
|
+
// right if "left_to_right" is true).
|
|
130
|
+
CrossingInputEdge(InputEdgeId input_id, bool left_to_right)
|
|
131
|
+
: left_to_right_(left_to_right), input_id_(input_id) {
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
InputEdgeId input_id() const { return input_id_; }
|
|
135
|
+
bool left_to_right() const { return left_to_right_; }
|
|
136
|
+
|
|
137
|
+
bool operator<(const CrossingInputEdge& other) const {
|
|
138
|
+
return input_id_ < other.input_id_;
|
|
139
|
+
}
|
|
140
|
+
bool operator<(const InputEdgeId& other) const {
|
|
141
|
+
return input_id_ < other;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private:
|
|
145
|
+
bool left_to_right_ : 1;
|
|
146
|
+
InputEdgeId input_id_ : 31;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// InputEdgeCrossings represents all pairs of intersecting input edges.
|
|
150
|
+
// It is sorted in lexicographic order.
|
|
151
|
+
using InputEdgeCrossings = vector<pair<InputEdgeId, CrossingInputEdge>>;
|
|
152
|
+
|
|
153
|
+
// Given two input edges A and B that intersect, suppose that A maps to a
|
|
154
|
+
// chain of snapped edges A_0, A_1, ..., A_m and B maps to a chain of snapped
|
|
155
|
+
// edges B_0, B_1, ..., B_n. CrossingGraphEdge represents an edge from chain
|
|
156
|
+
// B that shares a vertex with chain A. It is used as a temporary data
|
|
157
|
+
// representation while processing chain A. The arguments are:
|
|
158
|
+
//
|
|
159
|
+
// "id" - the Graph::EdgeId of an edge from chain B.
|
|
160
|
+
// "a_index" - the index of the vertex (A_i) that is shared with chain A.
|
|
161
|
+
// "outgoing" - true if the shared vertex is the first vertex of the B edge.
|
|
162
|
+
// "dst" - the Graph::VertexId of the vertex that is not shared with chain A.
|
|
163
|
+
//
|
|
164
|
+
// Note that if an edge from the B chain shares both vertices with the A
|
|
165
|
+
// chain, there will be two entries: an outgoing edge that treats its first
|
|
166
|
+
// vertex as being shared, and an incoming edge that treats its second vertex
|
|
167
|
+
// as being shared.
|
|
168
|
+
struct CrossingGraphEdge {
|
|
169
|
+
CrossingGraphEdge(EdgeId _id, int _a_index, bool _outgoing, VertexId _dst)
|
|
170
|
+
: id(_id), a_index(_a_index), outgoing(_outgoing), dst(_dst) {
|
|
171
|
+
}
|
|
172
|
+
EdgeId id;
|
|
173
|
+
int a_index;
|
|
174
|
+
bool outgoing;
|
|
175
|
+
VertexId dst;
|
|
176
|
+
};
|
|
177
|
+
using CrossingGraphEdgeVector = absl::InlinedVector<CrossingGraphEdge, 2>;
|
|
178
|
+
|
|
179
|
+
// Returns a vector of EdgeIds sorted by input edge id. When more than one
|
|
180
|
+
// output edge has the same input edge id (i.e., the input edge snapped to a
|
|
181
|
+
// chain of edges), the edges are sorted so that they form a directed edge
|
|
182
|
+
// chain.
|
|
183
|
+
//
|
|
184
|
+
// This function could possibily be moved to S2Builder::Graph, but note that
|
|
185
|
+
// it has special requirements. Namely, duplicate edges and sibling pairs
|
|
186
|
+
// must be kept in order to ensure that every output edge corresponds to
|
|
187
|
+
// exactly one input edge. (See also S2Builder::Graph::GetInputEdgeOrder.)
|
|
188
|
+
static vector<EdgeId> GetInputEdgeChainOrder(
|
|
189
|
+
const Graph& g, const vector<InputEdgeId>& input_ids) {
|
|
190
|
+
|
|
191
|
+
S2_DCHECK(g.options().edge_type() == EdgeType::DIRECTED);
|
|
192
|
+
S2_DCHECK(g.options().duplicate_edges() == DuplicateEdges::KEEP);
|
|
193
|
+
S2_DCHECK(g.options().sibling_pairs() == SiblingPairs::KEEP);
|
|
194
|
+
|
|
195
|
+
// First, sort the edges so that the edges corresponding to each input edge
|
|
196
|
+
// are consecutive. (Each input edge was snapped to a chain of output
|
|
197
|
+
// edges, or two chains in the case of undirected input edges.)
|
|
198
|
+
vector<EdgeId> order = g.GetInputEdgeOrder(input_ids);
|
|
199
|
+
|
|
200
|
+
// Now sort the group of edges corresponding to each input edge in edge
|
|
201
|
+
// chain order (e.g. AB, BC, CD).
|
|
202
|
+
vector<pair<VertexId, EdgeId>> vmap; // Map from source vertex to edge id.
|
|
203
|
+
vector<int> indegree(g.num_vertices()); // Restricted to current input edge.
|
|
204
|
+
for (int end, begin = 0; begin < order.size(); begin = end) {
|
|
205
|
+
// Gather the edges that came from a single input edge.
|
|
206
|
+
InputEdgeId input_id = input_ids[order[begin]];
|
|
207
|
+
for (end = begin; end < order.size(); ++end) {
|
|
208
|
+
if (input_ids[order[end]] != input_id) break;
|
|
209
|
+
}
|
|
210
|
+
if (end - begin == 1) continue;
|
|
211
|
+
|
|
212
|
+
// Build a map from the source vertex of each edge to its edge id,
|
|
213
|
+
// and also compute the indegree at each vertex considering only the edges
|
|
214
|
+
// that came from the current input edge.
|
|
215
|
+
for (int i = begin; i < end; ++i) {
|
|
216
|
+
EdgeId e = order[i];
|
|
217
|
+
vmap.push_back(make_pair(g.edge(e).first, e));
|
|
218
|
+
indegree[g.edge(e).second] += 1;
|
|
219
|
+
}
|
|
220
|
+
std::sort(vmap.begin(), vmap.end());
|
|
221
|
+
|
|
222
|
+
// Find the starting edge for building the edge chain.
|
|
223
|
+
EdgeId next = g.num_edges();
|
|
224
|
+
for (int i = begin; i < end; ++i) {
|
|
225
|
+
EdgeId e = order[i];
|
|
226
|
+
if (indegree[g.edge(e).first] == 0) next = e;
|
|
227
|
+
}
|
|
228
|
+
// Build the edge chain.
|
|
229
|
+
for (int i = begin; ;) {
|
|
230
|
+
order[i] = next;
|
|
231
|
+
VertexId v = g.edge(next).second;
|
|
232
|
+
indegree[v] = 0; // Clear as we go along.
|
|
233
|
+
if (++i == end) break;
|
|
234
|
+
auto out = lower_bound(vmap.begin(), vmap.end(), make_pair(v, 0));
|
|
235
|
+
S2_DCHECK_EQ(v, out->first);
|
|
236
|
+
next = out->second;
|
|
237
|
+
}
|
|
238
|
+
vmap.clear();
|
|
239
|
+
}
|
|
240
|
+
return order;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Given a set of clipping instructions encoded as a set of InputEdgeCrossings,
|
|
244
|
+
// GraphEdgeClipper determines which graph edges correspond to clipped
|
|
245
|
+
// portions of input edges and removes them.
|
|
246
|
+
//
|
|
247
|
+
// The clipping model is as follows. The input consists of edge chains. The
|
|
248
|
+
// clipper maintains an "inside" boolean state as it clips each chain, and
|
|
249
|
+
// toggles this state whenever an input edge is crossed. Any edges that are
|
|
250
|
+
// deemed to be "outside" after clipping are removed.
|
|
251
|
+
//
|
|
252
|
+
// The "inside" state can be reset when necessary (e.g., when jumping to the
|
|
253
|
+
// start of a new chain) by adding a special crossing marked kSetInside.
|
|
254
|
+
// There are also two other special "crossings" that modify the clipping
|
|
255
|
+
// parameters: kSetInvertB specifies that edges should be clipped to the
|
|
256
|
+
// exterior of the other region, and kSetReverseA specifies that edges should
|
|
257
|
+
// be reversed before emitting them (which is needed to implement difference
|
|
258
|
+
// operations).
|
|
259
|
+
class GraphEdgeClipper {
|
|
260
|
+
public:
|
|
261
|
+
// "input_dimensions" is a vector specifying the dimension of each input
|
|
262
|
+
// edge (0, 1, or 2). "input_crossings" is the set of all crossings to be
|
|
263
|
+
// used when clipping the edges of "g", sorted in lexicographic order.
|
|
264
|
+
//
|
|
265
|
+
// The clipped set of edges and their corresponding set of input edge ids
|
|
266
|
+
// are returned in "new_edges" and "new_input_edge_ids". (These can be used
|
|
267
|
+
// to construct a new S2Builder::Graph.)
|
|
268
|
+
GraphEdgeClipper(const Graph& g, const vector<int8>& input_dimensions,
|
|
269
|
+
const InputEdgeCrossings& input_crossings,
|
|
270
|
+
vector<Graph::Edge>* new_edges,
|
|
271
|
+
vector<InputEdgeIdSetId>* new_input_edge_ids);
|
|
272
|
+
void Run();
|
|
273
|
+
|
|
274
|
+
private:
|
|
275
|
+
void AddEdge(Graph::Edge edge, InputEdgeId input_edge_id);
|
|
276
|
+
void GatherIncidentEdges(
|
|
277
|
+
const vector<VertexId>& a, int ai,
|
|
278
|
+
const vector<CrossingInputEdge>& b_input_edges,
|
|
279
|
+
vector<CrossingGraphEdgeVector>* b_edges) const;
|
|
280
|
+
int GetCrossedVertexIndex(
|
|
281
|
+
const vector<VertexId>& a, const CrossingGraphEdgeVector& b,
|
|
282
|
+
bool left_to_right) const;
|
|
283
|
+
int GetVertexRank(const CrossingGraphEdge& e) const;
|
|
284
|
+
bool EdgeChainOnLeft(const vector<VertexId>& a,
|
|
285
|
+
EdgeId b_first, EdgeId b_last) const;
|
|
286
|
+
|
|
287
|
+
const Graph& g_;
|
|
288
|
+
Graph::VertexInMap in_;
|
|
289
|
+
Graph::VertexOutMap out_;
|
|
290
|
+
const vector<int8>& input_dimensions_;
|
|
291
|
+
const InputEdgeCrossings& input_crossings_;
|
|
292
|
+
vector<Graph::Edge>* new_edges_;
|
|
293
|
+
vector<InputEdgeIdSetId>* new_input_edge_ids_;
|
|
294
|
+
|
|
295
|
+
// Every graph edge is associated with exactly one input edge in our case,
|
|
296
|
+
// which means that we can declare g_.input_edge_id_set_ids() as a vector of
|
|
297
|
+
// InputEdgeIds rather than a vector of InputEdgeIdSetIds. (This also takes
|
|
298
|
+
// advantage of the fact that IdSetLexicon represents a singleton set as the
|
|
299
|
+
// value of its single element.)
|
|
300
|
+
const vector<InputEdgeId>& input_ids_;
|
|
301
|
+
|
|
302
|
+
vector<EdgeId> order_; // Graph edges sorted in input edge id order.
|
|
303
|
+
vector<int> rank_; // The rank of each graph edge within order_.
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
GraphEdgeClipper::GraphEdgeClipper(
|
|
307
|
+
const Graph& g, const vector<int8>& input_dimensions,
|
|
308
|
+
const InputEdgeCrossings& input_crossings,
|
|
309
|
+
vector<Graph::Edge>* new_edges,
|
|
310
|
+
vector<InputEdgeIdSetId>* new_input_edge_ids)
|
|
311
|
+
: g_(g), in_(g), out_(g),
|
|
312
|
+
input_dimensions_(input_dimensions),
|
|
313
|
+
input_crossings_(input_crossings),
|
|
314
|
+
new_edges_(new_edges),
|
|
315
|
+
new_input_edge_ids_(new_input_edge_ids),
|
|
316
|
+
input_ids_(g.input_edge_id_set_ids()),
|
|
317
|
+
order_(GetInputEdgeChainOrder(g_, input_ids_)),
|
|
318
|
+
rank_(order_.size()) {
|
|
319
|
+
for (int i = 0; i < order_.size(); ++i) {
|
|
320
|
+
rank_[order_[i]] = i;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
inline void GraphEdgeClipper::AddEdge(Graph::Edge edge,
|
|
325
|
+
InputEdgeId input_edge_id) {
|
|
326
|
+
new_edges_->push_back(edge);
|
|
327
|
+
new_input_edge_ids_->push_back(input_edge_id);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
void GraphEdgeClipper::Run() {
|
|
331
|
+
// Declare vectors here and reuse them to avoid reallocation.
|
|
332
|
+
vector<VertexId> a_vertices;
|
|
333
|
+
vector<int> a_num_crossings;
|
|
334
|
+
vector<bool> a_isolated;
|
|
335
|
+
vector<CrossingInputEdge> b_input_edges;
|
|
336
|
+
vector<CrossingGraphEdgeVector> b_edges;
|
|
337
|
+
|
|
338
|
+
bool inside = false;
|
|
339
|
+
bool invert_b = false;
|
|
340
|
+
bool reverse_a = false;
|
|
341
|
+
auto next = input_crossings_.begin();
|
|
342
|
+
for (int i = 0; i < order_.size(); ++i) {
|
|
343
|
+
// For each input edge (the "A" input edge), gather all the input edges
|
|
344
|
+
// that cross it (the "B" input edges).
|
|
345
|
+
InputEdgeId a_input_id = input_ids_[order_[i]];
|
|
346
|
+
const Graph::Edge& edge0 = g_.edge(order_[i]);
|
|
347
|
+
b_input_edges.clear();
|
|
348
|
+
for (; next != input_crossings_.end(); ++next) {
|
|
349
|
+
if (next->first != a_input_id) break;
|
|
350
|
+
if (next->second.input_id() >= 0) {
|
|
351
|
+
b_input_edges.push_back(next->second);
|
|
352
|
+
} else if (next->second.input_id() == kSetInside) {
|
|
353
|
+
inside = next->second.left_to_right();
|
|
354
|
+
} else if (next->second.input_id() == kSetInvertB) {
|
|
355
|
+
invert_b = next->second.left_to_right();
|
|
356
|
+
} else {
|
|
357
|
+
S2_DCHECK_EQ(next->second.input_id(), kSetReverseA);
|
|
358
|
+
reverse_a = next->second.left_to_right();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Optimization for degenerate edges.
|
|
362
|
+
// TODO(ericv): If the output layer for this edge dimension specifies
|
|
363
|
+
// DegenerateEdges::DISCARD, then remove the edge here.
|
|
364
|
+
if (edge0.first == edge0.second) {
|
|
365
|
+
inside ^= (b_input_edges.size() & 1);
|
|
366
|
+
AddEdge(edge0, a_input_id);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
// Optimization for the case where there are no crossings.
|
|
370
|
+
if (b_input_edges.empty()) {
|
|
371
|
+
// In general the caller only passes edges that are part of the output
|
|
372
|
+
// (i.e., we could S2_DCHECK(inside) here). The one exception is for
|
|
373
|
+
// polyline/polygon operations, where the polygon edges are needed to
|
|
374
|
+
// compute the polyline output but are not emitted themselves.
|
|
375
|
+
if (inside) {
|
|
376
|
+
AddEdge(reverse_a ? Graph::reverse(edge0) : edge0, a_input_id);
|
|
377
|
+
}
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
// Walk along the chain of snapped edges for input edge A, and at each
|
|
381
|
+
// vertex collect all the incident edges that belong to one of the
|
|
382
|
+
// crossing edge chains (the "B" input edges).
|
|
383
|
+
a_vertices.clear();
|
|
384
|
+
a_vertices.push_back(edge0.first);
|
|
385
|
+
b_edges.clear();
|
|
386
|
+
b_edges.resize(b_input_edges.size());
|
|
387
|
+
GatherIncidentEdges(a_vertices, 0, b_input_edges, &b_edges);
|
|
388
|
+
for (; i < order_.size() && input_ids_[order_[i]] == a_input_id; ++i) {
|
|
389
|
+
a_vertices.push_back(g_.edge(order_[i]).second);
|
|
390
|
+
GatherIncidentEdges(a_vertices, a_vertices.size() - 1, b_input_edges,
|
|
391
|
+
&b_edges);
|
|
392
|
+
}
|
|
393
|
+
--i;
|
|
394
|
+
if (s2builder_verbose) {
|
|
395
|
+
std::cout << "input edge " << a_input_id << " (inside=" << inside << "):";
|
|
396
|
+
for (VertexId id : a_vertices) std::cout << " " << id;
|
|
397
|
+
}
|
|
398
|
+
// Now for each B edge chain, decide which vertex of the A chain it
|
|
399
|
+
// crosses, and keep track of the number of signed crossings at each A
|
|
400
|
+
// vertex. The sign of a crossing depends on whether the other edge
|
|
401
|
+
// crosses from left to right or right to left.
|
|
402
|
+
//
|
|
403
|
+
// This would not be necessary if all calculations were done in exact
|
|
404
|
+
// arithmetic, because crossings would have strictly alternating signs.
|
|
405
|
+
// But because we have already snapped the result, some crossing locations
|
|
406
|
+
// are ambiguous, and GetCrossedVertexIndex() handles this by choosing a
|
|
407
|
+
// candidate vertex arbitrarily. The end result is that rarely, we may
|
|
408
|
+
// see two crossings in a row with the same sign. We correct for this by
|
|
409
|
+
// adding extra output edges that essentially link up the crossings in the
|
|
410
|
+
// correct (alternating sign) order. Compared to the "correct" behavior,
|
|
411
|
+
// the only difference is that we have added some extra sibling pairs
|
|
412
|
+
// (consisting of an edge and its corresponding reverse edge) which do not
|
|
413
|
+
// affect the result.
|
|
414
|
+
a_num_crossings.clear();
|
|
415
|
+
a_num_crossings.resize(a_vertices.size());
|
|
416
|
+
a_isolated.clear();
|
|
417
|
+
a_isolated.resize(a_vertices.size());
|
|
418
|
+
for (int bi = 0; bi < b_input_edges.size(); ++bi) {
|
|
419
|
+
bool left_to_right = b_input_edges[bi].left_to_right();
|
|
420
|
+
int a_index = GetCrossedVertexIndex(a_vertices, b_edges[bi],
|
|
421
|
+
left_to_right);
|
|
422
|
+
if (a_index >= 0) {
|
|
423
|
+
if (s2builder_verbose) {
|
|
424
|
+
std::cout << std::endl << " " << "b input edge "
|
|
425
|
+
<< b_input_edges[bi].input_id() << " (l2r=" << left_to_right
|
|
426
|
+
<< ", crossing=" << a_vertices[a_index] << ")";
|
|
427
|
+
for (const auto& x : b_edges[bi]) {
|
|
428
|
+
const Graph::Edge& e = g_.edge(x.id);
|
|
429
|
+
std::cout << " (" << e.first << ", " << e.second << ")";
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Keep track of the number of signed crossings (see above).
|
|
433
|
+
bool is_line = input_dimensions_[b_input_edges[bi].input_id()] == 1;
|
|
434
|
+
int sign = is_line ? 0 : (left_to_right == invert_b) ? -1 : 1;
|
|
435
|
+
a_num_crossings[a_index] += sign;
|
|
436
|
+
|
|
437
|
+
// Any polyline or polygon vertex that has at least one crossing but no
|
|
438
|
+
// adjacent emitted edge may be emitted as an isolated vertex.
|
|
439
|
+
a_isolated[a_index] = true;
|
|
440
|
+
} else {
|
|
441
|
+
// TODO(b/112043775): fix this condition.
|
|
442
|
+
S2_LOG(DFATAL) << "Failed to get crossed vertex index.";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (s2builder_verbose) std::cout << std::endl;
|
|
446
|
+
|
|
447
|
+
// Finally, we iterate through the A edge chain, keeping track of the
|
|
448
|
+
// number of signed crossings as we go along. The "multiplicity" is
|
|
449
|
+
// defined as the cumulative number of signed crossings, and indicates how
|
|
450
|
+
// many edges should be output (and in which direction) in order to link
|
|
451
|
+
// up the edge crossings in the correct order. (The multiplicity is
|
|
452
|
+
// almost always either 0 or 1 except in very rare cases.)
|
|
453
|
+
int multiplicity = inside + a_num_crossings[0];
|
|
454
|
+
for (int ai = 1; ai < a_vertices.size(); ++ai) {
|
|
455
|
+
if (multiplicity != 0) {
|
|
456
|
+
a_isolated[ai - 1] = a_isolated[ai] = false;
|
|
457
|
+
}
|
|
458
|
+
int edge_count = reverse_a ? -multiplicity : multiplicity;
|
|
459
|
+
// Output any forward edges required.
|
|
460
|
+
for (int i = 0; i < edge_count; ++i) {
|
|
461
|
+
AddEdge(Graph::Edge(a_vertices[ai - 1], a_vertices[ai]), a_input_id);
|
|
462
|
+
}
|
|
463
|
+
// Output any reverse edges required.
|
|
464
|
+
for (int i = edge_count; i < 0; ++i) {
|
|
465
|
+
AddEdge(Graph::Edge(a_vertices[ai], a_vertices[ai - 1]), a_input_id);
|
|
466
|
+
}
|
|
467
|
+
multiplicity += a_num_crossings[ai];
|
|
468
|
+
}
|
|
469
|
+
// Multiplicities other than 0 or 1 can only occur in the edge interior.
|
|
470
|
+
S2_DCHECK(multiplicity == 0 || multiplicity == 1);
|
|
471
|
+
inside = (multiplicity != 0);
|
|
472
|
+
|
|
473
|
+
// Output any isolated polyline vertices.
|
|
474
|
+
// TODO(ericv): Only do this if an output layer wants degenerate edges.
|
|
475
|
+
if (input_dimensions_[a_input_id] != 0) {
|
|
476
|
+
for (int ai = 0; ai < a_vertices.size(); ++ai) {
|
|
477
|
+
if (a_isolated[ai]) {
|
|
478
|
+
AddEdge(Graph::Edge(a_vertices[ai], a_vertices[ai]), a_input_id);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Given the vertices of the snapped edge chain for an input edge A and the
|
|
486
|
+
// set of input edges B that cross input edge A, this method gathers all of
|
|
487
|
+
// the snapped edges of B that are incident to a given snapped vertex of A.
|
|
488
|
+
// The incident edges for each input edge of B are appended to a separate
|
|
489
|
+
// output vector. (A and B can refer to either the input edge or the
|
|
490
|
+
// corresponding snapped edge chain.)
|
|
491
|
+
void GraphEdgeClipper::GatherIncidentEdges(
|
|
492
|
+
const vector<VertexId>& a, int ai,
|
|
493
|
+
const vector<CrossingInputEdge>& b_input_edges,
|
|
494
|
+
vector<CrossingGraphEdgeVector>* b_edges) const {
|
|
495
|
+
// Examine all of the edges incident to the given vertex of A. If any edge
|
|
496
|
+
// comes from a B input edge, append it to the appropriate vector.
|
|
497
|
+
S2_DCHECK_EQ(b_input_edges.size(), b_edges->size());
|
|
498
|
+
for (EdgeId e : in_.edge_ids(a[ai])) {
|
|
499
|
+
InputEdgeId id = input_ids_[e];
|
|
500
|
+
auto it = lower_bound(b_input_edges.begin(), b_input_edges.end(), id);
|
|
501
|
+
if (it != b_input_edges.end() && it->input_id() == id) {
|
|
502
|
+
auto& edges = (*b_edges)[it - b_input_edges.begin()];
|
|
503
|
+
edges.push_back(CrossingGraphEdge(e, ai, false, g_.edge(e).first));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
for (EdgeId e : out_.edge_ids(a[ai])) {
|
|
507
|
+
InputEdgeId id = input_ids_[e];
|
|
508
|
+
auto it = lower_bound(b_input_edges.begin(), b_input_edges.end(), id);
|
|
509
|
+
if (it != b_input_edges.end() && it->input_id() == id) {
|
|
510
|
+
auto& edges = (*b_edges)[it - b_input_edges.begin()];
|
|
511
|
+
edges.push_back(CrossingGraphEdge(e, ai, true, g_.edge(e).second));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Returns the "vertex rank" of the shared vertex associated with the given
|
|
517
|
+
// CrossingGraphEdge. Recall that graph edges are sorted in input edge order,
|
|
518
|
+
// and that the rank of an edge is its position in this order (rank_[e]).
|
|
519
|
+
// VertexRank(e) is defined such that VertexRank(e.src) == rank_[e] and
|
|
520
|
+
// VertexRank(e.dst) == rank_[e] + 1. Note that the concept of "vertex rank"
|
|
521
|
+
// is only defined within a single edge chain (since different edge chains can
|
|
522
|
+
// have overlapping vertex ranks).
|
|
523
|
+
int GraphEdgeClipper::GetVertexRank(const CrossingGraphEdge& e) const {
|
|
524
|
+
return rank_[e.id] + !e.outgoing;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Given an edge chain A that is crossed by another edge chain B (where
|
|
528
|
+
// "left_to_right" indicates whether B crosses A from left to right), this
|
|
529
|
+
// method decides which vertex of A the crossing takes place at. The
|
|
530
|
+
// parameters are the vertices of the A chain ("a") and the set of edges in
|
|
531
|
+
// the B chain ("b") that are incident to vertices of A. The B chain edges
|
|
532
|
+
// are sorted in increasing order of (a_index, outgoing) tuple.
|
|
533
|
+
int GraphEdgeClipper::GetCrossedVertexIndex(
|
|
534
|
+
const vector<VertexId>& a, const CrossingGraphEdgeVector& b,
|
|
535
|
+
bool left_to_right) const {
|
|
536
|
+
S2_DCHECK(!a.empty());
|
|
537
|
+
S2_DCHECK(!b.empty());
|
|
538
|
+
|
|
539
|
+
// The reason this calculation is tricky is that after snapping, the A and B
|
|
540
|
+
// chains may meet and separate several times. For example, if B crosses A
|
|
541
|
+
// from left to right, then B may touch A, make an excursion to the left of
|
|
542
|
+
// A, come back to A, then make an excursion to the right of A and come back
|
|
543
|
+
// to A again, like this:
|
|
544
|
+
//
|
|
545
|
+
// *--B--*-\ /-*-\
|
|
546
|
+
// B-\ /-B B-\ 6 7 8 9
|
|
547
|
+
// *--A--*--A--*-A,B-*--A--*--A--*-A,B-*--A--*--A--*-A,B-*
|
|
548
|
+
// 0 1 2 3 4 5 \-B B-/
|
|
549
|
+
// \-*-/
|
|
550
|
+
//
|
|
551
|
+
// (where "*" is a vertex, and "A" and "B" are edge labels). Note that B
|
|
552
|
+
// may also follow A for one or more edges whenever they touch (e.g. between
|
|
553
|
+
// vertices 2 and 3). In this case the only vertices of A where the
|
|
554
|
+
// crossing could take place are 5 and 6, i.e. after all excursions of B to
|
|
555
|
+
// the left of A, and before all excursions of B to the right of A.
|
|
556
|
+
//
|
|
557
|
+
// Other factors to consider are that the portion of B before and/or after
|
|
558
|
+
// the crossing may be degenerate, and some or all of the B edges may be
|
|
559
|
+
// reversed relative to the A edges.
|
|
560
|
+
|
|
561
|
+
// First, check whether edge A is degenerate.
|
|
562
|
+
int n = a.size();
|
|
563
|
+
if (n == 1) return 0;
|
|
564
|
+
|
|
565
|
+
// If edge chain B is incident to only one vertex of A, we're done.
|
|
566
|
+
if (b[0].a_index == b.back().a_index) return b[0].a_index;
|
|
567
|
+
|
|
568
|
+
// Determine whether the B chain visits the first and last vertices that it
|
|
569
|
+
// shares with the A chain in the same order or the reverse order. This is
|
|
570
|
+
// only needed to implement one special case (see below).
|
|
571
|
+
bool b_reversed = GetVertexRank(b[0]) > GetVertexRank(b.back());
|
|
572
|
+
|
|
573
|
+
// Examine each incident B edge and use it to narrow the range of positions
|
|
574
|
+
// where the crossing could occur in the B chain. Vertex positions are
|
|
575
|
+
// represented as a range [lo, hi] of vertex ranks in the B chain (see
|
|
576
|
+
// GetVertexRank).
|
|
577
|
+
//
|
|
578
|
+
// Note that if an edge of B is incident to the first or last vertex of A,
|
|
579
|
+
// we can't test which side of the A chain it is on. (An s2pred::Sign test
|
|
580
|
+
// doesn't work; e.g. if the B edge is XY and the first edge of A is YZ,
|
|
581
|
+
// then snapping can change the sign of XYZ while maintaining topological
|
|
582
|
+
// guarantees.) There can be up to 4 such edges (one incoming and one
|
|
583
|
+
// outgoing edge at each endpoint of A). Two of these edges logically
|
|
584
|
+
// extend past the end of the A chain and place no restrictions on the
|
|
585
|
+
// crossing vertex. The other two edges define the ends of the subchain
|
|
586
|
+
// where B shares vertices with A. We save these edges in order to handle a
|
|
587
|
+
// special case (see below).
|
|
588
|
+
int lo = -1, hi = order_.size(); // Vertex ranks of acceptable crossings
|
|
589
|
+
EdgeId b_first = -1, b_last = -1; // "b" subchain connecting "a" endpoints
|
|
590
|
+
for (const auto& e : b) {
|
|
591
|
+
int ai = e.a_index;
|
|
592
|
+
if (ai == 0) {
|
|
593
|
+
if (e.outgoing != b_reversed && e.dst != a[1]) b_first = e.id;
|
|
594
|
+
} else if (ai == n - 1) {
|
|
595
|
+
if (e.outgoing == b_reversed && e.dst != a[n - 2]) b_last = e.id;
|
|
596
|
+
} else {
|
|
597
|
+
// This B edge is incident to an interior vertex of the A chain. First
|
|
598
|
+
// check whether this edge is identical (or reversed) to an edge in the
|
|
599
|
+
// A chain, in which case it does not create any restrictions.
|
|
600
|
+
if (e.dst == a[ai - 1] || e.dst == a[ai + 1]) continue;
|
|
601
|
+
|
|
602
|
+
// Otherwise we can test which side of the A chain the edge lies on.
|
|
603
|
+
bool on_left = s2pred::OrderedCCW(g_.vertex(a[ai + 1]), g_.vertex(e.dst),
|
|
604
|
+
g_.vertex(a[ai - 1]), g_.vertex(a[ai]));
|
|
605
|
+
|
|
606
|
+
// Every B edge that is incident to an interior vertex of the A chain
|
|
607
|
+
// places some restriction on where the crossing vertex could be.
|
|
608
|
+
if (left_to_right == on_left) {
|
|
609
|
+
// This is a pre-crossing edge, so the crossing cannot be before the
|
|
610
|
+
// destination vertex of this edge. (For example, the input B edge
|
|
611
|
+
// crosses the input A edge from left to right and this edge of the B
|
|
612
|
+
// chain is to the left of the A chain.)
|
|
613
|
+
lo = max(lo, rank_[e.id] + 1);
|
|
614
|
+
} else {
|
|
615
|
+
// This is a post-crossing edge, so the crossing cannot be after the
|
|
616
|
+
// source vertex of this edge.
|
|
617
|
+
hi = min(hi, rank_[e.id]);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// There is one special case. If a subchain of B connects the first and
|
|
622
|
+
// last vertices of A, then together with the edges of A this forms a loop
|
|
623
|
+
// whose orientation can be tested to determine whether B is on the left or
|
|
624
|
+
// right side of A. This is only possible (and only necessary) if the B
|
|
625
|
+
// subchain does not include any interior vertices of A, since otherwise the
|
|
626
|
+
// B chain might cross from one side of A to the other.
|
|
627
|
+
//
|
|
628
|
+
// Note that it would be possible to avoid this test in some situations by
|
|
629
|
+
// checking whether either endpoint of the A chain has two incident B edges,
|
|
630
|
+
// in which case we could check which side of the B chain the A edge is on
|
|
631
|
+
// and use this to limit the possible crossing locations.
|
|
632
|
+
if (b_first >= 0 && b_last >= 0) {
|
|
633
|
+
// The B subchain connects the first and last vertices of A. Test whether
|
|
634
|
+
// the chain includes any interior vertices of A. We do this indirectly
|
|
635
|
+
// by testing whether any edge of B has restricted the range of allowable
|
|
636
|
+
// crossing vertices (since any interior edge of the B subchain incident
|
|
637
|
+
// to any interior edge of A is guaranteed to do so).
|
|
638
|
+
int min_rank = order_.size(), max_rank = -1;
|
|
639
|
+
for (const auto& e : b) {
|
|
640
|
+
min_rank = min(min_rank, GetVertexRank(e));
|
|
641
|
+
max_rank = max(max_rank, GetVertexRank(e));
|
|
642
|
+
}
|
|
643
|
+
if (lo <= min_rank && hi >= max_rank) {
|
|
644
|
+
// The B subchain is not incident to any interior vertex of A.
|
|
645
|
+
// Swap the edges if necessary so that they are in B chain order.
|
|
646
|
+
if (b_reversed) swap(b_first, b_last);
|
|
647
|
+
bool on_left = EdgeChainOnLeft(a, b_first, b_last);
|
|
648
|
+
if (left_to_right == on_left) {
|
|
649
|
+
lo = max(lo, rank_[b_last] + 1);
|
|
650
|
+
} else {
|
|
651
|
+
hi = min(hi, rank_[b_first]);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Otherwise we choose the smallest shared VertexId in the acceptable range,
|
|
657
|
+
// in order to ensure that both chains choose the same crossing vertex.
|
|
658
|
+
int best = -1;
|
|
659
|
+
S2_DCHECK_LE(lo, hi);
|
|
660
|
+
for (const auto& e : b) {
|
|
661
|
+
int ai = e.a_index;
|
|
662
|
+
int vrank = GetVertexRank(e);
|
|
663
|
+
if (vrank >= lo && vrank <= hi && (best < 0 || a[ai] < a[best])) {
|
|
664
|
+
best = ai;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return best;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Given edge chains A and B that form a loop (after possibly reversing the
|
|
671
|
+
// direction of chain B), returns true if chain B is to the left of chain A.
|
|
672
|
+
// Chain A is given as a sequence of vertices, while chain B is specified as
|
|
673
|
+
// the first and last edges of the chain.
|
|
674
|
+
bool GraphEdgeClipper::EdgeChainOnLeft(
|
|
675
|
+
const vector<VertexId>& a, EdgeId b_first, EdgeId b_last) const {
|
|
676
|
+
// Gather all the interior vertices of the B subchain.
|
|
677
|
+
vector<VertexId> loop;
|
|
678
|
+
for (int i = rank_[b_first]; i < rank_[b_last]; ++i) {
|
|
679
|
+
loop.push_back(g_.edge(order_[i]).second);
|
|
680
|
+
}
|
|
681
|
+
// Possibly reverse the chain so that it forms a loop when "a" is appended.
|
|
682
|
+
if (g_.edge(b_last).second != a[0]) std::reverse(loop.begin(), loop.end());
|
|
683
|
+
loop.insert(loop.end(), a.begin(), a.end());
|
|
684
|
+
// Duplicate the first two vertices to simplify vertex indexing.
|
|
685
|
+
for (int j = 0; j < 2; j++) {
|
|
686
|
+
loop.insert(loop.end(), *(loop.begin() + j));
|
|
687
|
+
}
|
|
688
|
+
// Now B is to the left of A if and only if the loop is counterclockwise.
|
|
689
|
+
double sum = 0;
|
|
690
|
+
for (int i = 2; i < loop.size(); ++i) {
|
|
691
|
+
sum += S2::TurnAngle(g_.vertex(loop[i - 2]), g_.vertex(loop[i - 1]),
|
|
692
|
+
g_.vertex(loop[i]));
|
|
693
|
+
}
|
|
694
|
+
return sum > 0;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Given a set of clipping instructions encoded as a set of intersections
|
|
698
|
+
// between input edges, EdgeClippingLayer determines which graph edges
|
|
699
|
+
// correspond to clipped portions of input edges and removes them. It
|
|
700
|
+
// assembles the remaining edges into a new S2Builder::Graph and passes the
|
|
701
|
+
// result to the given output layer for assembly.
|
|
702
|
+
class EdgeClippingLayer : public S2Builder::Layer {
|
|
703
|
+
public:
|
|
704
|
+
EdgeClippingLayer(const vector<unique_ptr<S2Builder::Layer>>* layers,
|
|
705
|
+
const vector<int8>* input_dimensions,
|
|
706
|
+
const InputEdgeCrossings* input_crossings)
|
|
707
|
+
: layers_(*layers),
|
|
708
|
+
input_dimensions_(*input_dimensions),
|
|
709
|
+
input_crossings_(*input_crossings) {
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Layer interface:
|
|
713
|
+
GraphOptions graph_options() const override;
|
|
714
|
+
void Build(const Graph& g, S2Error* error) override;
|
|
715
|
+
|
|
716
|
+
private:
|
|
717
|
+
const vector<unique_ptr<S2Builder::Layer>>& layers_;
|
|
718
|
+
const vector<int8>& input_dimensions_;
|
|
719
|
+
const InputEdgeCrossings& input_crossings_;
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
GraphOptions EdgeClippingLayer::graph_options() const {
|
|
723
|
+
// We keep all edges, including degenerate ones, so that we can figure out
|
|
724
|
+
// the correspondence between input edge crossings and output edge
|
|
725
|
+
// crossings.
|
|
726
|
+
return GraphOptions(EdgeType::DIRECTED, DegenerateEdges::KEEP,
|
|
727
|
+
DuplicateEdges::KEEP, SiblingPairs::KEEP);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Helper function (in anonymous namespace) to create an S2Builder::Graph from
|
|
731
|
+
// a vector of edges.
|
|
732
|
+
Graph MakeGraph(
|
|
733
|
+
const Graph& g, GraphOptions* options, vector<Graph::Edge>* new_edges,
|
|
734
|
+
vector<InputEdgeIdSetId>* new_input_edge_ids,
|
|
735
|
+
IdSetLexicon* new_input_edge_id_set_lexicon, S2Error* error) {
|
|
736
|
+
if (options->edge_type() == EdgeType::UNDIRECTED) {
|
|
737
|
+
// Create a reversed edge for every edge.
|
|
738
|
+
int n = new_edges->size();
|
|
739
|
+
new_edges->reserve(2 * n);
|
|
740
|
+
new_input_edge_ids->reserve(2 * n);
|
|
741
|
+
for (int i = 0; i < n; ++i) {
|
|
742
|
+
new_edges->push_back(Graph::reverse((*new_edges)[i]));
|
|
743
|
+
new_input_edge_ids->push_back(IdSetLexicon::EmptySetId());
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
Graph::ProcessEdges(options, new_edges, new_input_edge_ids,
|
|
747
|
+
new_input_edge_id_set_lexicon, error);
|
|
748
|
+
return Graph(*options, &g.vertices(), new_edges, new_input_edge_ids,
|
|
749
|
+
new_input_edge_id_set_lexicon, &g.label_set_ids(),
|
|
750
|
+
&g.label_set_lexicon(), g.is_full_polygon_predicate());
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
void EdgeClippingLayer::Build(const Graph& g, S2Error* error) {
|
|
754
|
+
// The bulk of the work is handled by GraphEdgeClipper.
|
|
755
|
+
vector<Graph::Edge> new_edges;
|
|
756
|
+
vector<InputEdgeIdSetId> new_input_edge_ids;
|
|
757
|
+
// Destroy the GraphEdgeClipper immediately to save memory.
|
|
758
|
+
GraphEdgeClipper(g, input_dimensions_, input_crossings_,
|
|
759
|
+
&new_edges, &new_input_edge_ids).Run();
|
|
760
|
+
if (s2builder_verbose) {
|
|
761
|
+
std::cout << "Edges after clipping: " << std::endl;
|
|
762
|
+
for (int i = 0; i < new_edges.size(); ++i) {
|
|
763
|
+
std::cout << " " << new_input_edge_ids[i] << " (" << new_edges[i].first
|
|
764
|
+
<< ", " << new_edges[i].second << ")" << std::endl;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
// Construct one or more graphs from the clipped edges and pass them to the
|
|
768
|
+
// given output layer(s).
|
|
769
|
+
IdSetLexicon new_input_edge_id_set_lexicon;
|
|
770
|
+
if (layers_.size() == 1) {
|
|
771
|
+
GraphOptions options = layers_[0]->graph_options();
|
|
772
|
+
Graph new_graph = MakeGraph(g, &options, &new_edges, &new_input_edge_ids,
|
|
773
|
+
&new_input_edge_id_set_lexicon, error);
|
|
774
|
+
layers_[0]->Build(new_graph, error);
|
|
775
|
+
} else {
|
|
776
|
+
// The Graph objects must be valid until the last Build() call completes,
|
|
777
|
+
// so we store all of the graph data in arrays with 3 elements.
|
|
778
|
+
S2_DCHECK_EQ(3, layers_.size());
|
|
779
|
+
vector<Graph::Edge> layer_edges[3];
|
|
780
|
+
vector<InputEdgeIdSetId> layer_input_edge_ids[3];
|
|
781
|
+
S2Builder::GraphOptions layer_options[3];
|
|
782
|
+
vector<S2Builder::Graph> layer_graphs; // No default constructor.
|
|
783
|
+
layer_graphs.reserve(3);
|
|
784
|
+
// Separate the edges according to their dimension.
|
|
785
|
+
for (int i = 0; i < new_edges.size(); ++i) {
|
|
786
|
+
int d = input_dimensions_[new_input_edge_ids[i]];
|
|
787
|
+
layer_edges[d].push_back(new_edges[i]);
|
|
788
|
+
layer_input_edge_ids[d].push_back(new_input_edge_ids[i]);
|
|
789
|
+
}
|
|
790
|
+
// Clear variables to save space.
|
|
791
|
+
vector<Graph::Edge>().swap(new_edges);
|
|
792
|
+
vector<InputEdgeIdSetId>().swap(new_input_edge_ids);
|
|
793
|
+
for (int d = 0; d < 3; ++d) {
|
|
794
|
+
layer_options[d] = layers_[d]->graph_options();
|
|
795
|
+
layer_graphs.push_back(MakeGraph(
|
|
796
|
+
g, &layer_options[d], &layer_edges[d], &layer_input_edge_ids[d],
|
|
797
|
+
&new_input_edge_id_set_lexicon, error));
|
|
798
|
+
layers_[d]->Build(layer_graphs[d], error);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
} // namespace
|
|
804
|
+
|
|
805
|
+
class S2BooleanOperation::Impl {
|
|
806
|
+
public:
|
|
807
|
+
explicit Impl(S2BooleanOperation* op)
|
|
808
|
+
: op_(op), index_crossings_first_region_id_(-1) {
|
|
809
|
+
}
|
|
810
|
+
bool Build(S2Error* error);
|
|
811
|
+
|
|
812
|
+
private:
|
|
813
|
+
class CrossingIterator;
|
|
814
|
+
class CrossingProcessor;
|
|
815
|
+
using ShapeEdge = s2shapeutil::ShapeEdge;
|
|
816
|
+
using ShapeEdgeId = s2shapeutil::ShapeEdgeId;
|
|
817
|
+
|
|
818
|
+
// An IndexCrossing represents a pair of intersecting S2ShapeIndex edges
|
|
819
|
+
// ("a_edge" and "b_edge"). We store all such intersections because the
|
|
820
|
+
// algorithm needs them twice, once when processing the boundary of region A
|
|
821
|
+
// and once when processing the boundary of region B.
|
|
822
|
+
struct IndexCrossing {
|
|
823
|
+
ShapeEdgeId a, b;
|
|
824
|
+
|
|
825
|
+
// True if S2::CrossingSign(a_edge, b_edge) > 0.
|
|
826
|
+
uint32 is_interior_crossing : 1;
|
|
827
|
+
|
|
828
|
+
// True if "a_edge" crosses "b_edge" from left to right. Undefined if
|
|
829
|
+
// is_interior_crossing is false.
|
|
830
|
+
uint32 left_to_right: 1;
|
|
831
|
+
|
|
832
|
+
// Equal to S2::VertexCrossing(a_edge, b_edge). Undefined if "a_edge" and
|
|
833
|
+
// "b_edge" do not share exactly one vertex or either edge is degenerate.
|
|
834
|
+
uint32 is_vertex_crossing : 1;
|
|
835
|
+
|
|
836
|
+
// All flags are "false" by default.
|
|
837
|
+
IndexCrossing(ShapeEdgeId _a, ShapeEdgeId _b)
|
|
838
|
+
: a(_a), b(_b), is_interior_crossing(false), left_to_right(false),
|
|
839
|
+
is_vertex_crossing(false) {
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
friend bool operator==(const IndexCrossing& x, const IndexCrossing& y) {
|
|
843
|
+
return x.a == y.a && x.b == y.b;
|
|
844
|
+
}
|
|
845
|
+
friend bool operator<(const IndexCrossing& x, const IndexCrossing& y) {
|
|
846
|
+
// The compiler (2017) doesn't optimize the following as well:
|
|
847
|
+
// return x.a < y.a || (x.a == y.a && x.b < y.b);
|
|
848
|
+
if (x.a.shape_id < y.a.shape_id) return true;
|
|
849
|
+
if (y.a.shape_id < x.a.shape_id) return false;
|
|
850
|
+
if (x.a.edge_id < y.a.edge_id) return true;
|
|
851
|
+
if (y.a.edge_id < x.a.edge_id) return false;
|
|
852
|
+
if (x.b.shape_id < y.b.shape_id) return true;
|
|
853
|
+
if (y.b.shape_id < x.b.shape_id) return false;
|
|
854
|
+
return x.b.edge_id < y.b.edge_id;
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
using IndexCrossings = vector<IndexCrossing>;
|
|
858
|
+
|
|
859
|
+
bool is_boolean_output() const { return op_->result_empty_ != nullptr; }
|
|
860
|
+
|
|
861
|
+
// All of the methods below support "early exit" in the case of boolean
|
|
862
|
+
// results by returning "false" as soon as the result is known to be
|
|
863
|
+
// non-empty.
|
|
864
|
+
bool AddBoundary(int a_region_id, bool invert_a, bool invert_b,
|
|
865
|
+
bool invert_result,
|
|
866
|
+
const vector<ShapeEdgeId>& a_chain_starts,
|
|
867
|
+
CrossingProcessor* cp);
|
|
868
|
+
bool GetChainStarts(int a_region_id, bool invert_a, bool invert_b,
|
|
869
|
+
bool invert_result, CrossingProcessor* cp,
|
|
870
|
+
vector<ShapeEdgeId>* chain_starts);
|
|
871
|
+
bool ProcessIncidentEdges(const ShapeEdge& a,
|
|
872
|
+
S2ContainsPointQuery<S2ShapeIndex>* query,
|
|
873
|
+
CrossingProcessor* cp);
|
|
874
|
+
static bool HasInterior(const S2ShapeIndex& index);
|
|
875
|
+
static bool AddIndexCrossing(const ShapeEdge& a, const ShapeEdge& b,
|
|
876
|
+
bool is_interior, IndexCrossings* crossings);
|
|
877
|
+
bool GetIndexCrossings(int region_id);
|
|
878
|
+
bool AddBoundaryPair(bool invert_a, bool invert_b, bool invert_result,
|
|
879
|
+
CrossingProcessor* cp);
|
|
880
|
+
bool AreRegionsIdentical() const;
|
|
881
|
+
bool BuildOpType(OpType op_type);
|
|
882
|
+
bool IsFullPolygonResult(const S2Builder::Graph& g, S2Error* error) const;
|
|
883
|
+
bool IsFullPolygonUnion(const S2ShapeIndex& a,
|
|
884
|
+
const S2ShapeIndex& b) const;
|
|
885
|
+
bool IsFullPolygonIntersection(const S2ShapeIndex& a,
|
|
886
|
+
const S2ShapeIndex& b) const;
|
|
887
|
+
bool IsFullPolygonDifference(const S2ShapeIndex& a,
|
|
888
|
+
const S2ShapeIndex& b) const;
|
|
889
|
+
bool IsFullPolygonSymmetricDifference(const S2ShapeIndex& a,
|
|
890
|
+
const S2ShapeIndex& b) const;
|
|
891
|
+
|
|
892
|
+
// A bit mask representing all six faces of the S2 cube.
|
|
893
|
+
static constexpr uint8 kAllFacesMask = 0x3f;
|
|
894
|
+
|
|
895
|
+
S2BooleanOperation* op_;
|
|
896
|
+
|
|
897
|
+
// The S2Builder used to construct the output.
|
|
898
|
+
unique_ptr<S2Builder> builder_;
|
|
899
|
+
|
|
900
|
+
// A vector specifying the dimension of each edge added to S2Builder.
|
|
901
|
+
vector<int8> input_dimensions_;
|
|
902
|
+
|
|
903
|
+
// The set of all input edge crossings, which is used by EdgeClippingLayer
|
|
904
|
+
// to construct the clipped output polygon.
|
|
905
|
+
InputEdgeCrossings input_crossings_;
|
|
906
|
+
|
|
907
|
+
// kSentinel is a sentinel value used to mark the end of vectors.
|
|
908
|
+
static const ShapeEdgeId kSentinel;
|
|
909
|
+
|
|
910
|
+
// A vector containing all pairs of crossing edges from the two input
|
|
911
|
+
// regions (including edge pairs that share a common vertex). The first
|
|
912
|
+
// element of each pair is an edge from "index_crossings_first_region_id_",
|
|
913
|
+
// while the second element of each pair is an edge from the other region.
|
|
914
|
+
IndexCrossings index_crossings_;
|
|
915
|
+
|
|
916
|
+
// Indicates that the first element of each crossing edge pair in
|
|
917
|
+
// "index_crossings_" corresponds to an edge from the given region.
|
|
918
|
+
// This field is negative if index_crossings_ has not been computed yet.
|
|
919
|
+
int index_crossings_first_region_id_;
|
|
920
|
+
|
|
921
|
+
// Temporary storage used in GetChainStarts(), declared here to avoid
|
|
922
|
+
// repeatedly allocating memory.
|
|
923
|
+
IndexCrossings tmp_crossings_;
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
const s2shapeutil::ShapeEdgeId S2BooleanOperation::Impl::kSentinel(
|
|
927
|
+
std::numeric_limits<int32>::max(), 0);
|
|
928
|
+
|
|
929
|
+
// A helper class for iterating through the edges from region B that cross a
|
|
930
|
+
// particular edge from region A. It caches information from the current
|
|
931
|
+
// shape, chain, and edge so that it doesn't need to be looked up repeatedly.
|
|
932
|
+
// Typical usage:
|
|
933
|
+
//
|
|
934
|
+
// void SomeFunction(ShapeEdgeId a_id, CrossingIterator *it) {
|
|
935
|
+
// // Iterate through the edges that cross edge "a_id".
|
|
936
|
+
// for (; !it->Done(a_id); it->Next()) {
|
|
937
|
+
// ... use it->b_shape(), it->b_edge(), etc ...
|
|
938
|
+
// }
|
|
939
|
+
class S2BooleanOperation::Impl::CrossingIterator {
|
|
940
|
+
public:
|
|
941
|
+
// Creates an iterator over crossing edge pairs (a, b) where "b" is an edge
|
|
942
|
+
// from "b_index". "crossings_complete" indicates that "crossings" contains
|
|
943
|
+
// all edge crossings between the two regions (rather than a subset).
|
|
944
|
+
CrossingIterator(const S2ShapeIndex* b_index,
|
|
945
|
+
const IndexCrossings* crossings, bool crossings_complete)
|
|
946
|
+
: b_index_(*b_index), it_(crossings->begin()), b_shape_id_(-1),
|
|
947
|
+
crossings_complete_(crossings_complete) {
|
|
948
|
+
Update();
|
|
949
|
+
}
|
|
950
|
+
void Next() {
|
|
951
|
+
++it_;
|
|
952
|
+
Update();
|
|
953
|
+
}
|
|
954
|
+
bool Done(ShapeEdgeId id) const { return a_id() != id; }
|
|
955
|
+
|
|
956
|
+
// True if all edge crossings are available (see above).
|
|
957
|
+
bool crossings_complete() const { return crossings_complete_; }
|
|
958
|
+
|
|
959
|
+
// True if this crossing occurs at a point interior to both edges.
|
|
960
|
+
bool is_interior_crossing() const { return it_->is_interior_crossing; }
|
|
961
|
+
|
|
962
|
+
// Equal to S2::VertexCrossing(a_edge, b_edge), provided that a_edge and
|
|
963
|
+
// b_edge have exactly one vertex in common and neither edge is degenerate.
|
|
964
|
+
bool is_vertex_crossing() const { return it_->is_vertex_crossing; }
|
|
965
|
+
|
|
966
|
+
// True if a_edge crosses b_edge from left to right (for interior crossings).
|
|
967
|
+
bool left_to_right() const { return it_->left_to_right; }
|
|
968
|
+
|
|
969
|
+
ShapeEdgeId a_id() const { return it_->a; }
|
|
970
|
+
ShapeEdgeId b_id() const { return it_->b; }
|
|
971
|
+
const S2ShapeIndex& b_index() const { return b_index_; }
|
|
972
|
+
const S2Shape& b_shape() const { return *b_shape_; }
|
|
973
|
+
int b_dimension() const { return b_dimension_; }
|
|
974
|
+
int b_shape_id() const { return b_shape_id_; }
|
|
975
|
+
int b_edge_id() const { return b_id().edge_id; }
|
|
976
|
+
|
|
977
|
+
S2Shape::Edge b_edge() const {
|
|
978
|
+
return b_shape_->edge(b_edge_id()); // Opportunity to cache this.
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// Information about the chain to which an edge belongs.
|
|
982
|
+
struct ChainInfo {
|
|
983
|
+
int chain_id; // chain id
|
|
984
|
+
int start; // starting edge id
|
|
985
|
+
int limit; // limit edge id
|
|
986
|
+
};
|
|
987
|
+
// Returns a description of the chain to which the current B edge belongs.
|
|
988
|
+
const ChainInfo& b_chain_info() const {
|
|
989
|
+
if (b_info_.chain_id < 0) {
|
|
990
|
+
b_info_.chain_id = b_shape().chain_position(b_edge_id()).chain_id;
|
|
991
|
+
auto chain = b_shape().chain(b_info_.chain_id);
|
|
992
|
+
b_info_.start = chain.start;
|
|
993
|
+
b_info_.limit = chain.start + chain.length;
|
|
994
|
+
}
|
|
995
|
+
return b_info_;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
private:
|
|
999
|
+
// Updates information about the B shape whenever it changes.
|
|
1000
|
+
void Update() {
|
|
1001
|
+
if (a_id() != kSentinel && b_id().shape_id != b_shape_id_) {
|
|
1002
|
+
b_shape_id_ = b_id().shape_id;
|
|
1003
|
+
b_shape_ = b_index_.shape(b_shape_id_);
|
|
1004
|
+
b_dimension_ = b_shape_->dimension();
|
|
1005
|
+
b_info_.chain_id = -1; // Computed on demand.
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const S2ShapeIndex& b_index_;
|
|
1010
|
+
IndexCrossings::const_iterator it_;
|
|
1011
|
+
const S2Shape* b_shape_;
|
|
1012
|
+
int b_shape_id_;
|
|
1013
|
+
int b_dimension_;
|
|
1014
|
+
mutable ChainInfo b_info_; // Computed on demand.
|
|
1015
|
+
bool crossings_complete_;
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
// CrossingProcessor is a helper class that processes all the edges from one
|
|
1019
|
+
// region that cross a specific edge of the other region. It outputs the
|
|
1020
|
+
// appropriate edges to an S2Builder, and outputs other information required
|
|
1021
|
+
// by GraphEdgeClipper to the given vectors.
|
|
1022
|
+
class S2BooleanOperation::Impl::CrossingProcessor {
|
|
1023
|
+
public:
|
|
1024
|
+
// Prepares to build output for the given polygon and polyline boundary
|
|
1025
|
+
// models. Edges are emitted to "builder", while other auxiliary data is
|
|
1026
|
+
// appended to the given vectors.
|
|
1027
|
+
//
|
|
1028
|
+
// If a predicate is being evaluated (i.e., we do not need to construct the
|
|
1029
|
+
// actual result), then "builder" and the various output vectors should all
|
|
1030
|
+
// be nullptr.
|
|
1031
|
+
CrossingProcessor(const PolygonModel& polygon_model,
|
|
1032
|
+
const PolylineModel& polyline_model,
|
|
1033
|
+
bool polyline_loops_have_boundaries,
|
|
1034
|
+
S2Builder* builder,
|
|
1035
|
+
vector<int8>* input_dimensions,
|
|
1036
|
+
InputEdgeCrossings *input_crossings)
|
|
1037
|
+
: polygon_model_(polygon_model), polyline_model_(polyline_model),
|
|
1038
|
+
polyline_loops_have_boundaries_(polyline_loops_have_boundaries),
|
|
1039
|
+
builder_(builder), input_dimensions_(input_dimensions),
|
|
1040
|
+
input_crossings_(input_crossings), prev_inside_(false) {
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Starts processing edges from the given region. "invert_a", "invert_b",
|
|
1044
|
+
// and "invert_result" indicate whether region A, region B, and/or the
|
|
1045
|
+
// result should be inverted, which allows operations such as union and
|
|
1046
|
+
// difference to be implemented. For example, union is ~(~A & ~B).
|
|
1047
|
+
//
|
|
1048
|
+
// This method should be called in pairs, once to process the edges from
|
|
1049
|
+
// region A and once to process the edges from region B.
|
|
1050
|
+
void StartBoundary(int a_region_id, bool invert_a, bool invert_b,
|
|
1051
|
+
bool invert_result);
|
|
1052
|
+
|
|
1053
|
+
// Starts processing edges from the given shape.
|
|
1054
|
+
void StartShape(const S2Shape* a_shape);
|
|
1055
|
+
|
|
1056
|
+
// Starts processing edges from the given chain.
|
|
1057
|
+
void StartChain(int chain_id, S2Shape::Chain chain, bool inside);
|
|
1058
|
+
|
|
1059
|
+
// Processes the given edge "a_id". "it" should be positioned to the set of
|
|
1060
|
+
// edges from the other region that cross "a_id" (if any).
|
|
1061
|
+
//
|
|
1062
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1063
|
+
// as soon as the result is known to be non-empty.
|
|
1064
|
+
bool ProcessEdge(ShapeEdgeId a_id, CrossingIterator* it);
|
|
1065
|
+
|
|
1066
|
+
// This method should be called after each pair of calls to StartBoundary.
|
|
1067
|
+
// (The only operation that processes more than one pair of boundaries is
|
|
1068
|
+
// SYMMETRIC_DIFFERENCE, which computes the union of A-B and B-A.)
|
|
1069
|
+
//
|
|
1070
|
+
// Resets the state of the CrossingProcessor.
|
|
1071
|
+
void DoneBoundaryPair();
|
|
1072
|
+
|
|
1073
|
+
// Indicates whether the point being processed along the current edge chain
|
|
1074
|
+
// is in the polygonal interior of the opposite region, using semi-open
|
|
1075
|
+
// boundaries. If "invert_b_" is true then this field is inverted.
|
|
1076
|
+
//
|
|
1077
|
+
// This value along with the set of incident edges can be used to compute
|
|
1078
|
+
// whether the opposite region contains this point under any of the
|
|
1079
|
+
// supported boundary models (PolylineModel::CLOSED, etc).
|
|
1080
|
+
bool inside() const { return inside_; }
|
|
1081
|
+
|
|
1082
|
+
private:
|
|
1083
|
+
// SourceEdgeCrossing represents an input edge that crosses some other
|
|
1084
|
+
// edge; it crosses the edge from left to right iff the second parameter
|
|
1085
|
+
// is "true".
|
|
1086
|
+
using SourceEdgeCrossing = pair<SourceId, bool>;
|
|
1087
|
+
struct PointCrossingResult;
|
|
1088
|
+
struct EdgeCrossingResult;
|
|
1089
|
+
|
|
1090
|
+
InputEdgeId input_edge_id() const { return input_dimensions_->size(); }
|
|
1091
|
+
|
|
1092
|
+
// Returns true if the edges on either side of the first vertex of the
|
|
1093
|
+
// current edge have not been emitted.
|
|
1094
|
+
//
|
|
1095
|
+
// REQUIRES: This method is called just after updating "inside_" for "v0".
|
|
1096
|
+
bool is_v0_isolated(ShapeEdgeId a_id) const {
|
|
1097
|
+
return !inside_ && v0_emitted_max_edge_id_ < a_id.edge_id;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Returns true if "a_id" is the last edge of the current chain, and the
|
|
1101
|
+
// edges on either side of the last vertex have not been emitted (including
|
|
1102
|
+
// the possibility that the chain forms a loop).
|
|
1103
|
+
bool is_chain_last_vertex_isolated(ShapeEdgeId a_id) const {
|
|
1104
|
+
return (a_id.edge_id == chain_limit_ - 1 && !chain_v0_emitted_ &&
|
|
1105
|
+
v0_emitted_max_edge_id_ <= a_id.edge_id);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// Returns true if the given polyline edge contains "v0", taking into
|
|
1109
|
+
// account the specified PolylineModel.
|
|
1110
|
+
bool polyline_contains_v0(int edge_id, int chain_start) const {
|
|
1111
|
+
return (polyline_model_ != PolylineModel::OPEN || edge_id > chain_start);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
void AddCrossing(const SourceEdgeCrossing& crossing) {
|
|
1115
|
+
source_edge_crossings_.push_back(make_pair(input_edge_id(), crossing));
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
void SetClippingState(InputEdgeId parameter, bool state) {
|
|
1119
|
+
AddCrossing(SourceEdgeCrossing(SourceId(parameter), state));
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1123
|
+
// as soon as the result is known to be non-empty.
|
|
1124
|
+
bool AddEdge(ShapeEdgeId a_id, const S2Shape::Edge& a,
|
|
1125
|
+
int dimension, int interior_crossings) {
|
|
1126
|
+
if (builder_ == nullptr) return false; // Boolean output.
|
|
1127
|
+
if (interior_crossings > 0) {
|
|
1128
|
+
// Build a map that translates temporary edge ids (SourceId) to
|
|
1129
|
+
// the representation used by EdgeClippingLayer (InputEdgeId).
|
|
1130
|
+
SourceId src_id(a_region_id_, a_id.shape_id, a_id.edge_id);
|
|
1131
|
+
source_id_map_[src_id] = input_edge_id();
|
|
1132
|
+
}
|
|
1133
|
+
// Set the GraphEdgeClipper's "inside" state to match ours.
|
|
1134
|
+
if (inside_ != prev_inside_) SetClippingState(kSetInside, inside_);
|
|
1135
|
+
input_dimensions_->push_back(dimension);
|
|
1136
|
+
builder_->AddEdge(a.v0, a.v1);
|
|
1137
|
+
inside_ ^= (interior_crossings & 1);
|
|
1138
|
+
prev_inside_ = inside_;
|
|
1139
|
+
return true;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1143
|
+
// as soon as the result is known to be non-empty.
|
|
1144
|
+
bool AddPointEdge(const S2Point& p, int dimension) {
|
|
1145
|
+
if (builder_ == nullptr) return false; // Boolean output.
|
|
1146
|
+
if (!prev_inside_) SetClippingState(kSetInside, true);
|
|
1147
|
+
input_dimensions_->push_back(dimension);
|
|
1148
|
+
builder_->AddEdge(p, p);
|
|
1149
|
+
prev_inside_ = true;
|
|
1150
|
+
return true;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
bool ProcessEdge0(ShapeEdgeId a_id, const S2Shape::Edge& a,
|
|
1154
|
+
CrossingIterator* it);
|
|
1155
|
+
bool ProcessEdge1(ShapeEdgeId a_id, const S2Shape::Edge& a,
|
|
1156
|
+
CrossingIterator* it);
|
|
1157
|
+
bool ProcessEdge2(ShapeEdgeId a_id, const S2Shape::Edge& a,
|
|
1158
|
+
CrossingIterator* it);
|
|
1159
|
+
|
|
1160
|
+
void SkipCrossings(ShapeEdgeId a_id, CrossingIterator* it);
|
|
1161
|
+
PointCrossingResult ProcessPointCrossings(
|
|
1162
|
+
ShapeEdgeId a_id, const S2Point& a0, CrossingIterator* it) const;
|
|
1163
|
+
EdgeCrossingResult ProcessEdgeCrossings(
|
|
1164
|
+
ShapeEdgeId a_id, const S2Shape::Edge& a, CrossingIterator* it);
|
|
1165
|
+
|
|
1166
|
+
bool IsPolylineVertexInside(bool matches_polyline,
|
|
1167
|
+
bool matches_polygon) const;
|
|
1168
|
+
bool IsPolylineEdgeInside(const EdgeCrossingResult& r) const;
|
|
1169
|
+
bool PolylineEdgeContainsVertex(const S2Point& v,
|
|
1170
|
+
const CrossingIterator& it) const;
|
|
1171
|
+
|
|
1172
|
+
// Constructor parameters:
|
|
1173
|
+
|
|
1174
|
+
PolygonModel polygon_model_;
|
|
1175
|
+
PolylineModel polyline_model_;
|
|
1176
|
+
bool polyline_loops_have_boundaries_;
|
|
1177
|
+
|
|
1178
|
+
// The output of the CrossingProcessor consists of a subset of the input
|
|
1179
|
+
// edges that are emitted to "builder_", and some auxiliary information
|
|
1180
|
+
// that allows GraphEdgeClipper to determine which segments of those input
|
|
1181
|
+
// edges belong to the output. The auxiliary information consists of the
|
|
1182
|
+
// dimension of each input edge, and set of input edges from the other
|
|
1183
|
+
// region that cross each input input edge.
|
|
1184
|
+
S2Builder* builder_;
|
|
1185
|
+
vector<int8>* input_dimensions_;
|
|
1186
|
+
InputEdgeCrossings* input_crossings_;
|
|
1187
|
+
|
|
1188
|
+
// Fields set by StartBoundary:
|
|
1189
|
+
|
|
1190
|
+
int a_region_id_, b_region_id_;
|
|
1191
|
+
bool invert_a_, invert_b_, invert_result_;
|
|
1192
|
+
bool is_union_; // True if this is a UNION operation.
|
|
1193
|
+
|
|
1194
|
+
// Fields set by StartShape:
|
|
1195
|
+
|
|
1196
|
+
const S2Shape* a_shape_;
|
|
1197
|
+
int a_dimension_;
|
|
1198
|
+
|
|
1199
|
+
// Fields set by StartChain:
|
|
1200
|
+
|
|
1201
|
+
int chain_id_;
|
|
1202
|
+
int chain_start_;
|
|
1203
|
+
int chain_limit_;
|
|
1204
|
+
|
|
1205
|
+
// Fields updated by ProcessEdge:
|
|
1206
|
+
|
|
1207
|
+
// A temporary representation of input_crossings_ that is used internally
|
|
1208
|
+
// until all necessary edges from *both* polygons have been emitted to the
|
|
1209
|
+
// S2Builder. This field is then converted by DoneBoundaryPair() into
|
|
1210
|
+
// the InputEdgeCrossings format expected by GraphEdgeClipper.
|
|
1211
|
+
//
|
|
1212
|
+
// The reason that we can't construct input_crossings_ directly is that it
|
|
1213
|
+
// uses InputEdgeIds to identify the edges from both polygons, and when we
|
|
1214
|
+
// are processing edges from the first polygon, InputEdgeIds have not yet
|
|
1215
|
+
// been assigned to the second polygon. So instead this field identifies
|
|
1216
|
+
// edges from the first polygon using an InputEdgeId, and edges from the
|
|
1217
|
+
// second polygon using a (region_id, shape_id, edge_id) tuple (i.e., a
|
|
1218
|
+
// SourceId).
|
|
1219
|
+
//
|
|
1220
|
+
// All crossings are represented twice, once to indicate that an edge from
|
|
1221
|
+
// polygon 0 is crossed by an edge from polygon 1, and once to indicate that
|
|
1222
|
+
// an edge from polygon 1 is crossed by an edge from polygon 0.
|
|
1223
|
+
using SourceEdgeCrossings = vector<pair<InputEdgeId, SourceEdgeCrossing>>;
|
|
1224
|
+
SourceEdgeCrossings source_edge_crossings_;
|
|
1225
|
+
|
|
1226
|
+
// A map that translates from SourceId (the (region_id, shape_id,
|
|
1227
|
+
// edge_id) triple that identifies an S2ShapeIndex edge) to InputEdgeId (the
|
|
1228
|
+
// sequentially increasing numbers assigned to input edges by S2Builder).
|
|
1229
|
+
using SourceIdMap = gtl::btree_map<SourceId, InputEdgeId>;
|
|
1230
|
+
SourceIdMap source_id_map_;
|
|
1231
|
+
|
|
1232
|
+
// Indicates whether the point being processed along the current edge chain
|
|
1233
|
+
// is in the polygonal interior of the opposite region, using semi-open
|
|
1234
|
+
// boundaries. If "invert_b_" is true then this field is inverted.
|
|
1235
|
+
//
|
|
1236
|
+
// Equal to: b_index_.Contains(current point) ^ invert_b_
|
|
1237
|
+
bool inside_;
|
|
1238
|
+
|
|
1239
|
+
// The value of that "inside_" would have just before the end of the
|
|
1240
|
+
// previous edge added to S2Builder. This value is used to determine
|
|
1241
|
+
// whether the GraphEdgeClipper state needs to be updated when jumping from
|
|
1242
|
+
// one edge chain to another.
|
|
1243
|
+
bool prev_inside_;
|
|
1244
|
+
|
|
1245
|
+
// The maximum edge id of any edge in the current chain whose v0 vertex has
|
|
1246
|
+
// already been emitted. This is used to determine when an isolated vertex
|
|
1247
|
+
// needs to be emitted, e.g. when two closed polygons share only a vertex.
|
|
1248
|
+
int v0_emitted_max_edge_id_;
|
|
1249
|
+
|
|
1250
|
+
// True if the first vertex of the current chain has been emitted. This is
|
|
1251
|
+
// used when processing loops in order to determine whether the first/last
|
|
1252
|
+
// vertex of the loop should be emitted as an isolated vertex.
|
|
1253
|
+
bool chain_v0_emitted_;
|
|
1254
|
+
};
|
|
1255
|
+
|
|
1256
|
+
// See documentation above.
|
|
1257
|
+
void S2BooleanOperation::Impl::CrossingProcessor::StartBoundary(
|
|
1258
|
+
int a_region_id, bool invert_a, bool invert_b, bool invert_result) {
|
|
1259
|
+
a_region_id_ = a_region_id;
|
|
1260
|
+
b_region_id_ = 1 - a_region_id;
|
|
1261
|
+
invert_a_ = invert_a;
|
|
1262
|
+
invert_b_ = invert_b;
|
|
1263
|
+
invert_result_ = invert_result;
|
|
1264
|
+
is_union_ = invert_b && invert_result;
|
|
1265
|
+
|
|
1266
|
+
// Specify to GraphEdgeClipper how these edges should be clipped.
|
|
1267
|
+
SetClippingState(kSetReverseA, invert_a != invert_result);
|
|
1268
|
+
SetClippingState(kSetInvertB, invert_b);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// See documentation above.
|
|
1272
|
+
inline void S2BooleanOperation::Impl::CrossingProcessor::StartShape(
|
|
1273
|
+
const S2Shape* a_shape) {
|
|
1274
|
+
a_shape_ = a_shape;
|
|
1275
|
+
a_dimension_ = a_shape->dimension();
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// See documentation above.
|
|
1279
|
+
inline void S2BooleanOperation::Impl::CrossingProcessor::StartChain(
|
|
1280
|
+
int chain_id, S2Shape::Chain chain, bool inside) {
|
|
1281
|
+
chain_id_ = chain_id;
|
|
1282
|
+
chain_start_ = chain.start;
|
|
1283
|
+
chain_limit_ = chain.start + chain.length;
|
|
1284
|
+
inside_ = inside;
|
|
1285
|
+
v0_emitted_max_edge_id_ = chain.start - 1; // No edges emitted yet.
|
|
1286
|
+
chain_v0_emitted_ = false;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// See documentation above.
|
|
1290
|
+
bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge(
|
|
1291
|
+
ShapeEdgeId a_id, CrossingIterator* it) {
|
|
1292
|
+
// chain_edge() is faster than edge() when there are multiple chains.
|
|
1293
|
+
auto a = a_shape_->chain_edge(chain_id_, a_id.edge_id - chain_start_);
|
|
1294
|
+
if (a_dimension_ == 0) {
|
|
1295
|
+
return ProcessEdge0(a_id, a, it);
|
|
1296
|
+
} else if (a_dimension_ == 1) {
|
|
1297
|
+
return ProcessEdge1(a_id, a, it);
|
|
1298
|
+
} else {
|
|
1299
|
+
S2_DCHECK_EQ(2, a_dimension_);
|
|
1300
|
+
return ProcessEdge2(a_id, a, it);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// PointCrossingResult describes the relationship between a point from region A
|
|
1305
|
+
// and a set of crossing edges from region B. For example, "matches_polygon"
|
|
1306
|
+
// indicates whether a polygon vertex from region B matches the given point.
|
|
1307
|
+
struct S2BooleanOperation::Impl::CrossingProcessor::PointCrossingResult {
|
|
1308
|
+
PointCrossingResult()
|
|
1309
|
+
: matches_point(false), matches_polyline(false), matches_polygon(false) {
|
|
1310
|
+
}
|
|
1311
|
+
// Note that "matches_polyline" is true only if the point matches a polyline
|
|
1312
|
+
// vertex of B *and* the polyline contains that vertex, whereas
|
|
1313
|
+
// "matches_polygon" is true if the point matches any polygon vertex.
|
|
1314
|
+
bool matches_point; // Matches point.
|
|
1315
|
+
bool matches_polyline; // Matches contained polyline vertex.
|
|
1316
|
+
bool matches_polygon; // Matches polygon vertex.
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
// Processes an edge of dimension 0 (i.e., a point) from region A.
|
|
1320
|
+
//
|
|
1321
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1322
|
+
// as soon as the result is known to be non-empty.
|
|
1323
|
+
bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge0(
|
|
1324
|
+
ShapeEdgeId a_id, const S2Shape::Edge& a, CrossingIterator* it) {
|
|
1325
|
+
S2_DCHECK_EQ(a.v0, a.v1);
|
|
1326
|
+
// When a region is inverted, all points and polylines are discarded.
|
|
1327
|
+
if (invert_a_ != invert_result_) {
|
|
1328
|
+
SkipCrossings(a_id, it);
|
|
1329
|
+
return true;
|
|
1330
|
+
}
|
|
1331
|
+
PointCrossingResult r = ProcessPointCrossings(a_id, a.v0, it);
|
|
1332
|
+
|
|
1333
|
+
// "contained" indicates whether the current point is inside the polygonal
|
|
1334
|
+
// interior of the opposite region, using semi-open boundaries.
|
|
1335
|
+
bool contained = inside_ ^ invert_b_;
|
|
1336
|
+
if (r.matches_polygon && polygon_model_ != PolygonModel::SEMI_OPEN) {
|
|
1337
|
+
contained = (polygon_model_ == PolygonModel::CLOSED);
|
|
1338
|
+
}
|
|
1339
|
+
if (r.matches_polyline) contained = true;
|
|
1340
|
+
|
|
1341
|
+
// The output of UNION includes duplicate values, so ensure that points are
|
|
1342
|
+
// not suppressed by other points.
|
|
1343
|
+
if (r.matches_point && !is_union_) contained = true;
|
|
1344
|
+
|
|
1345
|
+
// Test whether the point is contained after region B is inverted.
|
|
1346
|
+
if (contained == invert_b_) return true; // Don't exit early.
|
|
1347
|
+
return AddPointEdge(a.v0, 0);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Skip any crossings that were not needed to determine the result.
|
|
1351
|
+
inline void S2BooleanOperation::Impl::CrossingProcessor::SkipCrossings(
|
|
1352
|
+
ShapeEdgeId a_id, CrossingIterator* it) {
|
|
1353
|
+
while (!it->Done(a_id)) it->Next();
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// Returns a summary of the relationship between a point from region A and
|
|
1357
|
+
// a set of crossing edges from region B (see PointCrossingResult).
|
|
1358
|
+
S2BooleanOperation::Impl::CrossingProcessor::PointCrossingResult
|
|
1359
|
+
S2BooleanOperation::Impl::CrossingProcessor::ProcessPointCrossings(
|
|
1360
|
+
ShapeEdgeId a_id, const S2Point& a0, CrossingIterator* it) const {
|
|
1361
|
+
PointCrossingResult r;
|
|
1362
|
+
for (; !it->Done(a_id); it->Next()) {
|
|
1363
|
+
if (it->b_dimension() == 0) {
|
|
1364
|
+
r.matches_point = true;
|
|
1365
|
+
} else if (it->b_dimension() == 1) {
|
|
1366
|
+
if (PolylineEdgeContainsVertex(a0, *it)) {
|
|
1367
|
+
r.matches_polyline = true;
|
|
1368
|
+
}
|
|
1369
|
+
} else {
|
|
1370
|
+
r.matches_polygon = true;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return r;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// EdgeCrossingResult describes the relationship between an edge from region A
|
|
1377
|
+
// ("a_edge") and a set of crossing edges from region B. For example,
|
|
1378
|
+
// "matches_polygon" indicates whether "a_edge" matches a polygon edge from
|
|
1379
|
+
// region B.
|
|
1380
|
+
struct S2BooleanOperation::Impl::CrossingProcessor::EdgeCrossingResult {
|
|
1381
|
+
EdgeCrossingResult()
|
|
1382
|
+
: matches_polyline(false), matches_polygon(false), matches_sibling(false),
|
|
1383
|
+
a0_matches_polyline(false), a1_matches_polyline(false),
|
|
1384
|
+
a0_matches_polygon(false), a1_matches_polygon(false),
|
|
1385
|
+
a0_crossings(0), a1_crossings(0), interior_crossings(0) {
|
|
1386
|
+
}
|
|
1387
|
+
// These fields indicate that "a_edge" exactly matches an edge of B.
|
|
1388
|
+
bool matches_polyline; // Matches polyline edge (either direction).
|
|
1389
|
+
bool matches_polygon; // Matches polygon edge (same direction).
|
|
1390
|
+
bool matches_sibling; // Matches polygon edge (reverse direction).
|
|
1391
|
+
|
|
1392
|
+
// These fields indicate that a vertex of "a_edge" matches a polyline vertex
|
|
1393
|
+
// of B *and* the polyline contains that vertex.
|
|
1394
|
+
bool a0_matches_polyline; // Start vertex matches contained polyline vertex.
|
|
1395
|
+
bool a1_matches_polyline; // End vertex matches contained polyline vertex.
|
|
1396
|
+
|
|
1397
|
+
// These fields indicate that a vertex of "a_edge" matches a polygon vertex
|
|
1398
|
+
// of B. (Unlike with polylines, the polygon may not contain that vertex.)
|
|
1399
|
+
bool a0_matches_polygon; // Start vertex matches polygon vertex.
|
|
1400
|
+
bool a1_matches_polygon; // End vertex matches polygon vertex.
|
|
1401
|
+
|
|
1402
|
+
// These fields count the number of edge crossings at the start vertex, end
|
|
1403
|
+
// vertex, and interior of "a_edge".
|
|
1404
|
+
int a0_crossings; // Count of polygon crossings at start vertex.
|
|
1405
|
+
int a1_crossings; // Count of polygon crossings at end vertex.
|
|
1406
|
+
int interior_crossings; // Count of polygon crossings in edge interior.
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
// Processes an edge of dimension 1 (i.e., a polyline edge) from region A.
|
|
1410
|
+
//
|
|
1411
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1412
|
+
// as soon as the result is known to be non-empty.
|
|
1413
|
+
bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge1(
|
|
1414
|
+
ShapeEdgeId a_id, const S2Shape::Edge& a, CrossingIterator* it) {
|
|
1415
|
+
// When a region is inverted, all points and polylines are discarded.
|
|
1416
|
+
if (invert_a_ != invert_result_) {
|
|
1417
|
+
SkipCrossings(a_id, it);
|
|
1418
|
+
return true;
|
|
1419
|
+
}
|
|
1420
|
+
// Evaluate whether the start vertex should belong to the output, in case it
|
|
1421
|
+
// needs to be emitted as an isolated vertex.
|
|
1422
|
+
EdgeCrossingResult r = ProcessEdgeCrossings(a_id, a, it);
|
|
1423
|
+
bool a0_inside = IsPolylineVertexInside(r.a0_matches_polyline,
|
|
1424
|
+
r.a0_matches_polygon);
|
|
1425
|
+
|
|
1426
|
+
// Test whether the entire polyline edge should be emitted (or not emitted)
|
|
1427
|
+
// because it matches a polyline or polygon edge.
|
|
1428
|
+
inside_ ^= (r.a0_crossings & 1);
|
|
1429
|
+
if (inside_ != IsPolylineEdgeInside(r)) {
|
|
1430
|
+
inside_ ^= true; // Invert the inside_ state.
|
|
1431
|
+
++r.a1_crossings; // Restore the correct (semi-open) state later.
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// If neither edge adjacent to v0 was emitted, and this polyline contains
|
|
1435
|
+
// v0, and the other region contains v0, then emit an isolated vertex.
|
|
1436
|
+
if (!polyline_loops_have_boundaries_ && a_id.edge_id == chain_start_ &&
|
|
1437
|
+
a.v0 == a_shape_->chain_edge(chain_id_,
|
|
1438
|
+
chain_limit_ - chain_start_ - 1).v1) {
|
|
1439
|
+
// This is the first vertex of a polyline loop, so we can't decide if it
|
|
1440
|
+
// is isolated until we process the last polyline edge.
|
|
1441
|
+
chain_v0_emitted_ = inside_;
|
|
1442
|
+
} else if (is_v0_isolated(a_id) &&
|
|
1443
|
+
polyline_contains_v0(a_id.edge_id, chain_start_) && a0_inside) {
|
|
1444
|
+
if (!AddPointEdge(a.v0, 1)) return false;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// Test whether the entire edge or any part of it belongs to the output.
|
|
1448
|
+
if (inside_ || r.interior_crossings > 0) {
|
|
1449
|
+
// Note: updates "inside_" to correspond to the state just before a1.
|
|
1450
|
+
if (!AddEdge(a_id, a, 1 /*dimension*/, r.interior_crossings)) {
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
// Remember whether the edge portion just before "a1" was emitted, so that
|
|
1455
|
+
// we can decide whether "a1" need to be emitted as an isolated vertex.
|
|
1456
|
+
if (inside_) v0_emitted_max_edge_id_ = a_id.edge_id + 1;
|
|
1457
|
+
|
|
1458
|
+
// Verify that edge crossings are being counted correctly.
|
|
1459
|
+
inside_ ^= (r.a1_crossings & 1);
|
|
1460
|
+
if (it->crossings_complete()) {
|
|
1461
|
+
S2_DCHECK_EQ(MakeS2ContainsPointQuery(&it->b_index()).Contains(a.v1),
|
|
1462
|
+
inside_ ^ invert_b_);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// Special case to test whether the last vertex of a polyline should be
|
|
1466
|
+
// emitted as an isolated vertex.
|
|
1467
|
+
if (it->crossings_complete() && is_chain_last_vertex_isolated(a_id) &&
|
|
1468
|
+
(polyline_model_ == PolylineModel::CLOSED ||
|
|
1469
|
+
(!polyline_loops_have_boundaries_ &&
|
|
1470
|
+
a.v1 == a_shape_->chain_edge(chain_id_, chain_start_).v0)) &&
|
|
1471
|
+
IsPolylineVertexInside(r.a1_matches_polyline, r.a1_matches_polygon)) {
|
|
1472
|
+
if (!AddPointEdge(a.v1, 1)) return false;
|
|
1473
|
+
}
|
|
1474
|
+
return true;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// Returns true if the current point being processed (which must be a polyline
|
|
1478
|
+
// vertex) is contained by the opposite region (after inversion if "invert_b_"
|
|
1479
|
+
// is true). "matches_polyline" and "matches_polygon" indicate whether the
|
|
1480
|
+
// vertex matches a polyline/polygon vertex of the opposite region.
|
|
1481
|
+
bool S2BooleanOperation::Impl::CrossingProcessor::IsPolylineVertexInside(
|
|
1482
|
+
bool matches_polyline, bool matches_polygon) const {
|
|
1483
|
+
// "contained" indicates whether the current point is inside the polygonal
|
|
1484
|
+
// interior of the opposite region using semi-open boundaries.
|
|
1485
|
+
bool contained = inside_ ^ invert_b_;
|
|
1486
|
+
|
|
1487
|
+
// For UNION the output includes duplicate polylines. The test below
|
|
1488
|
+
// ensures that isolated polyline vertices are not suppressed by other
|
|
1489
|
+
// polyline vertices in the output.
|
|
1490
|
+
if (matches_polyline && !is_union_) {
|
|
1491
|
+
contained = true;
|
|
1492
|
+
} else if (matches_polygon && polygon_model_ != PolygonModel::SEMI_OPEN) {
|
|
1493
|
+
contained = (polygon_model_ == PolygonModel::CLOSED);
|
|
1494
|
+
}
|
|
1495
|
+
// Finally, invert the result if the opposite region should be inverted.
|
|
1496
|
+
return contained ^ invert_b_;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Returns true if the current polyline edge is contained by the opposite
|
|
1500
|
+
// region (after inversion if "invert_b_" is true).
|
|
1501
|
+
inline bool S2BooleanOperation::Impl::CrossingProcessor::IsPolylineEdgeInside(
|
|
1502
|
+
const EdgeCrossingResult& r) const {
|
|
1503
|
+
// "contained" indicates whether the current point is inside the polygonal
|
|
1504
|
+
// interior of the opposite region using semi-open boundaries.
|
|
1505
|
+
bool contained = inside_ ^ invert_b_;
|
|
1506
|
+
if (r.matches_polyline && !is_union_) {
|
|
1507
|
+
contained = true;
|
|
1508
|
+
} else if (r.matches_polygon) {
|
|
1509
|
+
// In the SEMI_OPEN model, polygon sibling pairs cancel each other and
|
|
1510
|
+
// have no effect on point or edge containment.
|
|
1511
|
+
if (!(r.matches_sibling && polygon_model_ == PolygonModel::SEMI_OPEN)) {
|
|
1512
|
+
contained = (polygon_model_ != PolygonModel::OPEN);
|
|
1513
|
+
}
|
|
1514
|
+
} else if (r.matches_sibling) {
|
|
1515
|
+
contained = (polygon_model_ == PolygonModel::CLOSED);
|
|
1516
|
+
}
|
|
1517
|
+
// Finally, invert the result if the opposite region should be inverted.
|
|
1518
|
+
return contained ^ invert_b_;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// Processes an edge of dimension 2 (i.e., a polygon edge) from region A.
|
|
1522
|
+
//
|
|
1523
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1524
|
+
// as soon as the result is known to be non-empty.
|
|
1525
|
+
bool S2BooleanOperation::Impl::CrossingProcessor::ProcessEdge2(
|
|
1526
|
+
ShapeEdgeId a_id, const S2Shape::Edge& a, CrossingIterator* it) {
|
|
1527
|
+
// In order to keep only one copy of any shared polygon edges, we only
|
|
1528
|
+
// output shared edges when processing the second region.
|
|
1529
|
+
bool emit_shared = (a_region_id_ == 1);
|
|
1530
|
+
|
|
1531
|
+
// Degeneracies such as isolated vertices and sibling pairs can only be
|
|
1532
|
+
// created by intersecting CLOSED polygons or unioning OPEN polygons.
|
|
1533
|
+
bool emit_degenerate =
|
|
1534
|
+
(polygon_model_ == PolygonModel::CLOSED && !invert_a_ && !invert_b_) ||
|
|
1535
|
+
(polygon_model_ == PolygonModel::OPEN && invert_a_ && invert_b_);
|
|
1536
|
+
|
|
1537
|
+
EdgeCrossingResult r = ProcessEdgeCrossings(a_id, a, it);
|
|
1538
|
+
S2_DCHECK(!r.matches_polyline);
|
|
1539
|
+
inside_ ^= (r.a0_crossings & 1);
|
|
1540
|
+
|
|
1541
|
+
// If only one region is inverted, matching/sibling relations are reversed.
|
|
1542
|
+
// TODO(ericv): Update the following code to handle degenerate loops.
|
|
1543
|
+
S2_DCHECK(!r.matches_polygon || !r.matches_sibling);
|
|
1544
|
+
if (invert_a_ != invert_b_) swap(r.matches_polygon, r.matches_sibling);
|
|
1545
|
+
|
|
1546
|
+
// Test whether the entire polygon edge should be emitted (or not emitted)
|
|
1547
|
+
// because it matches a polygon edge or its sibling.
|
|
1548
|
+
bool new_inside = inside_;
|
|
1549
|
+
|
|
1550
|
+
// Shared edge are emitted only while processing the second region.
|
|
1551
|
+
if (r.matches_polygon) new_inside = emit_shared;
|
|
1552
|
+
|
|
1553
|
+
// Sibling pairs are emitted only when degeneracies are desired.
|
|
1554
|
+
if (r.matches_sibling) new_inside = emit_degenerate;
|
|
1555
|
+
if (inside_ != new_inside) {
|
|
1556
|
+
inside_ ^= true; // Invert the inside_ state.
|
|
1557
|
+
++r.a1_crossings; // Restore the correct (semi-open) state later.
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// Test whether the first vertex of this edge should be emitted as an
|
|
1561
|
+
// isolated degenerate vertex.
|
|
1562
|
+
if (a_id.edge_id == chain_start_) {
|
|
1563
|
+
chain_v0_emitted_ = inside_;
|
|
1564
|
+
} else if (emit_shared && emit_degenerate && r.a0_matches_polygon &&
|
|
1565
|
+
is_v0_isolated(a_id)) {
|
|
1566
|
+
if (!AddPointEdge(a.v0, 2)) return false;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// Test whether the entire edge or any part of it belongs to the output.
|
|
1570
|
+
if (inside_ || r.interior_crossings > 0) {
|
|
1571
|
+
// Note: updates "inside_" to correspond to the state just before a1.
|
|
1572
|
+
if (!AddEdge(a_id, a, 2 /*dimension*/, r.interior_crossings)) {
|
|
1573
|
+
return false;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// Remember whether the edge portion just before "a1" was emitted, so that
|
|
1578
|
+
// we can decide whether "a1" need to be emitted as an isolated vertex.
|
|
1579
|
+
if (inside_) v0_emitted_max_edge_id_ = a_id.edge_id + 1;
|
|
1580
|
+
inside_ ^= (r.a1_crossings & 1);
|
|
1581
|
+
|
|
1582
|
+
// Verify that edge crossings are being counted correctly.
|
|
1583
|
+
if (it->crossings_complete()) {
|
|
1584
|
+
S2_DCHECK_EQ(MakeS2ContainsPointQuery(&it->b_index()).Contains(a.v1),
|
|
1585
|
+
inside_ ^ invert_b_);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// Special case to test whether the last vertex of a loop should be emitted
|
|
1589
|
+
// as an isolated degenerate vertex.
|
|
1590
|
+
if (emit_shared && emit_degenerate && r.a1_matches_polygon &&
|
|
1591
|
+
it->crossings_complete() && is_chain_last_vertex_isolated(a_id)) {
|
|
1592
|
+
if (!AddPointEdge(a.v1, 2)) return false;
|
|
1593
|
+
}
|
|
1594
|
+
return true;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// Returns a summary of the relationship between a test edge from region A and
|
|
1598
|
+
// a set of crossing edges from region B (see EdgeCrossingResult).
|
|
1599
|
+
//
|
|
1600
|
+
// NOTE(ericv): We could save a bit of work when matching polygon vertices by
|
|
1601
|
+
// passing in a flag saying whether this information is needed. For example
|
|
1602
|
+
// if is only needed in ProcessEdge2 when (emit_shared && emit_degenerate).
|
|
1603
|
+
S2BooleanOperation::Impl::CrossingProcessor::EdgeCrossingResult
|
|
1604
|
+
S2BooleanOperation::Impl::CrossingProcessor::ProcessEdgeCrossings(
|
|
1605
|
+
ShapeEdgeId a_id, const S2Shape::Edge& a, CrossingIterator* it) {
|
|
1606
|
+
EdgeCrossingResult r;
|
|
1607
|
+
if (it->Done(a_id)) return r;
|
|
1608
|
+
|
|
1609
|
+
// TODO(ericv): bool a_degenerate = (a.v0 == a.v1);
|
|
1610
|
+
for (; !it->Done(a_id); it->Next()) {
|
|
1611
|
+
// Polylines and polygons are not affected by point geometry.
|
|
1612
|
+
if (it->b_dimension() == 0) continue;
|
|
1613
|
+
S2Shape::Edge b = it->b_edge();
|
|
1614
|
+
if (it->is_interior_crossing()) {
|
|
1615
|
+
// The crossing occurs in the edge interior. The condition below says
|
|
1616
|
+
// that (1) polyline crossings don't affect polygon output, and (2)
|
|
1617
|
+
// subtracting a crossing polyline from a polyline has no effect.
|
|
1618
|
+
if (a_dimension_ <= it->b_dimension() &&
|
|
1619
|
+
!(invert_b_ != invert_result_ && it->b_dimension() == 1)) {
|
|
1620
|
+
SourceId src_id(b_region_id_, it->b_shape_id(), it->b_edge_id());
|
|
1621
|
+
AddCrossing(make_pair(src_id, it->left_to_right()));
|
|
1622
|
+
}
|
|
1623
|
+
r.interior_crossings += (it->b_dimension() == 1) ? 2 : 1;
|
|
1624
|
+
} else if (it->b_dimension() == 1) {
|
|
1625
|
+
// Polygons are not affected by polyline geometry.
|
|
1626
|
+
if (a_dimension_ == 2) continue;
|
|
1627
|
+
if ((a.v0 == b.v0 && a.v1 == b.v1) || (a.v0 == b.v1 && a.v1 == b.v0)) {
|
|
1628
|
+
r.matches_polyline = true;
|
|
1629
|
+
}
|
|
1630
|
+
if ((a.v0 == b.v0 || a.v0 == b.v1) &&
|
|
1631
|
+
PolylineEdgeContainsVertex(a.v0, *it)) {
|
|
1632
|
+
r.a0_matches_polyline = true;
|
|
1633
|
+
}
|
|
1634
|
+
if ((a.v1 == b.v0 || a.v1 == b.v1) &&
|
|
1635
|
+
PolylineEdgeContainsVertex(a.v1, *it)) {
|
|
1636
|
+
r.a1_matches_polyline = true;
|
|
1637
|
+
}
|
|
1638
|
+
} else {
|
|
1639
|
+
S2_DCHECK_EQ(2, it->b_dimension());
|
|
1640
|
+
if (a.v0 == b.v0 && a.v1 == b.v1) {
|
|
1641
|
+
++r.a0_crossings;
|
|
1642
|
+
r.matches_polygon = true;
|
|
1643
|
+
} else if (a.v0 == b.v1 && a.v1 == b.v0) {
|
|
1644
|
+
++r.a0_crossings;
|
|
1645
|
+
r.matches_sibling = true;
|
|
1646
|
+
} else if (it->is_vertex_crossing()) {
|
|
1647
|
+
if (a.v0 == b.v0 || a.v0 == b.v1) {
|
|
1648
|
+
++r.a0_crossings;
|
|
1649
|
+
} else {
|
|
1650
|
+
++r.a1_crossings;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
if (a.v0 == b.v0 || a.v0 == b.v1) {
|
|
1654
|
+
r.a0_matches_polygon = true;
|
|
1655
|
+
}
|
|
1656
|
+
if (a.v1 == b.v0 || a.v1 == b.v1) {
|
|
1657
|
+
r.a1_matches_polygon = true;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return r;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
// Returns true if the vertex "v" is contained by the polyline edge referred
|
|
1665
|
+
// to by the CrossingIterator "it", taking into account the PolylineModel.
|
|
1666
|
+
//
|
|
1667
|
+
// REQUIRES: it.b_dimension() == 1
|
|
1668
|
+
// REQUIRES: "v" is an endpoint of it.b_edge()
|
|
1669
|
+
bool S2BooleanOperation::Impl::CrossingProcessor::PolylineEdgeContainsVertex(
|
|
1670
|
+
const S2Point& v, const CrossingIterator& it) const {
|
|
1671
|
+
S2_DCHECK_EQ(1, it.b_dimension());
|
|
1672
|
+
S2_DCHECK(it.b_edge().v0 == v || it.b_edge().v1 == v);
|
|
1673
|
+
|
|
1674
|
+
// Closed polylines contain all their vertices.
|
|
1675
|
+
if (polyline_model_ == PolylineModel::CLOSED) return true;
|
|
1676
|
+
|
|
1677
|
+
// Note that the code below is structured so that it.b_edge() is not usually
|
|
1678
|
+
// needed (since accessing the edge can be relatively expensive).
|
|
1679
|
+
const auto& b_chain = it.b_chain_info();
|
|
1680
|
+
int b_edge_id = it.b_edge_id();
|
|
1681
|
+
|
|
1682
|
+
// The last polyline vertex is never contained. (For polyline loops, it is
|
|
1683
|
+
// sufficient to treat the first vertex as begin contained.) This case also
|
|
1684
|
+
// handles degenerate polylines (polylines with one edge where v0 == v1),
|
|
1685
|
+
// which do not contain any points.
|
|
1686
|
+
if (b_edge_id == b_chain.limit - 1 && v == it.b_edge().v1) return false;
|
|
1687
|
+
|
|
1688
|
+
// Otherwise all interior vertices are contained. The first polyline
|
|
1689
|
+
// vertex is contained if either the polyline model is not OPEN, or the
|
|
1690
|
+
// polyline forms a loop and polyline_loops_have_boundaries_ is false.
|
|
1691
|
+
if (polyline_contains_v0(b_edge_id, b_chain.start)) return true;
|
|
1692
|
+
if (v != it.b_edge().v0) return true;
|
|
1693
|
+
if (polyline_loops_have_boundaries_) return false;
|
|
1694
|
+
return v == it.b_shape().chain_edge(b_chain.chain_id,
|
|
1695
|
+
b_chain.limit - b_chain.start - 1).v1;
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// Translates the temporary representation of crossing edges (SourceId) into
|
|
1699
|
+
// the format expected by EdgeClippingLayer (InputEdgeId).
|
|
1700
|
+
void S2BooleanOperation::Impl::CrossingProcessor::DoneBoundaryPair() {
|
|
1701
|
+
// Add entries that translate the "special" crossings.
|
|
1702
|
+
source_id_map_[SourceId(kSetInside)] = kSetInside;
|
|
1703
|
+
source_id_map_[SourceId(kSetInvertB)] = kSetInvertB;
|
|
1704
|
+
source_id_map_[SourceId(kSetReverseA)] = kSetReverseA;
|
|
1705
|
+
input_crossings_->reserve(input_crossings_->size() +
|
|
1706
|
+
source_edge_crossings_.size());
|
|
1707
|
+
for (const auto& tmp : source_edge_crossings_) {
|
|
1708
|
+
auto it = source_id_map_.find(tmp.second.first);
|
|
1709
|
+
S2_DCHECK(it != source_id_map_.end());
|
|
1710
|
+
input_crossings_->push_back(make_pair(
|
|
1711
|
+
tmp.first, CrossingInputEdge(it->second, tmp.second.second)));
|
|
1712
|
+
}
|
|
1713
|
+
source_edge_crossings_.clear();
|
|
1714
|
+
source_id_map_.clear();
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// Clips the boundary of A to the interior of the opposite region B and adds
|
|
1718
|
+
// the resulting edges to the output. Optionally, any combination of region
|
|
1719
|
+
// A, region B, and the result may be inverted, which allows operations such
|
|
1720
|
+
// as union and difference to be implemented.
|
|
1721
|
+
//
|
|
1722
|
+
// Note that when an input region is inverted with respect to the output
|
|
1723
|
+
// (e.g., invert_a != invert_result), all polygon edges are reversed and all
|
|
1724
|
+
// points and polylines are discarded, since the complement of such objects
|
|
1725
|
+
// cannot be represented. (If you want to compute the complement of points
|
|
1726
|
+
// or polylines, you can use S2LaxPolygonShape to represent your geometry as
|
|
1727
|
+
// degenerate polygons instead.)
|
|
1728
|
+
//
|
|
1729
|
+
// This method must be called an even number of times (first to clip A to B
|
|
1730
|
+
// and then to clip B to A), calling DoneBoundaryPair() after each pair.
|
|
1731
|
+
//
|
|
1732
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1733
|
+
// as soon as the result is known to be non-empty.
|
|
1734
|
+
bool S2BooleanOperation::Impl::AddBoundary(
|
|
1735
|
+
int a_region_id, bool invert_a, bool invert_b, bool invert_result,
|
|
1736
|
+
const vector<ShapeEdgeId>& a_chain_starts, CrossingProcessor* cp) {
|
|
1737
|
+
const S2ShapeIndex& a_index = *op_->regions_[a_region_id];
|
|
1738
|
+
const S2ShapeIndex& b_index = *op_->regions_[1 - a_region_id];
|
|
1739
|
+
if (!GetIndexCrossings(a_region_id)) return false;
|
|
1740
|
+
cp->StartBoundary(a_region_id, invert_a, invert_b, invert_result);
|
|
1741
|
+
|
|
1742
|
+
// Walk the boundary of region A and build a list of all edge crossings.
|
|
1743
|
+
// We also keep track of whether the current vertex is inside region B.
|
|
1744
|
+
auto next_start = a_chain_starts.begin();
|
|
1745
|
+
CrossingIterator next_crossing(&b_index, &index_crossings_,
|
|
1746
|
+
true /*crossings_complete*/);
|
|
1747
|
+
ShapeEdgeId next_id = min(*next_start, next_crossing.a_id());
|
|
1748
|
+
while (next_id != kSentinel) {
|
|
1749
|
+
int a_shape_id = next_id.shape_id;
|
|
1750
|
+
const S2Shape& a_shape = *a_index.shape(a_shape_id);
|
|
1751
|
+
cp->StartShape(&a_shape);
|
|
1752
|
+
while (next_id.shape_id == a_shape_id) {
|
|
1753
|
+
// TODO(ericv): Special handling of dimension 0? Can omit most of this
|
|
1754
|
+
// code, including the loop, since all chains are of length 1.
|
|
1755
|
+
int edge_id = next_id.edge_id;
|
|
1756
|
+
S2Shape::ChainPosition chain_position = a_shape.chain_position(edge_id);
|
|
1757
|
+
int chain_id = chain_position.chain_id;
|
|
1758
|
+
S2Shape::Chain chain = a_shape.chain(chain_id);
|
|
1759
|
+
bool start_inside = (next_id == *next_start);
|
|
1760
|
+
if (start_inside) ++next_start;
|
|
1761
|
+
cp->StartChain(chain_id, chain, start_inside);
|
|
1762
|
+
int chain_limit = chain.start + chain.length;
|
|
1763
|
+
while (edge_id < chain_limit) {
|
|
1764
|
+
ShapeEdgeId a_id(a_shape_id, edge_id);
|
|
1765
|
+
S2_DCHECK(cp->inside() || next_crossing.a_id() == a_id);
|
|
1766
|
+
if (!cp->ProcessEdge(a_id, &next_crossing)) {
|
|
1767
|
+
return false;
|
|
1768
|
+
}
|
|
1769
|
+
if (cp->inside()) {
|
|
1770
|
+
++edge_id;
|
|
1771
|
+
} else if (next_crossing.a_id().shape_id == a_shape_id &&
|
|
1772
|
+
next_crossing.a_id().edge_id < chain_limit) {
|
|
1773
|
+
edge_id = next_crossing.a_id().edge_id;
|
|
1774
|
+
} else {
|
|
1775
|
+
break;
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
next_id = min(*next_start, next_crossing.a_id());
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
return true;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// Returns the first edge of each edge chain from "a_region_id" whose first
|
|
1785
|
+
// vertex is contained by opposite region's polygons (using the semi-open
|
|
1786
|
+
// boundary model). Each input region and the result region are inverted as
|
|
1787
|
+
// specified (invert_a, invert_b, and invert_result) before testing for
|
|
1788
|
+
// containment. The algorithm uses these "chain starts" in order to clip the
|
|
1789
|
+
// boundary of A to the interior of B in an output-senstive way.
|
|
1790
|
+
//
|
|
1791
|
+
// This method supports "early exit" in the case where a boolean predicate is
|
|
1792
|
+
// being evaluated and the algorithm discovers that the result region will be
|
|
1793
|
+
// non-empty.
|
|
1794
|
+
bool S2BooleanOperation::Impl::GetChainStarts(
|
|
1795
|
+
int a_region_id, bool invert_a, bool invert_b, bool invert_result,
|
|
1796
|
+
CrossingProcessor* cp, vector<ShapeEdgeId>* chain_starts) {
|
|
1797
|
+
const S2ShapeIndex& a_index = *op_->regions_[a_region_id];
|
|
1798
|
+
const S2ShapeIndex& b_index = *op_->regions_[1 - a_region_id];
|
|
1799
|
+
|
|
1800
|
+
if (is_boolean_output()) {
|
|
1801
|
+
// If boolean output is requested, then we use the CrossingProcessor to
|
|
1802
|
+
// determine whether the first edge of each chain will be emitted to the
|
|
1803
|
+
// output region. This lets us terminate the operation early in many
|
|
1804
|
+
// cases.
|
|
1805
|
+
cp->StartBoundary(a_region_id, invert_a, invert_b, invert_result);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// If region B has no two-dimensional shapes and is not inverted, then by
|
|
1809
|
+
// definition no chain starts are contained. However if boolean output is
|
|
1810
|
+
// requested then we check for containment anyway, since as a side effect we
|
|
1811
|
+
// may discover that the result region is non-empty and terminate the entire
|
|
1812
|
+
// operation early.
|
|
1813
|
+
bool b_has_interior = HasInterior(b_index);
|
|
1814
|
+
if (b_has_interior || invert_b || is_boolean_output()) {
|
|
1815
|
+
auto query = MakeS2ContainsPointQuery(&b_index);
|
|
1816
|
+
int num_shape_ids = a_index.num_shape_ids();
|
|
1817
|
+
for (int shape_id = 0; shape_id < num_shape_ids; ++shape_id) {
|
|
1818
|
+
S2Shape* a_shape = a_index.shape(shape_id);
|
|
1819
|
+
if (a_shape == nullptr) continue;
|
|
1820
|
+
|
|
1821
|
+
// If region A is being subtracted from region B, points and polylines
|
|
1822
|
+
// in region A can be ignored since these shapes never contribute to the
|
|
1823
|
+
// output (they can only remove edges from region B).
|
|
1824
|
+
if (invert_a != invert_result && a_shape->dimension() < 2) continue;
|
|
1825
|
+
|
|
1826
|
+
if (is_boolean_output()) cp->StartShape(a_shape);
|
|
1827
|
+
int num_chains = a_shape->num_chains();
|
|
1828
|
+
for (int chain_id = 0; chain_id < num_chains; ++chain_id) {
|
|
1829
|
+
S2Shape::Chain chain = a_shape->chain(chain_id);
|
|
1830
|
+
if (chain.length == 0) continue;
|
|
1831
|
+
ShapeEdge a(shape_id, chain.start, a_shape->chain_edge(chain_id, 0));
|
|
1832
|
+
bool inside = (b_has_interior && query.Contains(a.v0())) != invert_b;
|
|
1833
|
+
if (inside) {
|
|
1834
|
+
chain_starts->push_back(ShapeEdgeId(shape_id, chain.start));
|
|
1835
|
+
}
|
|
1836
|
+
if (is_boolean_output()) {
|
|
1837
|
+
cp->StartChain(chain_id, chain, inside);
|
|
1838
|
+
if (!ProcessIncidentEdges(a, &query, cp)) return false;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
chain_starts->push_back(kSentinel);
|
|
1844
|
+
return true;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
bool S2BooleanOperation::Impl::ProcessIncidentEdges(
|
|
1848
|
+
const ShapeEdge& a, S2ContainsPointQuery<S2ShapeIndex>* query,
|
|
1849
|
+
CrossingProcessor* cp) {
|
|
1850
|
+
tmp_crossings_.clear();
|
|
1851
|
+
query->VisitIncidentEdges(a.v0(), [&a, this](const ShapeEdge& b) {
|
|
1852
|
+
return AddIndexCrossing(a, b, false /*is_interior*/, &tmp_crossings_);
|
|
1853
|
+
});
|
|
1854
|
+
// Fast path for the common case where there are no incident edges. We
|
|
1855
|
+
// return false (terminating early) if the first chain edge will be emitted.
|
|
1856
|
+
if (tmp_crossings_.empty()) {
|
|
1857
|
+
return !cp->inside();
|
|
1858
|
+
}
|
|
1859
|
+
// Otherwise we invoke the full CrossingProcessor logic to determine whether
|
|
1860
|
+
// the first chain edge will be emitted.
|
|
1861
|
+
if (tmp_crossings_.size() > 1) {
|
|
1862
|
+
std::sort(tmp_crossings_.begin(), tmp_crossings_.end());
|
|
1863
|
+
// VisitIncidentEdges() should not generate any duplicate values.
|
|
1864
|
+
S2_DCHECK(std::adjacent_find(tmp_crossings_.begin(), tmp_crossings_.end()) ==
|
|
1865
|
+
tmp_crossings_.end());
|
|
1866
|
+
}
|
|
1867
|
+
tmp_crossings_.push_back(IndexCrossing(kSentinel, kSentinel));
|
|
1868
|
+
CrossingIterator next_crossing(&query->index(), &tmp_crossings_,
|
|
1869
|
+
false /*crossings_complete*/);
|
|
1870
|
+
return cp->ProcessEdge(a.id(), &next_crossing);
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
bool S2BooleanOperation::Impl::HasInterior(const S2ShapeIndex& index) {
|
|
1874
|
+
for (int s = index.num_shape_ids(); --s >= 0; ) {
|
|
1875
|
+
S2Shape* shape = index.shape(s);
|
|
1876
|
+
if (shape && shape->dimension() == 2) return true;
|
|
1877
|
+
}
|
|
1878
|
+
return false;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
inline bool S2BooleanOperation::Impl::AddIndexCrossing(
|
|
1882
|
+
const ShapeEdge& a, const ShapeEdge& b, bool is_interior,
|
|
1883
|
+
IndexCrossings* crossings) {
|
|
1884
|
+
crossings->push_back(IndexCrossing(a.id(), b.id()));
|
|
1885
|
+
IndexCrossing* crossing = &crossings->back();
|
|
1886
|
+
if (is_interior) {
|
|
1887
|
+
crossing->is_interior_crossing = true;
|
|
1888
|
+
if (s2pred::Sign(a.v0(), a.v1(), b.v0()) > 0) {
|
|
1889
|
+
crossing->left_to_right = true;
|
|
1890
|
+
}
|
|
1891
|
+
} else {
|
|
1892
|
+
// TODO(ericv): This field isn't used unless one shape is a polygon and
|
|
1893
|
+
// the other is a polyline or polygon, but we don't have the shape
|
|
1894
|
+
// dimension information readily available here.
|
|
1895
|
+
if (S2::VertexCrossing(a.v0(), a.v1(), b.v0(), b.v1())) {
|
|
1896
|
+
crossing->is_vertex_crossing = true;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
return true; // Continue visiting.
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
// Initialize index_crossings_ to the set of crossing edge pairs such that the
|
|
1903
|
+
// first element of each pair is an edge from "region_id".
|
|
1904
|
+
//
|
|
1905
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1906
|
+
// as soon as the result is known to be non-empty.
|
|
1907
|
+
bool S2BooleanOperation::Impl::GetIndexCrossings(int region_id) {
|
|
1908
|
+
if (region_id == index_crossings_first_region_id_) return true;
|
|
1909
|
+
if (index_crossings_first_region_id_ < 0) {
|
|
1910
|
+
S2_DCHECK_EQ(region_id, 0); // For efficiency, not correctness.
|
|
1911
|
+
if (!s2shapeutil::VisitCrossingEdgePairs(
|
|
1912
|
+
*op_->regions_[0], *op_->regions_[1],
|
|
1913
|
+
s2shapeutil::CrossingType::ALL,
|
|
1914
|
+
[this](const ShapeEdge& a, const ShapeEdge& b, bool is_interior) {
|
|
1915
|
+
// For all supported operations (union, intersection, and
|
|
1916
|
+
// difference), if the input edges have an interior crossing
|
|
1917
|
+
// then the output is guaranteed to have at least one edge.
|
|
1918
|
+
if (is_interior && is_boolean_output()) return false;
|
|
1919
|
+
return AddIndexCrossing(a, b, is_interior, &index_crossings_);
|
|
1920
|
+
})) {
|
|
1921
|
+
return false;
|
|
1922
|
+
}
|
|
1923
|
+
if (index_crossings_.size() > 1) {
|
|
1924
|
+
std::sort(index_crossings_.begin(), index_crossings_.end());
|
|
1925
|
+
index_crossings_.erase(
|
|
1926
|
+
std::unique(index_crossings_.begin(), index_crossings_.end()),
|
|
1927
|
+
index_crossings_.end());
|
|
1928
|
+
}
|
|
1929
|
+
// Add a sentinel value to simplify the loop logic.
|
|
1930
|
+
index_crossings_.push_back(IndexCrossing(kSentinel, kSentinel));
|
|
1931
|
+
index_crossings_first_region_id_ = 0;
|
|
1932
|
+
}
|
|
1933
|
+
if (region_id != index_crossings_first_region_id_) {
|
|
1934
|
+
for (auto& crossing : index_crossings_) {
|
|
1935
|
+
swap(crossing.a, crossing.b);
|
|
1936
|
+
// The following predicates get inverted when the edges are swapped.
|
|
1937
|
+
crossing.left_to_right ^= true;
|
|
1938
|
+
crossing.is_vertex_crossing ^= true;
|
|
1939
|
+
}
|
|
1940
|
+
std::sort(index_crossings_.begin(), index_crossings_.end());
|
|
1941
|
+
index_crossings_first_region_id_ = region_id;
|
|
1942
|
+
}
|
|
1943
|
+
return true;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1947
|
+
// as soon as the result is known to be non-empty.
|
|
1948
|
+
bool S2BooleanOperation::Impl::AddBoundaryPair(
|
|
1949
|
+
bool invert_a, bool invert_b, bool invert_result, CrossingProcessor* cp) {
|
|
1950
|
+
// Optimization: if the operation is DIFFERENCE or SYMMETRIC_DIFFERENCE,
|
|
1951
|
+
// it is worthwhile checking whether the two regions are identical (in which
|
|
1952
|
+
// case the output is empty).
|
|
1953
|
+
//
|
|
1954
|
+
// TODO(ericv): When boolean output is requested there are other quick
|
|
1955
|
+
// checks that could be done here, such as checking whether a full cell from
|
|
1956
|
+
// one S2ShapeIndex intersects a non-empty cell of the other S2ShapeIndex.
|
|
1957
|
+
auto type = op_->op_type();
|
|
1958
|
+
if (type == OpType::DIFFERENCE || type == OpType::SYMMETRIC_DIFFERENCE) {
|
|
1959
|
+
if (AreRegionsIdentical()) return true;
|
|
1960
|
+
} else if (!is_boolean_output()) {
|
|
1961
|
+
}
|
|
1962
|
+
vector<ShapeEdgeId> a_starts, b_starts;
|
|
1963
|
+
if (!GetChainStarts(0, invert_a, invert_b, invert_result, cp, &a_starts) ||
|
|
1964
|
+
!GetChainStarts(1, invert_b, invert_a, invert_result, cp, &b_starts) ||
|
|
1965
|
+
!AddBoundary(0, invert_a, invert_b, invert_result, a_starts, cp) ||
|
|
1966
|
+
!AddBoundary(1, invert_b, invert_a, invert_result, b_starts, cp)) {
|
|
1967
|
+
return false;
|
|
1968
|
+
}
|
|
1969
|
+
if (!is_boolean_output()) cp->DoneBoundaryPair();
|
|
1970
|
+
return true;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// Supports "early exit" in the case of boolean results by returning false
|
|
1974
|
+
// as soon as the result is known to be non-empty.
|
|
1975
|
+
bool S2BooleanOperation::Impl::BuildOpType(OpType op_type) {
|
|
1976
|
+
// CrossingProcessor does the real work of emitting the output edges.
|
|
1977
|
+
CrossingProcessor cp(op_->options_.polygon_model(),
|
|
1978
|
+
op_->options_.polyline_model(),
|
|
1979
|
+
op_->options_.polyline_loops_have_boundaries(),
|
|
1980
|
+
builder_.get(), &input_dimensions_, &input_crossings_);
|
|
1981
|
+
switch (op_type) {
|
|
1982
|
+
case OpType::UNION:
|
|
1983
|
+
// A | B == ~(~A & ~B)
|
|
1984
|
+
return AddBoundaryPair(true, true, true, &cp);
|
|
1985
|
+
|
|
1986
|
+
case OpType::INTERSECTION:
|
|
1987
|
+
// A & B
|
|
1988
|
+
return AddBoundaryPair(false, false, false, &cp);
|
|
1989
|
+
|
|
1990
|
+
case OpType::DIFFERENCE:
|
|
1991
|
+
// A - B = A & ~B
|
|
1992
|
+
return AddBoundaryPair(false, true, false, &cp);
|
|
1993
|
+
|
|
1994
|
+
case OpType::SYMMETRIC_DIFFERENCE:
|
|
1995
|
+
// Compute the union of (A - B) and (B - A).
|
|
1996
|
+
return (AddBoundaryPair(false, true, false, &cp) &&
|
|
1997
|
+
AddBoundaryPair(true, false, false, &cp));
|
|
1998
|
+
}
|
|
1999
|
+
S2_LOG(FATAL) << "Invalid S2BooleanOperation::OpType";
|
|
2000
|
+
return false;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// Returns a bit mask indicating which of the 6 S2 cube faces intersect the
|
|
2004
|
+
// index contents.
|
|
2005
|
+
uint8 GetFaceMask(const S2ShapeIndex& index) {
|
|
2006
|
+
uint8 mask = 0;
|
|
2007
|
+
S2ShapeIndex::Iterator it(&index, S2ShapeIndex::BEGIN);
|
|
2008
|
+
while (!it.done()) {
|
|
2009
|
+
int face = it.id().face();
|
|
2010
|
+
mask |= 1 << face;
|
|
2011
|
+
it.Seek(S2CellId::FromFace(face + 1).range_min());
|
|
2012
|
+
}
|
|
2013
|
+
return mask;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
// Given a polygon edge graph containing only degenerate edges and sibling edge
|
|
2017
|
+
// pairs, the purpose of this function is to decide whether the polygon is empty
|
|
2018
|
+
// or full except for the degeneracies, i.e. whether the degeneracies represent
|
|
2019
|
+
// shells or holes.
|
|
2020
|
+
bool S2BooleanOperation::Impl::IsFullPolygonResult(
|
|
2021
|
+
const S2Builder::Graph& g, S2Error* error) const {
|
|
2022
|
+
// If there are no edges of dimension 2, the result could be either the
|
|
2023
|
+
// empty polygon or the full polygon. Note that this is harder to determine
|
|
2024
|
+
// than you might think due to snapping. For example, the union of two
|
|
2025
|
+
// non-empty polygons can be empty, because both polygons consist of tiny
|
|
2026
|
+
// loops that are eliminated by snapping. Similarly, even if two polygons
|
|
2027
|
+
// both contain a common point their intersection can still be empty.
|
|
2028
|
+
//
|
|
2029
|
+
// We distinguish empty from full results using two heuristics:
|
|
2030
|
+
//
|
|
2031
|
+
// 1. We compute a bit mask representing the subset of the six S2 cube faces
|
|
2032
|
+
// intersected by each input geometry, and use this to determine if only
|
|
2033
|
+
// one of the two results is possible. (This test is very fast.) Note
|
|
2034
|
+
// that snapping will never cause the result to cover an entire extra
|
|
2035
|
+
// cube face because the maximum allowed snap radius is too small.
|
|
2036
|
+
S2_DCHECK_LE(S2Builder::SnapFunction::kMaxSnapRadius().degrees(), 70);
|
|
2037
|
+
//
|
|
2038
|
+
// 2. We compute the area of each input geometry, and use this to bound the
|
|
2039
|
+
// minimum and maximum area of the result. If only one of {0, 4*Pi} is
|
|
2040
|
+
// possible then we are done. If neither is possible then we choose the
|
|
2041
|
+
// one that is closest to being possible (since snapping can change the
|
|
2042
|
+
// result area). Both results are possible only when computing the
|
|
2043
|
+
// symmetric difference of two regions of area 2*Pi each, in which case we
|
|
2044
|
+
// must resort to additional heuristics (see below).
|
|
2045
|
+
//
|
|
2046
|
+
// TODO(ericv): Implement a predicate that uses the results of edge snapping
|
|
2047
|
+
// directly, rather than computing areas. This would not only be much faster
|
|
2048
|
+
// but would also allows all cases to be handled 100% robustly.
|
|
2049
|
+
const S2ShapeIndex& a = *op_->regions_[0];
|
|
2050
|
+
const S2ShapeIndex& b = *op_->regions_[1];
|
|
2051
|
+
switch (op_->op_type()) {
|
|
2052
|
+
case OpType::UNION:
|
|
2053
|
+
return IsFullPolygonUnion(a, b);
|
|
2054
|
+
|
|
2055
|
+
case OpType::INTERSECTION:
|
|
2056
|
+
return IsFullPolygonIntersection(a, b);
|
|
2057
|
+
|
|
2058
|
+
case OpType::DIFFERENCE:
|
|
2059
|
+
return IsFullPolygonDifference(a, b);
|
|
2060
|
+
|
|
2061
|
+
case OpType::SYMMETRIC_DIFFERENCE:
|
|
2062
|
+
return IsFullPolygonSymmetricDifference(a, b);
|
|
2063
|
+
|
|
2064
|
+
default:
|
|
2065
|
+
S2_LOG(FATAL) << "Invalid S2BooleanOperation::OpType";
|
|
2066
|
+
return false;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
bool S2BooleanOperation::Impl::IsFullPolygonUnion(
|
|
2071
|
+
const S2ShapeIndex& a, const S2ShapeIndex& b) const {
|
|
2072
|
+
// See comments in IsFullPolygonResult(). The most common case is that
|
|
2073
|
+
// neither input polygon is empty but the result is empty due to snapping.
|
|
2074
|
+
|
|
2075
|
+
// The result can be full only if the union of the two input geometries
|
|
2076
|
+
// intersects all six faces of the S2 cube. This test is fast.
|
|
2077
|
+
if ((GetFaceMask(a) | GetFaceMask(b)) != kAllFacesMask) return false;
|
|
2078
|
+
|
|
2079
|
+
// The union area satisfies:
|
|
2080
|
+
//
|
|
2081
|
+
// max(A, B) <= Union(A, B) <= min(4*Pi, A + B)
|
|
2082
|
+
//
|
|
2083
|
+
// where A, B can refer to a polygon or its area. We then choose the result
|
|
2084
|
+
// that assumes the smallest amount of error.
|
|
2085
|
+
double a_area = S2::GetArea(a), b_area = S2::GetArea(b);
|
|
2086
|
+
double min_area = max(a_area, b_area);
|
|
2087
|
+
double max_area = min(4 * M_PI, a_area + b_area);
|
|
2088
|
+
return min_area > 4 * M_PI - max_area;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
bool S2BooleanOperation::Impl::IsFullPolygonIntersection(
|
|
2092
|
+
const S2ShapeIndex& a, const S2ShapeIndex& b) const {
|
|
2093
|
+
// See comments in IsFullPolygonResult(). By far the most common case is
|
|
2094
|
+
// that the result is empty.
|
|
2095
|
+
|
|
2096
|
+
// The result can be full only if each of the two input geometries
|
|
2097
|
+
// intersects all six faces of the S2 cube. This test is fast.
|
|
2098
|
+
if ((GetFaceMask(a) & GetFaceMask(b)) != kAllFacesMask) return false;
|
|
2099
|
+
|
|
2100
|
+
// The intersection area satisfies:
|
|
2101
|
+
//
|
|
2102
|
+
// max(0, A + B - 4*Pi) <= Intersection(A, B) <= min(A, B)
|
|
2103
|
+
//
|
|
2104
|
+
// where A, B can refer to a polygon or its area. We then choose the result
|
|
2105
|
+
// that assumes the smallest amount of error.
|
|
2106
|
+
double a_area = S2::GetArea(a), b_area = S2::GetArea(b);
|
|
2107
|
+
double min_area = max(0.0, a_area + b_area - 4 * M_PI);
|
|
2108
|
+
double max_area = min(a_area, b_area);
|
|
2109
|
+
return min_area > 4 * M_PI - max_area;
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
bool S2BooleanOperation::Impl::IsFullPolygonDifference(
|
|
2113
|
+
const S2ShapeIndex& a, const S2ShapeIndex& b) const {
|
|
2114
|
+
// See comments in IsFullPolygonResult(). By far the most common case is
|
|
2115
|
+
// that the result is empty.
|
|
2116
|
+
|
|
2117
|
+
// The result can be full only if each cube face is intersected by the first
|
|
2118
|
+
// geometry. (The second geometry is irrelevant, since for example it could
|
|
2119
|
+
// consist of a tiny loop on each S2 cube face.) This test is fast.
|
|
2120
|
+
if (GetFaceMask(a) != kAllFacesMask) return false;
|
|
2121
|
+
|
|
2122
|
+
// The difference area satisfies:
|
|
2123
|
+
//
|
|
2124
|
+
// max(0, A - B) <= Difference(A, B) <= min(A, 4*Pi - B)
|
|
2125
|
+
//
|
|
2126
|
+
// where A, B can refer to a polygon or its area. We then choose the result
|
|
2127
|
+
// that assumes the smallest amount of error.
|
|
2128
|
+
double a_area = S2::GetArea(a), b_area = S2::GetArea(b);
|
|
2129
|
+
double min_area = max(0.0, a_area - b_area);
|
|
2130
|
+
double max_area = min(a_area, 4 * M_PI - b_area);
|
|
2131
|
+
return min_area > 4 * M_PI - max_area;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
bool S2BooleanOperation::Impl::IsFullPolygonSymmetricDifference(
|
|
2135
|
+
const S2ShapeIndex& a, const S2ShapeIndex& b) const {
|
|
2136
|
+
// See comments in IsFullPolygonResult(). By far the most common case is
|
|
2137
|
+
// that the result is empty.
|
|
2138
|
+
|
|
2139
|
+
// The result can be full only if the union of the two input geometries
|
|
2140
|
+
// intersects all six faces of the S2 cube. This test is fast.
|
|
2141
|
+
uint8 a_mask = GetFaceMask(a);
|
|
2142
|
+
uint8 b_mask = GetFaceMask(b);
|
|
2143
|
+
if ((a_mask | b_mask) != kAllFacesMask) return false;
|
|
2144
|
+
|
|
2145
|
+
// The symmetric difference area satisfies:
|
|
2146
|
+
//
|
|
2147
|
+
// |A - B| <= SymmetricDifference(A, B) <= 4*Pi - |4*Pi - (A + B)|
|
|
2148
|
+
//
|
|
2149
|
+
// where A, B can refer to a polygon or its area.
|
|
2150
|
+
double a_area = S2::GetArea(a), b_area = S2::GetArea(b);
|
|
2151
|
+
double min_area = fabs(a_area - b_area);
|
|
2152
|
+
double max_area = 4 * M_PI - fabs(4 * M_PI - (a_area + b_area));
|
|
2153
|
+
|
|
2154
|
+
// Now we choose the result that assumes the smallest amount of error
|
|
2155
|
+
// (min_area in the empty case, and (4*Pi - max_area) in the full case).
|
|
2156
|
+
// However in the case of symmetric difference these two errors may be equal,
|
|
2157
|
+
// meaning that the result is ambiguous. This happens when both polygons have
|
|
2158
|
+
// area 2*Pi. Furthermore, this can happen even when the areas are not
|
|
2159
|
+
// exactly 2*Pi due to snapping and area calculation errors.
|
|
2160
|
+
//
|
|
2161
|
+
// To determine whether the result is ambiguous, we compute a rough estimate
|
|
2162
|
+
// of the maximum expected area error (including errors due to snapping),
|
|
2163
|
+
// using the worst-case error bound for a hemisphere defined by 4 vertices.
|
|
2164
|
+
auto edge_snap_radius = op_->options_.snap_function().snap_radius() +
|
|
2165
|
+
S2::kIntersectionError; // split_crossing_edges
|
|
2166
|
+
double hemisphere_area_error = 2 * M_PI * edge_snap_radius.radians() +
|
|
2167
|
+
40 * DBL_EPSILON; // GetCurvatureMaxError
|
|
2168
|
+
|
|
2169
|
+
// The following sign is the difference between the error needed for an empty
|
|
2170
|
+
// result and the error needed for a full result. It is negative if an
|
|
2171
|
+
// empty result is possible, positive if a full result is possible, and zero
|
|
2172
|
+
// if both results are possible.
|
|
2173
|
+
double error_sign = min_area - (4 * M_PI - max_area);
|
|
2174
|
+
if (fabs(error_sign) <= hemisphere_area_error) {
|
|
2175
|
+
// Handling the ambiguous case correctly requires a more sophisticated
|
|
2176
|
+
// algorithm (see below), but we can at least handle the simple cases by
|
|
2177
|
+
// testing whether both input geometries intersect all 6 cube faces. If
|
|
2178
|
+
// not, then the result is definitely full.
|
|
2179
|
+
if ((a_mask & b_mask) != kAllFacesMask) return true;
|
|
2180
|
+
|
|
2181
|
+
// Otherwise both regions have area 2*Pi and intersect all 6 cube faces.
|
|
2182
|
+
// We choose "empty" in this case under the assumption that it is more
|
|
2183
|
+
// likely that the user is computing the difference between two nearly
|
|
2184
|
+
// identical polygons.
|
|
2185
|
+
//
|
|
2186
|
+
// TODO(ericv): Implement a robust algorithm based on examining the edge
|
|
2187
|
+
// snapping results directly, or alternatively add another heuristic (such
|
|
2188
|
+
// as testing containment of random points, or using a larger bit mask in
|
|
2189
|
+
// the tests above, e.g. a 24-bit mask representing all level 1 cells).
|
|
2190
|
+
return false;
|
|
2191
|
+
}
|
|
2192
|
+
return error_sign > 0;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
bool S2BooleanOperation::Impl::AreRegionsIdentical() const {
|
|
2196
|
+
const S2ShapeIndex* a = op_->regions_[0];
|
|
2197
|
+
const S2ShapeIndex* b = op_->regions_[1];
|
|
2198
|
+
if (a == b) return true;
|
|
2199
|
+
int num_shape_ids = a->num_shape_ids();
|
|
2200
|
+
if (num_shape_ids != b->num_shape_ids()) return false;
|
|
2201
|
+
for (int s = 0; s < num_shape_ids; ++s) {
|
|
2202
|
+
const S2Shape* a_shape = a->shape(s);
|
|
2203
|
+
const S2Shape* b_shape = b->shape(s);
|
|
2204
|
+
if (a_shape->dimension() != b_shape->dimension()) return false;
|
|
2205
|
+
if (a_shape->dimension() == 2) {
|
|
2206
|
+
auto a_ref = a_shape->GetReferencePoint();
|
|
2207
|
+
auto b_ref = b_shape->GetReferencePoint();
|
|
2208
|
+
if (a_ref.point != b_ref.point) return false;
|
|
2209
|
+
if (a_ref.contained != b_ref.contained) return false;
|
|
2210
|
+
}
|
|
2211
|
+
int num_chains = a_shape->num_chains();
|
|
2212
|
+
if (num_chains != b_shape->num_chains()) return false;
|
|
2213
|
+
for (int c = 0; c < num_chains; ++c) {
|
|
2214
|
+
S2Shape::Chain a_chain = a_shape->chain(c);
|
|
2215
|
+
S2Shape::Chain b_chain = b_shape->chain(c);
|
|
2216
|
+
S2_DCHECK_EQ(a_chain.start, b_chain.start);
|
|
2217
|
+
if (a_chain.length != b_chain.length) return false;
|
|
2218
|
+
for (int i = 0; i < a_chain.length; ++i) {
|
|
2219
|
+
S2Shape::Edge a_edge = a_shape->chain_edge(c, i);
|
|
2220
|
+
S2Shape::Edge b_edge = b_shape->chain_edge(c, i);
|
|
2221
|
+
if (a_edge.v0 != b_edge.v0) return false;
|
|
2222
|
+
if (a_edge.v1 != b_edge.v1) return false;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
return true;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
bool S2BooleanOperation::Impl::Build(S2Error* error) {
|
|
2230
|
+
error->Clear();
|
|
2231
|
+
if (is_boolean_output()) {
|
|
2232
|
+
// BuildOpType() returns true if and only if the result has no edges.
|
|
2233
|
+
S2Builder::Graph g; // Unused by IsFullPolygonResult() implementation.
|
|
2234
|
+
*op_->result_empty_ =
|
|
2235
|
+
BuildOpType(op_->op_type()) && !IsFullPolygonResult(g, error);
|
|
2236
|
+
return true;
|
|
2237
|
+
}
|
|
2238
|
+
// TODO(ericv): Rather than having S2Builder split the edges, it would be
|
|
2239
|
+
// faster to call AddVertex() in this class and have a new S2Builder
|
|
2240
|
+
// option that increases the edge_snap_radius_ to account for errors in
|
|
2241
|
+
// the intersection point (the way that split_crossing_edges does).
|
|
2242
|
+
S2Builder::Options options(op_->options_.snap_function());
|
|
2243
|
+
options.set_split_crossing_edges(true);
|
|
2244
|
+
|
|
2245
|
+
// TODO(ericv): Ideally idempotent() should be true, but existing clients
|
|
2246
|
+
// expect vertices closer than the full "snap_radius" to be snapped.
|
|
2247
|
+
options.set_idempotent(false);
|
|
2248
|
+
builder_ = make_unique<S2Builder>(options);
|
|
2249
|
+
builder_->StartLayer(make_unique<EdgeClippingLayer>(
|
|
2250
|
+
&op_->layers_, &input_dimensions_, &input_crossings_));
|
|
2251
|
+
|
|
2252
|
+
// Add a predicate that decides whether a result with no polygon edges should
|
|
2253
|
+
// be interpreted as the empty polygon or the full polygon.
|
|
2254
|
+
builder_->AddIsFullPolygonPredicate(
|
|
2255
|
+
[this](const S2Builder::Graph& g, S2Error* error) {
|
|
2256
|
+
return IsFullPolygonResult(g, error);
|
|
2257
|
+
});
|
|
2258
|
+
(void) BuildOpType(op_->op_type());
|
|
2259
|
+
return builder_->Build(error);
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
S2BooleanOperation::Options::Options()
|
|
2263
|
+
: snap_function_(make_unique<s2builderutil::IdentitySnapFunction>(
|
|
2264
|
+
S1Angle::Zero())) {
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
S2BooleanOperation::Options::Options(const SnapFunction& snap_function)
|
|
2268
|
+
: snap_function_(snap_function.Clone()) {
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
S2BooleanOperation::Options::Options(const Options& options)
|
|
2272
|
+
: snap_function_(options.snap_function_->Clone()),
|
|
2273
|
+
polygon_model_(options.polygon_model_),
|
|
2274
|
+
polyline_model_(options.polyline_model_),
|
|
2275
|
+
polyline_loops_have_boundaries_(options.polyline_loops_have_boundaries_),
|
|
2276
|
+
precision_(options.precision_),
|
|
2277
|
+
conservative_output_(options.conservative_output_),
|
|
2278
|
+
source_id_lexicon_(options.source_id_lexicon_) {
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
S2BooleanOperation::Options& S2BooleanOperation::Options::operator=(
|
|
2282
|
+
const Options& options) {
|
|
2283
|
+
snap_function_ = options.snap_function_->Clone();
|
|
2284
|
+
polygon_model_ = options.polygon_model_;
|
|
2285
|
+
polyline_model_ = options.polyline_model_;
|
|
2286
|
+
polyline_loops_have_boundaries_ = options.polyline_loops_have_boundaries_;
|
|
2287
|
+
precision_ = options.precision_;
|
|
2288
|
+
conservative_output_ = options.conservative_output_;
|
|
2289
|
+
source_id_lexicon_ = options.source_id_lexicon_;
|
|
2290
|
+
return *this;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
const SnapFunction& S2BooleanOperation::Options::snap_function() const {
|
|
2294
|
+
return *snap_function_;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
void S2BooleanOperation::Options::set_snap_function(
|
|
2298
|
+
const SnapFunction& snap_function) {
|
|
2299
|
+
snap_function_ = snap_function.Clone();
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
PolygonModel S2BooleanOperation::Options::polygon_model() const {
|
|
2303
|
+
return polygon_model_;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
void S2BooleanOperation::Options::set_polygon_model(PolygonModel model) {
|
|
2307
|
+
polygon_model_ = model;
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
PolylineModel S2BooleanOperation::Options::polyline_model() const {
|
|
2311
|
+
return polyline_model_;
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
void S2BooleanOperation::Options::set_polyline_model(PolylineModel model) {
|
|
2315
|
+
polyline_model_ = model;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
bool S2BooleanOperation::Options::polyline_loops_have_boundaries() const {
|
|
2319
|
+
return polyline_loops_have_boundaries_;
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
void S2BooleanOperation::Options::set_polyline_loops_have_boundaries(
|
|
2323
|
+
bool value) {
|
|
2324
|
+
polyline_loops_have_boundaries_ = value;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
Precision S2BooleanOperation::Options::precision() const {
|
|
2328
|
+
return precision_;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
bool S2BooleanOperation::Options::conservative_output() const {
|
|
2332
|
+
return conservative_output_;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
ValueLexicon<S2BooleanOperation::SourceId>*
|
|
2336
|
+
S2BooleanOperation::Options::source_id_lexicon() const {
|
|
2337
|
+
return source_id_lexicon_;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
const char* S2BooleanOperation::OpTypeToString(OpType op_type) {
|
|
2341
|
+
switch (op_type) {
|
|
2342
|
+
case OpType::UNION: return "UNION";
|
|
2343
|
+
case OpType::INTERSECTION: return "INTERSECTION";
|
|
2344
|
+
case OpType::DIFFERENCE: return "DIFFERENCE";
|
|
2345
|
+
case OpType::SYMMETRIC_DIFFERENCE: return "SYMMETRIC DIFFERENCE";
|
|
2346
|
+
default: return "Unknown OpType";
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
S2BooleanOperation::S2BooleanOperation(OpType op_type,
|
|
2351
|
+
const Options& options)
|
|
2352
|
+
: op_type_(op_type), options_(options), result_empty_(nullptr) {
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
S2BooleanOperation::S2BooleanOperation(OpType op_type, bool* result_empty,
|
|
2356
|
+
const Options& options)
|
|
2357
|
+
: op_type_(op_type), options_(options),
|
|
2358
|
+
result_empty_(result_empty) {
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
S2BooleanOperation::S2BooleanOperation(
|
|
2362
|
+
OpType op_type, unique_ptr<S2Builder::Layer> layer, const Options& options)
|
|
2363
|
+
: op_type_(op_type), options_(options), result_empty_(nullptr) {
|
|
2364
|
+
layers_.push_back(std::move(layer));
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
S2BooleanOperation::S2BooleanOperation(
|
|
2368
|
+
OpType op_type, vector<unique_ptr<S2Builder::Layer>> layers,
|
|
2369
|
+
const Options& options)
|
|
2370
|
+
: op_type_(op_type), options_(options), layers_(std::move(layers)),
|
|
2371
|
+
result_empty_(nullptr) {
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
bool S2BooleanOperation::Build(const S2ShapeIndex& a,
|
|
2375
|
+
const S2ShapeIndex& b,
|
|
2376
|
+
S2Error* error) {
|
|
2377
|
+
regions_[0] = &a;
|
|
2378
|
+
regions_[1] = &b;
|
|
2379
|
+
return Impl(this).Build(error);
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
bool S2BooleanOperation::IsEmpty(
|
|
2383
|
+
OpType op_type, const S2ShapeIndex& a, const S2ShapeIndex& b,
|
|
2384
|
+
const Options& options) {
|
|
2385
|
+
bool result_empty;
|
|
2386
|
+
S2BooleanOperation op(op_type, &result_empty, options);
|
|
2387
|
+
S2Error error;
|
|
2388
|
+
op.Build(a, b, &error);
|
|
2389
|
+
S2_DCHECK(error.ok());
|
|
2390
|
+
return result_empty;
|
|
2391
|
+
}
|