Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-17 09:07:47

0001 //------------------------------- -*- C++ -*- -------------------------------//
0002 // Copyright Celeritas contributors: see top-level COPYRIGHT file for details
0003 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
0004 //---------------------------------------------------------------------------//
0005 //! \file orange/orangeinp/detail/ConvexHullFinder.hh
0006 //---------------------------------------------------------------------------//
0007 #pragma once
0008 
0009 #include <algorithm>
0010 
0011 #include "corecel/Assert.hh"
0012 #include "corecel/Types.hh"
0013 #include "corecel/cont/Array.hh"
0014 #include "corecel/cont/Range.hh"
0015 #include "orange/OrangeTypes.hh"
0016 #include "orange/univ/detail/Utils.hh"
0017 
0018 #include "PolygonUtils.hh"
0019 
0020 namespace celeritas
0021 {
0022 namespace orangeinp
0023 {
0024 namespace detail
0025 {
0026 //---------------------------------------------------------------------------//
0027 /*!
0028  * Find the convex hull of a sequence of 2D points.
0029  *
0030  * This helper class does not take ownership of the supplied points and
0031  * tolerance, so the lifetime of objects of this class should be shorter than
0032  * the lifetime of these arguments. Points must be supplied in clockwise-order
0033  * such that segments between adjacent points, including the last and first
0034  * points, comprise a non-self-intersecting polygon. Exploiting this ordering,
0035  * the Graham Scan algorithm \citet{graham-convexhull-1972,
0036  * https://doi.org/10.1016/0020-0190(72)90045-2} finds the convex hull with
0037  * O(N) time complexity.
0038  */
0039 template<class T>
0040 class ConvexHullFinder
0041 {
0042   public:
0043     //!@{
0044     //! \name Type aliases
0045     using real_type = T;
0046     using Real2 = celeritas::Array<real_type, 2>;
0047     using VecReal2 = std::vector<Real2>;
0048     using VecVecReal2 = std::vector<VecReal2>;
0049     //!@}
0050 
0051   public:
0052     // Construct with vector of ordered points and a tolerance
0053     explicit ConvexHullFinder(VecReal2 const& points, Tolerance<> const& tol);
0054 
0055     // Make the convex hull
0056     VecReal2 make_convex_hull() const;
0057 
0058     // Calculate the concave regions, each supplied in clockwise order
0059     VecVecReal2 calc_concave_regions() const;
0060 
0061   private:
0062     /// TYPES ///
0063     using ConvexMask = std::vector<bool>;
0064 
0065     /// DATA ///
0066     VecReal2 const& points_;
0067     Tolerance<> const& tol_;
0068     SoftOrientation<real_type> soft_ori_;
0069     ConvexMask convex_mask_;
0070     size_type start_index_;
0071 
0072     /// HELPER FUNCTIONS ///
0073 
0074     // Make a SoftOrientation object based on the tolerance and polygon extents
0075     SoftOrientation<real_type> make_soft_ori() const;
0076 
0077     // Calculate a mask that indicates which points are on the convex hull
0078     ConvexMask calc_convex_mask() const;
0079 
0080     // Find the index of the element with the minimum y value
0081     size_type min_element_idx() const;
0082 
0083     // Determine if three points, traversed in order, form a clockwise turn
0084     bool is_clockwise(size_type i_prev, size_type i, size_type i_next) const;
0085 
0086     // Determine the next index, with modular indexing
0087     size_type calc_next(size_type i) const;
0088 
0089     // Determine the previous index, with modular indexing
0090     size_type calc_previous(size_type i) const;
0091 };
0092 
0093 //---------------------------------------------------------------------------//
0094 /*!
0095  * Construct with vector of ordered points.
0096  *
0097  * This function generates a mask that is used to calculate the convex hull
0098  * and associated concave regions. Note that this function does not enforce
0099  * ordering.
0100  *
0101  * \todo Check that points form a non-self-intersecting polygon with clockwise
0102  * ordering.
0103  */
0104 template<class T>
0105 ConvexHullFinder<T>::ConvexHullFinder(ConvexHullFinder::VecReal2 const& points,
0106                                       Tolerance<> const& tol)
0107     : points_{points}, tol_{tol}
0108 {
0109     CELER_EXPECT(points_.size() > 2);
0110 
0111     soft_ori_ = this->make_soft_ori();
0112     start_index_ = this->min_element_idx();
0113     convex_mask_ = this->calc_convex_mask();
0114 }
0115 
0116 //---------------------------------------------------------------------------//
0117 /*!
0118  * Make a SoftOrientation object based on the tolerance and polygon extents.
0119  */
0120 template<class T>
0121 auto ConvexHullFinder<T>::make_soft_ori() const -> SoftOrientation<real_type>
0122 {
0123     auto const x_cmp
0124         = [](Real2 const& a, Real2 const& b) { return a[0] < b[0]; };
0125     auto const y_cmp
0126         = [](Real2 const& a, Real2 const& b) { return a[1] < b[1]; };
0127 
0128     auto const [x_min, x_max]
0129         = std::minmax_element(points_.begin(), points_.end(), x_cmp);
0130     auto const [y_min, y_max]
0131         = std::minmax_element(points_.begin(), points_.end(), y_cmp);
0132 
0133     // Convert min/max x and y values to extents
0134     using extent_type = Real3::value_type;
0135     Real3 const extents{static_cast<extent_type>((*x_max)[0] - (*x_min)[0]),
0136                         static_cast<extent_type>((*y_max)[1] - (*y_min)[1]),
0137                         0};
0138 
0139     return SoftOrientation<real_type>(
0140         ::celeritas::detail::BumpCalculator(tol_)(extents));
0141 }
0142 
0143 //---------------------------------------------------------------------------//
0144 /*!
0145  * Make the convex hull.
0146  */
0147 template<class T>
0148 auto ConvexHullFinder<T>::make_convex_hull() const -> VecReal2
0149 {
0150     CELER_EXPECT(convex_mask_.size() > 2);
0151 
0152     VecReal2 convex_hull;
0153     for (auto i : range(convex_mask_.size()))
0154     {
0155         if (convex_mask_[i])
0156         {
0157             convex_hull.push_back(points_[i]);
0158         }
0159     }
0160     return convex_hull;
0161 }
0162 
0163 //---------------------------------------------------------------------------//
0164 /*!
0165  * Calculate the concave regions, each supplied in clockwise order.
0166  *
0167  * Here, a "concave region" is a region that lies entirely within the convex
0168  * hull, that is concavity within the *original* shape. Note that a concave
0169  * region itself may be convex or concave. For example, consider the shape:
0170  *
0171  *   0 _______ 1
0172  *    |       |
0173  *    |     2 |____ 3
0174  *    |            |
0175  *  5 |____________| 4
0176  *
0177  * The convex hull is (0, 1, 3, 4, 5). There is one concave region: the
0178  * triangle formed by (1, 2, 3).
0179  */
0180 template<class T>
0181 auto ConvexHullFinder<T>::calc_concave_regions() const -> VecVecReal2
0182 {
0183     CELER_EXPECT(convex_mask_.size() > 2);
0184     VecVecReal2 concave_regions;
0185 
0186     // Since the original shape was supplied in clockwise order, we must
0187     // traverse the points backwards in order to obtain the concave regions in
0188     // clockwise order.
0189     size_type i = this->calc_previous(start_index_);
0190     while (i != start_index_)
0191     {
0192         if (convex_mask_[i])
0193         {
0194             i = this->calc_previous(i);
0195         }
0196         else
0197         {
0198             VecReal2 concave_region;
0199             concave_region.push_back(points_[calc_next(i)]);
0200             do
0201             {
0202                 concave_region.push_back(points_[i]);
0203                 i = this->calc_previous(i);
0204             } while (!convex_mask_[i]);
0205 
0206             concave_region.push_back(points_[i]);
0207             concave_regions.push_back(std::move(concave_region));
0208         }
0209     }
0210     return concave_regions;
0211 }
0212 
0213 //---------------------------------------------------------------------------//
0214 // HELPER FUNCTIONS
0215 //---------------------------------------------------------------------------//
0216 /*!
0217  * Calculate a mask that indicates which points are on the convex hull.
0218  *
0219  * This method uses the Graham Scan algorithm.
0220  */
0221 template<class T>
0222 auto ConvexHullFinder<T>::calc_convex_mask() const -> ConvexMask
0223 {
0224     // Find the indices of the points on the convex hull. Start from the point
0225     // with the lowest y value, which is guaranteed to be on the hull.
0226     std::vector<size_type> hull;
0227     auto i = start_index_;
0228     hull.push_back(i);
0229     i = this->calc_next(i);
0230 
0231     for (size_type j = 0, n = points_.size(); j != n; ++j)
0232     {
0233         size_type i_next = this->calc_next(i);
0234 
0235         if (this->is_clockwise(hull.back(), i, i_next))
0236         {
0237             // Clockwise point is part of the hull
0238             hull.push_back(i);
0239         }
0240         else
0241         {
0242             // Pop points off the hull until we can reach the next point by
0243             // turning clockwise.
0244             while (hull.size() >= 2
0245                    && !this->is_clockwise(
0246                        hull[hull.size() - 2], hull.back(), i_next))
0247             {
0248                 hull.pop_back();
0249             }
0250         }
0251 
0252         i = i_next;
0253     }
0254 
0255     // Convert convex hull indices to a mask
0256     ConvexMask convex_mask(points_.size(), false);
0257     for (auto h : hull)
0258     {
0259         convex_mask[h] = true;
0260     }
0261 
0262     return convex_mask;
0263 }
0264 
0265 //---------------------------------------------------------------------------//
0266 /*!
0267  * Find the index of the element with the lowest y value.
0268  */
0269 template<class T>
0270 size_type ConvexHullFinder<T>::min_element_idx() const
0271 {
0272     auto starting_it = std::min_element(
0273         points_.begin(), points_.end(), [](Real2 const& a, Real2 const& b) {
0274             return a[1] < b[1];
0275         });
0276     return std::distance(points_.begin(), starting_it);
0277 };
0278 
0279 //---------------------------------------------------------------------------//
0280 /*!
0281  * Determine if three elements form a clockwise turn using the cross product.
0282  *
0283  * Here, colinear points are considered clockwise.
0284  */
0285 template<class T>
0286 auto ConvexHullFinder<T>::is_clockwise(size_type i_prev,
0287                                        size_type i,
0288                                        size_type i_next) const -> bool
0289 {
0290     auto const& a = points_[i_prev];
0291     auto const& b = points_[i];
0292     auto const& c = points_[i_next];
0293     return soft_ori_(a, b, c) != detail::Orientation::counterclockwise;
0294 }
0295 
0296 //---------------------------------------------------------------------------//
0297 /*!
0298  * Determine the next index using modular indexing.
0299  */
0300 template<class T>
0301 size_type ConvexHullFinder<T>::calc_next(size_type i) const
0302 {
0303     return (i + 1 < points_.size() ? i + 1 : 0);
0304 }
0305 
0306 //---------------------------------------------------------------------------//
0307 /*!
0308  * Determine the previous index using modular indexing.
0309  */
0310 template<class T>
0311 size_type ConvexHullFinder<T>::calc_previous(size_type i) const
0312 {
0313     return (i > 0 ? i : points_.size()) - 1;
0314 }
0315 
0316 //---------------------------------------------------------------------------//
0317 }  // namespace detail
0318 }  // namespace orangeinp
0319 }  // namespace celeritas