File indexing completed on 2026-08-20 08:44:11
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010 #ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_IS_VALID_POLYHEDRAL_SURFACE_HPP
0011 #define BOOST_GEOMETRY_ALGORITHMS_DETAIL_IS_VALID_POLYHEDRAL_SURFACE_HPP
0012
0013 #include <boost/geometry/algorithms/buffer.hpp>
0014 #include <boost/geometry/algorithms/correct.hpp>
0015 #include <boost/geometry/algorithms/detail/is_valid/interface.hpp>
0016 #include <boost/geometry/algorithms/detail/is_valid/polygon.hpp>
0017 #include <boost/geometry/algorithms/detail/calculate_point_order.hpp>
0018 #include <boost/geometry/algorithms/distance.hpp> //needed for rtree
0019 #include <boost/geometry/index/predicates.hpp>
0020 #include <boost/geometry/index/rtree.hpp>
0021 #include <boost/geometry/io/wkt/wkt.hpp>
0022 #include <boost/geometry/iterators/point_reverse_iterator.hpp>
0023
0024 #include <vector>
0025 #include <stack>
0026
0027 namespace boost { namespace geometry
0028 {
0029
0030 #ifndef DOXYGEN_NO_DETAIL
0031 namespace detail { namespace is_valid
0032 {
0033
0034 struct polygon_frontier_type
0035 {
0036 polygon_frontier_type(std::size_t size)
0037 : m_stack(), m_visited(size, false)
0038 {}
0039
0040 void push(std::size_t index)
0041 {
0042 if (!m_visited[index])
0043 {
0044 m_stack.push(index);
0045 m_visited[index] = true;
0046 }
0047 }
0048 bool empty()
0049 {
0050 return m_stack.empty();
0051 }
0052 std::size_t top()
0053 {
0054 return m_stack.top();
0055 }
0056 void pop()
0057 {
0058 m_stack.pop();
0059 }
0060 private :
0061 std::stack<std::size_t> m_stack;
0062 std::vector<bool> m_visited;
0063 };
0064
0065
0066
0067
0068
0069
0070
0071
0072
0073
0074
0075
0076 template <typename Point, typename Polygon, typename Strategy>
0077 int polygon_segment_intersection(Point const& s1,
0078 Point const& s2,
0079 Polygon const& polygon,
0080 int proj1,
0081 int proj2,
0082 Strategy const& strategy)
0083 {
0084 BOOST_GEOMETRY_ASSERT(geometry::dimension<Point>::value == 3);
0085 BOOST_GEOMETRY_ASSERT(geometry::dimension<Polygon>::value == 3);
0086
0087 auto const pit_a = points_begin(polygon);
0088 auto const& p_a = *pit_a;
0089
0090 int cut = 0;
0091
0092 for (auto pit = boost::next(pit_a); pit != boost::prior(points_end(polygon)); ++pit)
0093 {
0094 auto const& p_b = *pit;
0095 auto const& p_c = *boost::next(pit);
0096
0097 int const sign = strategy.side().apply(p_a, p_b, p_c, proj1, proj2);
0098
0099 int const sign1 = strategy.side3().apply(s1, p_a, s2, p_b);
0100 int const sign2 = strategy.side3().apply(s1, p_c, p_b, s2);
0101 int const sign3 = strategy.side3().apply(s1, p_a, p_c, s2);
0102
0103
0104 if ((sign1 == 0 && sign2 == 0) || (sign1 == 0 && sign3 == 0) || (sign2 == 0 && sign3 == 0))
0105 {
0106 return 2;
0107 }
0108
0109
0110 if (sign1 == 0 && sign2 == sign3) cut += sign;
0111
0112
0113 if (sign2 == 0 && sign1 == sign3) return 3;
0114
0115
0116 if (sign3 == 0 && sign1 == sign2) cut += sign;
0117
0118
0119 if (sign1 == sign2 && sign2 == sign3) cut += 2 * sign;
0120 }
0121
0122 return (cut == 2) ? 1 : 0;
0123 }
0124
0125 template <typename PolyhedralSurface>
0126 struct valid_intersection
0127 {
0128 valid_intersection(std::size_t num_of_polygons)
0129 : m_polygon_projections(num_of_polygons)
0130 {}
0131
0132 using coordinate_type = typename coordinate_type<PolyhedralSurface>::type;
0133 using polygon_3d = typename boost::range_value<PolyhedralSurface>::type;
0134 using ring_3d = typename boost::geometry::ring_type<polygon_3d>::type;
0135 using point_3d = typename boost::geometry::point_type<ring_3d>::type;
0136 using segment_3d = typename boost::geometry::model::segment<point_3d>;
0137 using box_3d = typename boost::geometry::model::box<point_3d>;
0138
0139 using point_2d = boost::geometry::model::point
0140 <coordinate_type, 2, boost::geometry::cs::cartesian>;
0141 using linestring_2d = typename boost::geometry::model::linestring<point_2d>;
0142 using polygon_2d = typename std::conditional
0143 <
0144 boost::geometry::point_order<polygon_3d>::value == boost::geometry::counterclockwise,
0145 typename boost::geometry::model::polygon<point_2d, true>,
0146 typename boost::geometry::model::polygon<point_2d, false>
0147 >::type;
0148 using ring_2d = typename boost::geometry::ring_type<polygon_2d>::type;
0149
0150 private:
0151
0152 enum class intersection_type
0153 {
0154 none,
0155 valid,
0156 invalid
0157 };
0158
0159 struct polygon_projection
0160 {
0161 polygon_2d projected_polygon;
0162 int p1_idx;
0163 int p2_idx;
0164 int p3_idx;
0165 int proj1;
0166 int proj2;
0167 int dir;
0168 };
0169
0170 template <typename Strategy>
0171 static std::tuple<int, int, int> affinely_independent(point_3d const& p1,
0172 point_3d const& p2,
0173 point_3d const& p3,
0174 Strategy const& strategy)
0175 {
0176 int side = strategy.side().apply(p1, p2, p3, 0, 1);
0177 if (side != 0) return {0, 1, side};
0178
0179 side = strategy.side().apply(p1, p2, p3, 0, 2);
0180 if (side != 0) return {0, 2, side};
0181
0182 side = strategy.side().apply(p1, p2, p3, 1, 2);
0183 if (side != 0) return {1, 2, side};
0184
0185 return {-1, -1, 0};
0186 }
0187
0188 template <typename Strategy>
0189 static std::tuple<int, int, int> find_non_collinear_points(polygon_3d const& polygon,
0190 int& p1_index,
0191 int& p2_index,
0192 int& p3_index,
0193 Strategy const& strategy)
0194 {
0195 for (auto pit1 = points_begin(polygon); pit1 != points_end(polygon); ++pit1)
0196 {
0197 for (auto pit2 = boost::next(pit1); pit2 != points_end(polygon); ++pit2)
0198 {
0199 for (auto pit3 = boost::next(pit2); pit3 != points_end(polygon); ++pit3)
0200 {
0201
0202 auto res = affinely_independent(*pit1, *pit2, *pit3, strategy);
0203 if (std::get<0>(res) != -1)
0204 {
0205 p1_index = std::distance(points_begin(polygon), pit1);
0206 p2_index = std::distance(points_begin(polygon), pit2);
0207 p3_index = std::distance(points_begin(polygon), pit3);
0208 return res;
0209 }
0210 }
0211 }
0212 }
0213 return {-1, -1, 0};
0214 }
0215
0216 template <typename VisitPolicy>
0217 static bool has_correct_intersection_orientation(polygon_2d const& polygon,
0218 point_2d const& segment_point1,
0219 point_2d const& segment_point2,
0220 VisitPolicy& visitor)
0221 {
0222 if (boost::geometry::equals(segment_point1, segment_point2))
0223 {
0224 visitor.template apply<failure_duplicate_points>();
0225 return false;
0226 }
0227
0228 boost::geometry::closing_iterator<ring_2d> it(boost::geometry::exterior_ring(polygon));
0229 auto begin = it;
0230 boost::geometry::closing_iterator<ring_2d> end(boost::geometry::exterior_ring(polygon), true);
0231
0232 for (; it != end - 1; ++it)
0233 {
0234 if (boost::geometry::equals(*it, segment_point1))
0235 {
0236 auto prev = (it == begin) ? end - 1 : boost::prior(it);
0237
0238 if (boost::geometry::equals(segment_point2, *prev))
0239 {
0240
0241 return true;
0242 }
0243
0244 auto next = (it == end) ? begin : boost::next(it);
0245
0246 if (boost::geometry::equals(segment_point2, *next))
0247 {
0248
0249 visitor.template apply<failure_inconsistent_orientation>();
0250 return false;
0251 }
0252 }
0253 }
0254
0255 visitor.template apply<failure_invalid_intersection>();
0256
0257 return false;
0258 }
0259
0260 point_2d project_point_to_coordinate_plane(point_3d const& point, int proj1, int proj2)
0261 {
0262 if (proj1 == 1 && proj2 == 2) return point_2d{geometry::get<1>(point), geometry::get<2>(point)};
0263 if (proj1 == 0 && proj2 == 2) return point_2d{geometry::get<0>(point), geometry::get<2>(point)};
0264 if (proj1 == 0 && proj2 == 1) return point_2d{geometry::get<0>(point), geometry::get<1>(point)};
0265 return point_2d{};
0266 }
0267
0268
0269 template <typename VisitPolicy, typename Strategy>
0270 intersection_type check_polygon_segment_intersection(point_3d const& segment_point1,
0271 point_3d const& segment_point2,
0272 polygon_3d const& polygon,
0273 std::size_t const polygon_index,
0274 VisitPolicy& visitor,
0275 Strategy const& strategy)
0276 {
0277 auto const& projection = m_polygon_projections[polygon_index];
0278 auto const& projected_polygon = projection.projected_polygon;
0279
0280 auto const& p1 = polygon.outer()[projection.p1_idx];
0281 auto const& p2 = polygon.outer()[projection.p2_idx];
0282 auto const& p3 = polygon.outer()[projection.p3_idx];
0283
0284 int proj1 = projection.proj1;
0285 int proj2 = projection.proj2;
0286 int dir = projection.dir;
0287
0288
0289 auto sign1 = strategy.side3().apply(p1, p2, p3, segment_point1);
0290 auto sign2 = strategy.side3().apply(p1, p2, p3, segment_point2);
0291
0292 if ((sign1 > 0 && sign2 > 0) || (sign1 < 0 && sign2 < 0))
0293 {
0294 return intersection_type::none;
0295 }
0296
0297
0298 if (sign1 == 0 && sign2 == 0)
0299 {
0300 auto const& projected_p1 = project_point_to_coordinate_plane(segment_point1, proj1, proj2);
0301 auto const& projected_p2 = project_point_to_coordinate_plane(segment_point2, proj1, proj2);
0302
0303 linestring_2d projected_segment = {projected_p1, projected_p2};
0304
0305
0306 std::tuple
0307 <
0308 boost::geometry::model::multi_point<point_2d>,
0309 boost::geometry::model::multi_linestring<linestring_2d>> intersections;
0310 boost::geometry::intersection(projected_segment, projected_polygon, intersections);
0311
0312 auto const& points = std::get<0>(intersections);
0313 auto const& linestrings = std::get<1>(intersections);
0314
0315 if (boost::geometry::is_empty(points) && boost::geometry::is_empty(linestrings))
0316 {
0317 return intersection_type::none;
0318 }
0319
0320 if (!points.empty())
0321 {
0322 if (points.size() == 1 &&
0323 (boost::geometry::equals(points.front(), projected_p1) ||
0324 boost::geometry::equals(points.front(), projected_p2)))
0325 {
0326 return intersection_type::none;
0327 }
0328 visitor.template apply<failure_invalid_intersection>();
0329 return intersection_type::invalid;
0330 }
0331
0332 if (linestrings.size() == 1)
0333 {
0334 if (boost::geometry::equals(linestrings.front(), projected_segment))
0335 {
0336 auto const& pp1 = dir < 0 ? projected_p2 : projected_p1;
0337 auto const& pp2 = dir < 0 ? projected_p1 : projected_p2;
0338 if (!has_correct_intersection_orientation(projected_polygon, pp1, pp2, visitor))
0339 {
0340 return intersection_type::invalid;
0341 }
0342 return intersection_type::valid;
0343 }
0344 }
0345 visitor.template apply<failure_invalid_intersection>();
0346 return intersection_type::invalid;
0347 }
0348
0349 int inter = polygon_segment_intersection(segment_point1, segment_point2, polygon, proj1,
0350 proj2, strategy);
0351
0352 if ((inter == 1 || inter == 3) || (inter == 2 && sign1 !=0 && sign2 != 0))
0353 {
0354 visitor.template apply<failure_invalid_intersection>();
0355 return intersection_type::invalid;
0356 }
0357
0358 return intersection_type::none;
0359 }
0360
0361
0362 template <typename VisitPolicy, typename Strategy>
0363 intersection_type check_polygon_polygon_intersection(polygon_3d const& polygon1,
0364 polygon_3d const& polygon2,
0365 std::size_t const& index2,
0366 VisitPolicy& visitor,
0367 Strategy const& strategy)
0368 {
0369 bool valid_intersection = false;
0370 auto const& ring1 = boost::geometry::exterior_ring(polygon1);
0371
0372 for (auto it = boost::begin(ring1); it + 1 != boost::end(ring1); ++it)
0373 {
0374 auto result = check_polygon_segment_intersection(*it, *(it + 1), polygon2, index2,
0375 visitor, strategy);
0376
0377 if (result == intersection_type::invalid)
0378 {
0379 return intersection_type::invalid;
0380 }
0381
0382 if (result == intersection_type::valid)
0383 {
0384 valid_intersection = true;
0385 }
0386 }
0387
0388 return valid_intersection ? intersection_type::valid : intersection_type::none;
0389 }
0390
0391 public:
0392
0393 template <typename VisitPolicy, typename Strategy>
0394 intersection_type apply(polygon_3d const& polygon1,
0395 polygon_3d const& polygon2,
0396 std::size_t const& index1,
0397 std::size_t const& index2,
0398 VisitPolicy& visitor,
0399 Strategy const& strategy)
0400 {
0401 auto intersection1 = check_polygon_polygon_intersection(polygon1, polygon2, index2,
0402 visitor, strategy);
0403
0404 if (intersection1 == intersection_type::invalid)
0405 {
0406 return intersection_type::invalid;
0407 }
0408
0409 auto intersection2 = check_polygon_polygon_intersection(polygon2, polygon1, index1,
0410 visitor, strategy);
0411
0412 if (intersection2 == intersection_type::invalid)
0413 {
0414 return intersection_type::invalid;
0415 }
0416
0417 if (intersection1 == intersection_type::none && intersection2 == intersection_type::none)
0418 {
0419 return intersection_type::none;
0420 }
0421
0422 return intersection_type::valid;
0423 }
0424
0425 template <typename VisitPolicy, typename Strategy>
0426 intersection_type initialize(polygon_3d const& polygon,
0427 std::size_t const polygon_index,
0428 VisitPolicy& visitor,
0429 Strategy const& strategy)
0430 {
0431
0432 ring_3d const& ring = boost::geometry::exterior_ring(polygon);
0433
0434 if (boost::size(ring) < detail::minimum_ring_size<ring_3d>::value)
0435 {
0436 visitor.template apply<failure_few_points_on_face>();
0437 return intersection_type::invalid;
0438 }
0439
0440
0441 int p1_idx = 0;
0442 int p2_idx = 0;
0443 int p3_idx = 0;
0444
0445 auto res = find_non_collinear_points(polygon, p1_idx, p2_idx, p3_idx, strategy);
0446
0447 if (std::get<0>(res) == -1)
0448 {
0449 visitor.template apply<failure_collinear_points_on_face>();
0450 return intersection_type::invalid;
0451 }
0452
0453 int proj1 = std::get<0>(res);
0454 int proj2 = std::get<1>(res);
0455 int dir = std::get<2>(res);
0456
0457 auto const& p1 = polygon.outer()[p1_idx];
0458 auto const& p2 = polygon.outer()[p2_idx];
0459 auto const& p3 = polygon.outer()[p3_idx];
0460
0461
0462 for (auto pit = points_begin(polygon); pit != points_end(polygon); ++pit)
0463 {
0464 if (strategy.side3().apply(p1, p2, p3, *pit) != 0)
0465 {
0466 visitor.template apply<failure_non_coplanar_points_on_face>();
0467 return intersection_type::invalid;
0468 }
0469 }
0470
0471
0472 polygon_2d projected_polygon;
0473
0474 auto add_projected_points = [&](auto begin_it, auto end_it)
0475 {
0476 for (auto it = begin_it; it != end_it; ++it)
0477 {
0478 projected_polygon.outer().emplace_back(
0479 project_point_to_coordinate_plane(*it, proj1, proj2));
0480 }
0481 };
0482
0483 if (dir > 0) {
0484 add_projected_points(points_begin(ring), points_end(ring));
0485 } else {
0486 add_projected_points(points_rbegin(ring), points_rend(ring));
0487 }
0488
0489
0490 if (!resolve_dynamic::is_valid<polygon_2d>::apply(projected_polygon, visitor, strategy))
0491 {
0492 return intersection_type::invalid;
0493 }
0494
0495
0496 m_polygon_projections[polygon_index] = polygon_projection{std::move(projected_polygon),
0497 p1_idx, p2_idx, p3_idx, proj1, proj2, dir};
0498
0499 return intersection_type::valid;
0500 }
0501
0502 private:
0503 std::vector<polygon_projection> m_polygon_projections;
0504 };
0505
0506 template <typename PolyhedralSurface>
0507 class is_valid_polyhedral_surface
0508 {
0509 public:
0510 template <typename VisitPolicy, typename Strategy>
0511 static inline bool apply(PolyhedralSurface const& surface,
0512 VisitPolicy& visitor,
0513 Strategy const& strategy)
0514 {
0515 boost::ignore_unused(strategy);
0516
0517 valid_intersection<PolyhedralSurface> valid_intersection(surface.size());
0518
0519
0520 for (std::size_t i = 0; i < surface.size(); ++i)
0521 {
0522 auto intersection = valid_intersection.initialize(surface[i], i, visitor, strategy);
0523
0524 using intersection_type = decltype(intersection);
0525
0526 if (intersection == intersection_type::invalid)
0527 {
0528 return false;
0529 }
0530 }
0531
0532
0533 using box_3d = typename boost::geometry::detail::is_valid::valid_intersection
0534 <PolyhedralSurface>::box_3d;
0535 using value = std::pair<box_3d, std::size_t>;
0536
0537 boost::geometry::index::rtree<value, boost::geometry::index::rstar<4>> rtree;
0538 std::vector<std::unique_ptr<box_3d>> boxes(surface.size());
0539
0540 for (std::size_t i = 0; i < surface.size(); ++i)
0541 {
0542 auto b = boost::geometry::return_envelope<box_3d>(surface[i], strategy);
0543 boxes[i] = std::make_unique<box_3d>(b);
0544 rtree.insert(std::make_pair(b, i));
0545 }
0546
0547
0548
0549
0550 auto it1 = boost::begin(surface);
0551
0552 polygon_frontier_type polygon_frontier(surface.size());
0553 polygon_frontier.push(0);
0554
0555 std::vector<std::size_t> remaining_polygons(surface.size());
0556 std::vector<std::size_t> positions(surface.size());
0557
0558 for (std::size_t i = 0; i < surface.size(); ++i)
0559 {
0560 remaining_polygons[i] = i;
0561 positions[i] = i;
0562 }
0563
0564 int remaining_count = surface.size();
0565
0566 while (!polygon_frontier.empty())
0567 {
0568 auto index1 = polygon_frontier.top();
0569 polygon_frontier.pop();
0570
0571 it1 = boost::begin(surface) + index1;
0572
0573
0574 auto moving_index = remaining_polygons[remaining_count - 1];
0575 remaining_polygons[positions[index1]] = moving_index;
0576 positions[moving_index] = positions[index1];
0577
0578
0579
0580 std::vector<value> intersecting_boxes;
0581 auto const& b_query = *boxes[index1];
0582 auto const& b_query_buf = boost::geometry::return_buffer<box_3d>(b_query, 0.0001);
0583
0584 rtree.query(boost::geometry::index::intersects(b_query_buf),
0585 std::back_inserter(intersecting_boxes));
0586
0587 for (int i = 0; i < remaining_count - 1; ++i)
0588 {
0589 auto index2 = remaining_polygons[i];
0590
0591
0592 if (std::find_if(intersecting_boxes.begin(), intersecting_boxes.end(),
0593 [index2](const value& v) { return v.second == index2; }
0594 ) == intersecting_boxes.end())
0595 {
0596 continue;
0597 }
0598
0599 auto intersection = valid_intersection.apply(*it1, surface[index2], index1, index2,
0600 visitor, strategy);
0601
0602 using intersection_type = decltype(intersection);
0603
0604 if (intersection == intersection_type::invalid)
0605 {
0606 return false;
0607 }
0608 else if (intersection == intersection_type::valid)
0609 {
0610 polygon_frontier.push(index2);
0611 }
0612 }
0613 remaining_count--;
0614 }
0615
0616 if (remaining_count > 0)
0617 {
0618 visitor.template apply<failure_disconnected_surface>();
0619 return false;
0620 }
0621
0622 return true;
0623 }
0624 };
0625
0626 }}
0627 #endif
0628
0629 #ifndef DOXYGEN_NO_DISPATCH
0630 namespace dispatch
0631 {
0632
0633
0634
0635
0636
0637
0638 template <typename PolyhedralSurface, bool AllowEmptyMultiGeometries>
0639 struct is_valid
0640 <
0641 PolyhedralSurface, polyhedral_surface_tag, AllowEmptyMultiGeometries
0642 > : detail::is_valid::is_valid_polyhedral_surface<PolyhedralSurface>
0643 {};
0644
0645
0646 }
0647 #endif
0648
0649 }}
0650
0651 #endif