/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2015-2018 Danny Robson */ #pragma once #include "volume.hpp" #include "../ops.hpp" #include "../../coord/fwd.hpp" #include "../../extent.hpp" #include "../../region.hpp" #include "../../random.hpp" #include #include /////////////////////////////////////////////////////////////////////////////// namespace cruft::geom::sample { /// Approximate a poisson disc sampling through the "Mitchell's Best /// Candidate" algorithm. /// /// We try to keep adding a new point to an output set. /// * Generate `candidates_count` new points /// * Remove any point that fails AcceptT /// * Order the points by minimum distance to the output set /// * Remove any point with a distance less than DistanceT /// * Select the point with the highest distance /// /// Keep iterating until no point is added to the output set. /// /// \tparam SamplerT A surface sampler /// \tparam DistanceT The type of point-to-point distances /// \tparam GeneratorT The random generator passed to the sampler /// \tparam AcceptT A unary bool function that returns true if a sampled /// point is permissible. The point is counted in the candidate set /// regardless. /// /// \return A vector of the computed points template < typename SamplerT, typename DistanceT, typename GeneratorT, typename AcceptT > auto poisson ( SamplerT const &sampler, GeneratorT &&gen, AcceptT &&accept, DistanceT &&minimum_distance, std::size_t const candidates_count ) { using point_type = decltype (sampler.eval (gen)); using value_type = typename point_type::value_type; std::vector selected; std::vector candidates; // Prime the found elements list with an initial point we can // perform distance calculations on. for (std::size_t i = 0; i < candidates_count; ++i) { auto const p = sampler.eval (gen); if (!accept (p)) continue; selected.push_back (p); break; } // We couldn't find an initial candidate so abort. if (selected.empty ()) return selected; // keep trying to add one more new point while (1) { // generate a group of candidate points candidates.clear (); std::generate_n ( std::back_inserter (candidates), candidates_count, [&] (void) { return sampler.eval (gen); } ); // Remove points that aren't acceptable std::erase_if ( candidates, [&] (auto const &p) { return !accept (p); } ); // find the point whose minimum distance to the existing // points is the greatest (while also being greater than the // required minimum distance); auto best_distance2 = std::numeric_limits::lowest (); size_t best_index = 0; for (size_t i = 0; i < candidates.size (); ++i) { auto const p = candidates[i]; auto d2 = std::numeric_limits::max (); // find the minimum distance from this candidate to the // selected points for (auto q: selected) d2 = cruft::min (d2, cruft::distance2 (p, q)); // record if it's the furthest away if (d2 > best_distance2 && d2 > cruft::pow (minimum_distance (p), 2)) { best_distance2 = d2; best_index = i; } } // if we didn't find a suitable point then we give up and // return the points we found, otherwise record the best point if (best_distance2 <= 0) break; selected.push_back (candidates[best_index]); } return selected; } /// A convenience function that forwards to poisson with unconditional /// point acceptance. template < typename SamplerT, typename DistanceT, typename GeneratorT > auto poisson ( SamplerT &&sampler, GeneratorT &&gen, DistanceT &&minimum_distance, std::size_t candidates_count ) { return poisson ( std::forward (sampler), std::forward (gen), [] (auto&&...) { return true; }, std::forward (minimum_distance), candidates_count ); } /// A surface sampler specialisation for 2d extents. /// /// The actual work is handed off to the volume sampler, as it's /// equivalent in 2 dimensions. template class surface> : public volume> { using volume>::volume; }; /// A surface sampler specialisation for 2d regions /// /// We encapsulate an extent sampler and an offset, then mainly pass off /// the work to the extent sampler. template class surface> { public: using shape_type = region<2,T>; surface (shape_type const &_shape) : m_extent (_shape.e) , m_offset (_shape.p.template as ()) { ; } template decltype(auto) eval (GeneratorT &&gen) { return m_extent.eval ( std::forward (gen) ) + m_offset; } private: surface> m_extent; cruft::vector<2,T> m_offset; }; }