diff --git a/CMakeLists.txt b/CMakeLists.txt index 87b495a0..a6600f9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -276,6 +276,7 @@ list ( geom/iostream.hpp geom/line.hpp geom/line.cpp + geom/ops.cpp geom/ops.hpp geom/plane.cpp geom/plane.hpp @@ -283,8 +284,9 @@ list ( geom/ray.hpp geom/rect.cpp geom/rect.hpp - geom/sample.cpp - geom/sample.hpp + geom/sample/fwd.hpp + geom/sample/surface.hpp + geom/sample/volume.hpp geom/segment.cpp geom/segment.hpp geom/sphere.cpp diff --git a/geom/aabb.hpp b/geom/aabb.hpp index b7a6c3ad..cbd7fa72 100644 --- a/geom/aabb.hpp +++ b/geom/aabb.hpp @@ -138,23 +138,35 @@ namespace cruft::geom { /////////////////////////////////////////////////////////////////////////////// -#include "./sample.hpp" +#include "sample/fwd.hpp" #include -namespace cruft::geom { +namespace cruft::geom::sample { template - struct sampler> { + class volume> { + public: + using shape_type = aabb; + + volume (shape_type const &target): + m_target (target) + { ; } + + template - static auto - eval (aabb shape, GeneratorT &g) + auto + eval (GeneratorT &&g) const noexcept { point res; for (size_t i = 0; i < S; ++i) - res[i] = cruft::random::uniform (shape.lo[i], shape.hi[i], g); + res[i] = cruft::random::uniform (m_target.lo[i], m_target.hi[i], g); return res; } + + + private: + shape_type const &m_target; }; } diff --git a/geom/ellipse.hpp b/geom/ellipse.hpp index c98e2164..3a3bae3e 100644 --- a/geom/ellipse.hpp +++ b/geom/ellipse.hpp @@ -138,20 +138,29 @@ namespace cruft::geom { /////////////////////////////////////////////////////////////////////////////// -#include "sample.hpp" +#include "sample/fwd.hpp" + +#include "../random.hpp" #include #include -namespace cruft::geom { +namespace cruft::geom::sample { /// Specialisation for uniform sampling of ellipses template - struct sampler> - { + class volume> { + public: + using shape_type = ellipse<2,T>; + + volume (shape_type const &target): + m_target (target) + { ; } + + /// Generate a random point within the ellipse. template - static auto - eval (ellipse<2,T> shape, GeneratorT &&g) + auto + eval (GeneratorT &&g) const noexcept { // We use a two step process: // * Generate a point within a unit sphere @@ -177,9 +186,13 @@ namespace cruft::geom { std::sin (phi) }; - auto const offset = circle_pos * rho * shape.radius; - return shape.origin + offset.template as (); + auto const offset = circle_pos * rho * m_target.radius; + return m_target.origin + offset.template as (); } + + + private: + shape_type const &m_target; }; diff --git a/geom/sample.cpp b/geom/ops.cpp similarity index 92% rename from geom/sample.cpp rename to geom/ops.cpp index 19e6ea7c..333daff9 100644 --- a/geom/sample.cpp +++ b/geom/ops.cpp @@ -6,4 +6,4 @@ * Copyright 2018 Danny Robson */ -#include "sample.hpp" +#include "ops.hpp" diff --git a/geom/ops.hpp b/geom/ops.hpp index d7dc2407..32b39293 100644 --- a/geom/ops.hpp +++ b/geom/ops.hpp @@ -6,8 +6,7 @@ * Copyright 2015-2018 Danny Robson */ -#ifndef __UTIL_GEOM_OPS_HPP -#define __UTIL_GEOM_OPS_HPP +#pragma once #include "./fwd.hpp" #include "../point.hpp" @@ -17,6 +16,9 @@ /////////////////////////////////////////////////////////////////////////////// namespace cruft::geom { + /// Tests whether any part of two shapes overlap. + /// + /// Returns true even if the the edges of the shapes are co-linear. template < size_t S, typename T, @@ -26,6 +28,8 @@ namespace cruft::geom { bool intersects (A, B); + + /// Returns a minimum squared distance between two shapes. template < size_t S, typename T, @@ -50,6 +54,8 @@ namespace cruft::geom { T distance (A, B); + + /// Returns an AABB for the supplied shape. template < size_t S, typename T, @@ -58,6 +64,8 @@ namespace cruft::geom { aabb bounds (K); + + /// Returns a maximum distance across a shape. template < size_t S, typename T, @@ -66,6 +74,8 @@ namespace cruft::geom { T diameter (K); + + /// Returns the closest point on a shape to the supplied point. template < size_t S, typename T, @@ -74,6 +84,7 @@ namespace cruft::geom { point closest (K, point); + template < size_t S, typename T, @@ -82,6 +93,7 @@ namespace cruft::geom { vector magnitude (K); + template < size_t S, typename T, @@ -90,5 +102,3 @@ namespace cruft::geom { K scale (K, T); } - -#endif diff --git a/geom/sample.hpp b/geom/sample.hpp deleted file mode 100644 index f73ba559..00000000 --- a/geom/sample.hpp +++ /dev/null @@ -1,175 +0,0 @@ -/* - * 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 "../coord/fwd.hpp" -#include "../extent.hpp" -#include "../random.hpp" - -#include "ops.hpp" - -#include -#include - - -/////////////////////////////////////////////////////////////////////////////// -namespace cruft::geom { - /// A function object that selects a uniformly random point inside a shape - /// using a provided random generator. The point will lie within the shape, - /// inclusive of boundaries. - /// - /// May be specialised for arbitrary shapes but uses rejection sampling - /// as a safe default. This implies that execution may not take a constant - /// time. - template - struct sampler { - /// Returns a point which lies within the supplied shape, inclusive - /// of borders. - template - static auto - eval (ShapeT const &shape, GeneratorT &&g) - { - auto b = bounds (shape); - - while (true) { - auto p = sample (b, g); - if (intersects (shape, p)) - return p; - } - } - }; - - - /////////////////////////////////////////////////////////////////////////// - /// A convenience function that calls sample::fn to select a random point - /// in a provided shape. - template < - typename ShapeT, - typename GeneratorT // random generator - > - auto - sample (ShapeT const &shape, GeneratorT &&gen) - { - return sampler::eval (shape, std::forward (gen)); - } - - - /////////////////////////////////////////////////////////////////////////// - std::vector - poisson_sample (cruft::extent2i, float distance, int samples); - - - /////////////////////////////////////////////////////////////////////////// - namespace surface { - /// A generator of samples that lie on the surface of a shape - template - class sampler; - - - template - sampler (ShapeT const&) -> sampler>; - - - //--------------------------------------------------------------------- - template - class sampler> { - public: - sampler (cruft::extent _target): - target (_target) - { ; } - - template - cruft::point - operator() (GeneratorT &&gen) const noexcept - { - cruft::point p; - - for (size_t i = 0; i < S; ++i) - p[i] = random::uniform (T{0}, target[i], gen); - - return p; - } - - private: - cruft::extent target; - }; - - - /// Approximate a poisson disc sampling through the "Mitchell's Best - /// Candidate" algorithm. - /// - /// Try to keep adding a new point to a set. Each new point is the - /// best of a set of candidates. The 'best' is the point that is - /// furthest from all selected points. - /// - /// \return A vector of the computed points - template - auto - poisson (SamplerT const &target, - GeneratorT &&gen, - DistanceT &&minimum_distance, - size_t candidates_count) - - { - using point_type = decltype (target (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 - selected.push_back (target (gen)); - - // 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 target (gen); - } - ); - - // 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; - } - } -} diff --git a/geom/sample/fwd.hpp b/geom/sample/fwd.hpp new file mode 100644 index 00000000..e5a05373 --- /dev/null +++ b/geom/sample/fwd.hpp @@ -0,0 +1,33 @@ +/* + * 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 + +namespace cruft::geom::sample { + /// Provides an interface to randomly sample points within the volume of + /// a supplied shape. + /// + /// This class _should_ be specialised for performance. Though a basic + /// rejection sampling implementation is defined as a fallback. + template + class volume; + + template + volume (ShapeT const&) -> volume>; + + + /// Provides an interface to randomly sample points across the surface of + /// a supplied shape. + /// + /// The class _must_ be specialised for each shape. + template + class surface; + + template + surface (ShapeT const&) -> surface>; +} diff --git a/geom/sample/surface.hpp b/geom/sample/surface.hpp new file mode 100644 index 00000000..3653ac0b --- /dev/null +++ b/geom/sample/surface.hpp @@ -0,0 +1,105 @@ +/* + * 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 "../../random.hpp" + +#include +#include + + +/////////////////////////////////////////////////////////////////////////////// +namespace cruft::geom::sample { + /// Approximate a poisson disc sampling through the "Mitchell's Best + /// Candidate" algorithm. + /// + /// Try to keep adding a new point to a set. Each new point is the + /// best of a set of candidates. The 'best' is the point that is + /// furthest from all selected points. + /// + /// \return A vector of the computed points + template + auto + poisson (SamplerT const &target, + GeneratorT &&gen, + DistanceT &&minimum_distance, + size_t candidates_count) + + { + using point_type = decltype (target.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 + selected.push_back (target.eval (gen)); + + // 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 target.eval (gen); + } + ); + + // 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 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; }; +} diff --git a/geom/sample/volume.hpp b/geom/sample/volume.hpp new file mode 100644 index 00000000..95dec1ec --- /dev/null +++ b/geom/sample/volume.hpp @@ -0,0 +1,105 @@ +/* + * 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 "../../coord/fwd.hpp" +#include "../../extent.hpp" +#include "../../random.hpp" + +#include "../ops.hpp" + +#include +#include + + +/////////////////////////////////////////////////////////////////////////////// +namespace cruft::geom::sample { + /////////////////////////////////////////////////////////////////////// + /// A convenience function that calls sample::fn to select a random + /// point in a provided shape. + /// + /// This function is useful because we can perform use partial + /// specialisation if we pass the functionality off to a well known + /// class. + template < + typename ShapeT, + typename GeneratorT // random generator + > + auto + eval (ShapeT const &shape, GeneratorT &&gen) + { + volume query (shape); + return query.eval (std::forward (gen)); + } + + + /// A function object that selects a uniformly random point inside a + /// shape using a provided random generator. The point will lie + /// within the shape, inclusive of boundaries. + /// + /// This should be specialised for anything complex, but will work + /// (with a varying performance hit) for anything that provides + /// bounds` and `intersects`. + template + class volume { + public: + explicit volume (ShapeT const &target): + m_target (target) + { ; } + + + /// Returns a point which lies within the supplied shape, inclusive + /// of borders. + /// + template + auto + eval (GeneratorT &&g) const + { + auto b = bounds (m_target); + + while (true) { + auto p = ::cruft::geom::sample::eval (b, g); + if (intersects (m_target, p)) + return p; + } + } + + private: + ShapeT const &m_target; + }; + + + /////////////////////////////////////////////////////////////////////// + /// A specialisation of the volume sampler for extents. + template + class volume> { + public: + using shape_type = extent; + + explicit volume (shape_type const &target): + m_target (target) + { ; } + + + template + auto + eval (GeneratorT &&gen) const noexcept + { + cruft::point p; + + for (size_t i = 0; i < S; ++i) + p[i] = random::uniform (T{0}, m_target[i], gen); + + return p; + } + + private: + shape_type const &m_target; + }; +} \ No newline at end of file diff --git a/geom/tri.hpp b/geom/tri.hpp index acd69f8b..19a1d9a7 100644 --- a/geom/tri.hpp +++ b/geom/tri.hpp @@ -10,7 +10,7 @@ #define CRUFT_GEOM_TRI_HPP #include "../point.hpp" -#include "sample.hpp" +#include "sample/fwd.hpp" #include #include @@ -33,11 +33,13 @@ namespace cruft::geom { using tri3f = tri<3,float>; - namespace surface { + namespace sample { template - class sampler> { + class surface> { public: - sampler (tri _target): + using shape_type = tri; + + explicit surface (tri _target): base (_target.a), v0 (_target.b - _target.a), v1 (_target.c - _target.a) @@ -53,7 +55,8 @@ namespace cruft::geom { private: cruft::point base; - cruft::vector v0, v1; + cruft::vector v0; + cruft::vector v1; }; }; diff --git a/tools/poisson.cpp b/tools/poisson.cpp index 07aa3539..fb363f50 100644 --- a/tools/poisson.cpp +++ b/tools/poisson.cpp @@ -1,7 +1,7 @@ #include "cmdopt.hpp" #include "functor.hpp" #include "geom/aabb.hpp" -#include "geom/sample.hpp" +#include "geom/sample/surface.hpp" #include #include @@ -25,10 +25,11 @@ main (int argc, char **argv) opts.scan (argc, argv); std::cout << ""; - for (auto p: cruft::geom::surface::poisson (cruft::geom::surface::sampler (area), - std::default_random_engine (), - cruft::functor::constant (distance), - samples)) + namespace sample = cruft::geom::sample; + for (auto const &p: sample::poisson (sample::surface {area}, + std::default_random_engine (), + cruft::functor::constant (distance), + samples)) { std::cout << ""; }