From 3829801c515eb43a7863859601d802254ebdb625 Mon Sep 17 00:00:00 2001 From: Bryn Lloyd Date: Fri, 16 Aug 2024 12:00:34 +0200 Subject: [PATCH 1/8] fix bug in PoissonDiskSampler, add test --- src/surface/poisson_disk_sampler.cpp | 2 +- test/CMakeLists.txt | 11 ++--- test/src/poisson_disk_sampler_test.cpp | 57 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 test/src/poisson_disk_sampler_test.cpp diff --git a/src/surface/poisson_disk_sampler.cpp b/src/surface/poisson_disk_sampler.cpp index 11ed5e71..36902887 100644 --- a/src/surface/poisson_disk_sampler.cpp +++ b/src/surface/poisson_disk_sampler.cpp @@ -90,7 +90,7 @@ SurfacePoint PoissonDiskSampler::generateCandidate(const SurfacePoint& xi) const TraceGeodesicResult trace; Vector2 dir = Vector2::fromAngle(randomReal(0., 2. * PI)); - double dist = std::sqrt(randomReal(rMinDist, 2. * rMinDist)); + double dist = randomReal(rMinDist, 2. * rMinDist); trace = traceGeodesic(geometry, xi, dist * dir); pathEndpoint = trace.endPoint; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 598d6667..3f13bf1f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -97,16 +97,17 @@ add_definitions(-DGC_TEST_ASSETS_ABS_PATH=\"${ABS_ASSETS_DIR}\") # Build the tests set(TEST_SRCS src/main_test.cpp - src/load_test_meshes.cpp src/eigen_interop_helpers_test.cpp + src/halfedge_geometry_test.cpp src/halfedge_mesh_test.cpp src/halfedge_mutation_test.cpp - src/halfedge_geometry_test.cpp - src/surface_misc_test.cpp - src/point_cloud_test.cpp + src/intrinsic_triangulation_test.cpp src/linear_algebra_test.cpp + src/load_test_meshes.cpp + src/point_cloud_test.cpp + src/poisson_disk_sampler_test.cpp src/stl_reader_test.cpp - src/intrinsic_triangulation_test.cpp + src/surface_misc_test.cpp ) add_executable(geometry-central-test "${TEST_SRCS}") diff --git a/test/src/poisson_disk_sampler_test.cpp b/test/src/poisson_disk_sampler_test.cpp new file mode 100644 index 00000000..5c5fdf3b --- /dev/null +++ b/test/src/poisson_disk_sampler_test.cpp @@ -0,0 +1,57 @@ + +#include + +#include +#include +#include + + +using namespace geometrycentral; +using namespace geometrycentral::surface; + + +size_t sampleSquareDisk(double scale, double sampling_distance) +{ + double const PTS[4][3] = {{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}}; + unsigned const TRIS[2][3] = {{0, 1, 2}, {0, 2, 3}}; + + SimplePolygonMesh simpleMesh; + for (const auto & t : TRIS) + simpleMesh.polygons.push_back({t[0], t[1], t[2]}); + + for (const auto & p : PTS) + simpleMesh.vertexCoordinates.push_back({scale * p[0], scale * p[1], scale * p[2]}); + + std::unique_ptr mesh; + std::unique_ptr geometry; + std::tie(mesh, geometry) = makeManifoldSurfaceMeshAndGeometry(simpleMesh.polygons, simpleMesh.vertexCoordinates); + + geometry->requireEdgeLengths(); + double meanEdgeLength = 0.; + for (auto e : mesh->edges()) + { + meanEdgeLength += geometry->edgeLengths[e]; + } + meanEdgeLength /= mesh->nEdges(); + geometry->unrequireEdgeLengths(); + + PoissonDiskSampler sampler(*mesh, *geometry); + auto samples = sampler.sample(scale * sampling_distance / meanEdgeLength); + return samples.size(); +} + + +class PoissonDiskSamplerSuite : public ::testing::Test {}; + +TEST_F(PoissonDiskSamplerSuite, PoissonDiskSamplerConstructor) { + size_t n1 = 0, n2 = 0; + for (size_t iter = 0; iter<10; iter++) + { + n1 += sampleSquareDisk(1.0, 0.1); + n2 += sampleSquareDisk(100.0, 0.1); + } + + EXPECT_GT(n1, 600); + EXPECT_LT(n1, 720); + EXPECT_NEAR(n1, n2, 100); +} From 08a7a0b86bc6d264991315a9a6231800c0902b3a Mon Sep 17 00:00:00 2001 From: Bryn Lloyd Date: Fri, 16 Aug 2024 12:08:30 +0200 Subject: [PATCH 2/8] rename variables, add comment --- test/src/poisson_disk_sampler_test.cpp | 73 ++++++++++++-------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/test/src/poisson_disk_sampler_test.cpp b/test/src/poisson_disk_sampler_test.cpp index 5c5fdf3b..393a1ed9 100644 --- a/test/src/poisson_disk_sampler_test.cpp +++ b/test/src/poisson_disk_sampler_test.cpp @@ -10,48 +10,45 @@ using namespace geometrycentral; using namespace geometrycentral::surface; -size_t sampleSquareDisk(double scale, double sampling_distance) -{ - double const PTS[4][3] = {{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}}; - unsigned const TRIS[2][3] = {{0, 1, 2}, {0, 2, 3}}; - - SimplePolygonMesh simpleMesh; - for (const auto & t : TRIS) - simpleMesh.polygons.push_back({t[0], t[1], t[2]}); - - for (const auto & p : PTS) - simpleMesh.vertexCoordinates.push_back({scale * p[0], scale * p[1], scale * p[2]}); - - std::unique_ptr mesh; - std::unique_ptr geometry; - std::tie(mesh, geometry) = makeManifoldSurfaceMeshAndGeometry(simpleMesh.polygons, simpleMesh.vertexCoordinates); - - geometry->requireEdgeLengths(); - double meanEdgeLength = 0.; - for (auto e : mesh->edges()) - { - meanEdgeLength += geometry->edgeLengths[e]; - } - meanEdgeLength /= mesh->nEdges(); - geometry->unrequireEdgeLengths(); - - PoissonDiskSampler sampler(*mesh, *geometry); - auto samples = sampler.sample(scale * sampling_distance / meanEdgeLength); - return samples.size(); +size_t sampleSquareDisk(double width, double sampling_distance) { + double const PTS[4][3] = {{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0}}; + unsigned const TRIS[2][3] = {{0, 1, 2}, {0, 2, 3}}; + + SimplePolygonMesh simpleMesh; + for (const auto& t : TRIS) simpleMesh.polygons.push_back({t[0], t[1], t[2]}); + + for (const auto& p : PTS) simpleMesh.vertexCoordinates.push_back({width * p[0], width * p[1], width * p[2]}); + + std::unique_ptr mesh; + std::unique_ptr geometry; + std::tie(mesh, geometry) = makeManifoldSurfaceMeshAndGeometry(simpleMesh.polygons, simpleMesh.vertexCoordinates); + + geometry->requireEdgeLengths(); + double meanEdgeLength = 0.; + for (auto e : mesh->edges()) { + meanEdgeLength += geometry->edgeLengths[e]; + } + meanEdgeLength /= mesh->nEdges(); + geometry->unrequireEdgeLengths(); + + PoissonDiskSampler sampler(*mesh, *geometry); + auto samples = sampler.sample(sampling_distance / meanEdgeLength); + return samples.size(); } class PoissonDiskSamplerSuite : public ::testing::Test {}; TEST_F(PoissonDiskSamplerSuite, PoissonDiskSamplerConstructor) { - size_t n1 = 0, n2 = 0; - for (size_t iter = 0; iter<10; iter++) - { - n1 += sampleSquareDisk(1.0, 0.1); - n2 += sampleSquareDisk(100.0, 0.1); - } - - EXPECT_GT(n1, 600); - EXPECT_LT(n1, 720); - EXPECT_NEAR(n1, n2, 100); + // PoissonDiskSampler doesn't allow to set a random seed. + // To prevent flaky test failures we 'average' over 10 iterations. + size_t n1 = 0, n2 = 0; + for (size_t iter = 0; iter < 10; iter++) { + n1 += sampleSquareDisk(1.0, 0.1); + n2 += sampleSquareDisk(100.0, 100.0 * 0.1); + } + + EXPECT_GT(n1, 600); + EXPECT_LT(n1, 720); + EXPECT_NEAR(n1, n2, 100); } From cb63a1a8e85ef24530e6e3df1b62d8e75c1744e4 Mon Sep 17 00:00:00 2001 From: Bryn Lloyd Date: Mon, 19 Aug 2024 08:14:43 +0200 Subject: [PATCH 3/8] fixes issue #183 --- include/geometrycentral/surface/poisson_disk_sampler.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/geometrycentral/surface/poisson_disk_sampler.h b/include/geometrycentral/surface/poisson_disk_sampler.h index da1689f2..3f08798e 100644 --- a/include/geometrycentral/surface/poisson_disk_sampler.h +++ b/include/geometrycentral/surface/poisson_disk_sampler.h @@ -22,6 +22,8 @@ class PoissonDiskSampler { std::vector pointsToAvoid = std::vector(), int rAvoidance = 1, bool use3DAvoidanceRadius = true); + double getMeanEdgeLength() const { return meanEdgeLength; } + private: // ===== Members ManifoldSurfaceMesh& mesh; From 93b65c1222e946534b0f957a510a448426732293 Mon Sep 17 00:00:00 2001 From: Bryn Lloyd Date: Tue, 20 Aug 2024 08:09:23 +0200 Subject: [PATCH 4/8] add struct for options --- .../surface/poisson_disk_sampler.h | 14 ++++++++++---- src/surface/poisson_disk_sampler.cpp | 17 +++++++++-------- test/src/poisson_disk_sampler_test.cpp | 5 ++++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/include/geometrycentral/surface/poisson_disk_sampler.h b/include/geometrycentral/surface/poisson_disk_sampler.h index 3f08798e..33706878 100644 --- a/include/geometrycentral/surface/poisson_disk_sampler.h +++ b/include/geometrycentral/surface/poisson_disk_sampler.h @@ -7,6 +7,15 @@ namespace geometrycentral { namespace surface { +struct PoissonDiskOptions { + double rCoef = 1.0; // minimum distance between samples, expressed as a multiple of the mean edge length + double absoluteRadius = -1.0; // actual minimum distance between samples + int kCandidates = 30; // number of candidate points chosen from the (r,2r)-annulus around each sample + std::vector pointsToAvoid; + int rAvoidance = 1; // radius of avoidance, expressed as a multiple of the mean edge length + bool use3DAvoidanceRadius = true; +}; + class PoissonDiskSampler { public: @@ -18,9 +27,7 @@ class PoissonDiskSampler { PoissonDiskSampler(ManifoldSurfaceMesh& mesh, VertexPositionGeometry& geometry); // ===== The main function: Sample the surface using Poisson Disk Sampling. - std::vector sample(double rCoef = 1.0, int kCandidates = 30, - std::vector pointsToAvoid = std::vector(), - int rAvoidance = 1, bool use3DAvoidanceRadius = true); + std::vector sample(const PoissonDiskOptions& options = PoissonDiskOptions()); double getMeanEdgeLength() const { return meanEdgeLength; } @@ -29,7 +36,6 @@ class PoissonDiskSampler { ManifoldSurfaceMesh& mesh; VertexPositionGeometry& geometry; - double rCoef; // minimum distance between samples, expressed as a multiple of the mean edge length int kCandidates; // number of candidate points chosen from the (r,2r)-annulus around each sample std::vector pointsToAvoid; diff --git a/src/surface/poisson_disk_sampler.cpp b/src/surface/poisson_disk_sampler.cpp index 36902887..c05196ea 100644 --- a/src/surface/poisson_disk_sampler.cpp +++ b/src/surface/poisson_disk_sampler.cpp @@ -256,22 +256,23 @@ void PoissonDiskSampler::clearData() { /* * The final function. */ -std::vector PoissonDiskSampler::sample(double rCoef_, int kCandidates_, - std::vector pointsToAvoid_, int rAvoidance, - bool use3DAvoidanceRadius) { +std::vector PoissonDiskSampler::sample(const PoissonDiskOptions& options) { clearData(); // Set parameters. - rCoef = rCoef_; - rMinDist = rCoef * meanEdgeLength; + if (options.rCoef > 0.0) { + rMinDist = options.rCoef * meanEdgeLength; + } else if (options.absoluteRadius > 0.0) { + rMinDist = options.absoluteRadius; + } sideLength = rMinDist / std::sqrt(3.0); - kCandidates = kCandidates_; - pointsToAvoid = pointsToAvoid_; + kCandidates = options.kCandidates; + pointsToAvoid = options.pointsToAvoid; // Add points to avoid. for (const SurfacePoint& pt : pointsToAvoid) { - addPointToSpatialLookupWithRadius(pt, rAvoidance - 1, use3DAvoidanceRadius); + addPointToSpatialLookupWithRadius(pt, options.rAvoidance - 1, options.use3DAvoidanceRadius); } // Carry out sampling process for each connected component. diff --git a/test/src/poisson_disk_sampler_test.cpp b/test/src/poisson_disk_sampler_test.cpp index 393a1ed9..b3dbc252 100644 --- a/test/src/poisson_disk_sampler_test.cpp +++ b/test/src/poisson_disk_sampler_test.cpp @@ -31,8 +31,11 @@ size_t sampleSquareDisk(double width, double sampling_distance) { meanEdgeLength /= mesh->nEdges(); geometry->unrequireEdgeLengths(); + PoissonDiskOptions options; + options.rCoef = sampling_distance / meanEdgeLength; + PoissonDiskSampler sampler(*mesh, *geometry); - auto samples = sampler.sample(sampling_distance / meanEdgeLength); + auto samples = sampler.sample(options); return samples.size(); } From 1fa521555805298ed8ce477c095e7adb55374404 Mon Sep 17 00:00:00 2001 From: Bryn Lloyd Date: Tue, 20 Aug 2024 08:57:35 +0200 Subject: [PATCH 5/8] seed random number generator for test --- test/src/poisson_disk_sampler_test.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/src/poisson_disk_sampler_test.cpp b/test/src/poisson_disk_sampler_test.cpp index b3dbc252..963f3a0d 100644 --- a/test/src/poisson_disk_sampler_test.cpp +++ b/test/src/poisson_disk_sampler_test.cpp @@ -34,6 +34,9 @@ size_t sampleSquareDisk(double width, double sampling_distance) { PoissonDiskOptions options; options.rCoef = sampling_distance / meanEdgeLength; + // make tests reproducible + geometrycentral::util_mersenne_twister.seed(101); + PoissonDiskSampler sampler(*mesh, *geometry); auto samples = sampler.sample(options); return samples.size(); From 129494dbf6ddd1c113ff2b49e9c24ea342c2499f Mon Sep 17 00:00:00 2001 From: nzfeng Date: Sat, 24 Aug 2024 16:29:24 -0400 Subject: [PATCH 6/8] Refine Poisson disk sampler options and update docs --- .../surface/algorithms/surface_sampling.md | 33 ++++++++++----- .../surface/poisson_disk_sampler.h | 18 ++++---- src/surface/poisson_disk_sampler.cpp | 41 +++++++------------ 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/docs/docs/surface/algorithms/surface_sampling.md b/docs/docs/surface/algorithms/surface_sampling.md index 96b3d4ab..379e0ec4 100644 --- a/docs/docs/surface/algorithms/surface_sampling.md +++ b/docs/docs/surface/algorithms/surface_sampling.md @@ -8,9 +8,7 @@ Currently the algorithm only works on manifold meshes. The algorithm has a few parameters that roughly correspond to the algorithm of Bridson's 2007 [Fast Poisson Disk Sampling in Arbitrary Dimensions](https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf). -Additionally, you can specify points around samples should be avoided (shown in red below.) By default, samples will avoid these points with the same radius `r` used in the rest of the algorithm. You can optionally specify a "radius of avoidance" for these points, where the radius of avoidance is given in multiples of `r`. - -The radius of avoidance can be further be specified to be a radius in 3D space, or a radius in terms of distance along the surface. The former will produce a radius of avoidance that will appear perfectly round and is likely more visually pleasing, but for very large radii may occlude samples from opposite sides of the mesh. The latter will restrict the radius of avoidance to only be along the surface, but such a metric ball will not appear perfectly round, especially in areas with very large and sudden changes in curvature. +Additionally, you can specify points around samples should be avoided (shown in red below.) ![poisson disk sample with point of avoidance](/media/poisson_disk_sample.png) @@ -23,15 +21,9 @@ The radius of avoidance can be further be specified to be a radius in 3D space, The mesh and geometry cannot be changed after construction. -??? func "`#!cpp std::vector PoissonDiskSampler::sample(double rCoef = 1.0, int kCandidates = 30, std::vector pointsToAvoid = std::vector(), int rAvoidance = 1, bool use3DAvoidanceRadius = true);`" +??? func "`#!cpp std::vector sample(const PoissonDiskOptions& options = PoissonDiskOptions())`" Poisson disk-sample the surface mesh. - - - `rCoef`: corresponds to the minimum distance between samples, expressed as a multiple of the mean edge length. The actual minimum distance is computed as `r = rCoef * meanEdgeLength` - - `kCandidates`: the number of candidate points chosen from the (r,2r)-annulus around each sample. - - `pointsToAvoid`: SurfacePoints which samples should avoid. - - `rAvoidance`: the radius of avoidance around each point to avoid, expressed as a multiple of `r`. - - `use3DAvoidanceRadius`: If true, the radius of avoidance will specify a solid ball in 3D space around which samples are avoided. Otherwise, samples are avoided within a ball _on the surface_. ### Example @@ -51,4 +43,23 @@ std::tie(mesh, geometry) = readManifoldSurfaceMesh(filename); // construct a solver PoissonDiskSampler poissonSampler(*mesh, *geometry); std::vector samples = poissonSampler.sample(); // sample using default parameters -``` \ No newline at end of file + +// Sample with some different parameters. +PoissonDiskOptions sampleOptions; +sampleOptions.r = 2.; +std::vector newSamples = poissonSampler.sample(sampleOptions); +``` +## Helper Types +### Options + +Options are passed in to `options` via a `PoissonDiskOptions` object. + +| Field | Default value |Meaning| +|---|---|---| +| `#!cpp double r`| `1` | The minimum distance between samples, expressed in world-space units. | +| `#!cpp int kCandidates`| `30` | The number of candidate points chosen from the (`r`,2`r`)-annulus around each sample. | +| `#!cpp std::vector pointsToAvoid`| `std::vector()` | Points which samples should avoid. | +| `#!cpp double rAvoidance`| `1` | The radius of avoidance around each point to avoid, expressed in world-space units. | +| `#!cpp bool use3DAvoidance`| `true` | If true, the radius of avoidance will specify a solid ball in 3D space around which samples are avoided. Otherwise, samples are avoided within a geodesic ball on the surface. | + +Using the `use3DAvoidance` option, the radius of avoidance `rAvoidance` can specify either the radius in 3D space, or in terms of distance along the surface. The former will produce a radius of avoidance that will appear perfectly round, and is likely more visually pleasing, but for very large radii may occlude samples from opposite sides of the mesh. The latter will restrict the radius of avoidance to only be along the surface, but such a metric ball may not appear perfectly round, especially in areas with very large changes in curvature. \ No newline at end of file diff --git a/include/geometrycentral/surface/poisson_disk_sampler.h b/include/geometrycentral/surface/poisson_disk_sampler.h index 33706878..2451be05 100644 --- a/include/geometrycentral/surface/poisson_disk_sampler.h +++ b/include/geometrycentral/surface/poisson_disk_sampler.h @@ -8,12 +8,11 @@ namespace geometrycentral { namespace surface { struct PoissonDiskOptions { - double rCoef = 1.0; // minimum distance between samples, expressed as a multiple of the mean edge length - double absoluteRadius = -1.0; // actual minimum distance between samples - int kCandidates = 30; // number of candidate points chosen from the (r,2r)-annulus around each sample + double r = 1.0; // minimum distance between samples + int kCandidates = 30; // number of candidate points chosen from the (r,2r)-annulus around each sample std::vector pointsToAvoid; - int rAvoidance = 1; // radius of avoidance, expressed as a multiple of the mean edge length - bool use3DAvoidanceRadius = true; + double rAvoidance = 1.0; // radius of avoidance + bool use3DAvoidance = true; }; class PoissonDiskSampler { @@ -29,8 +28,6 @@ class PoissonDiskSampler { // ===== The main function: Sample the surface using Poisson Disk Sampling. std::vector sample(const PoissonDiskOptions& options = PoissonDiskOptions()); - double getMeanEdgeLength() const { return meanEdgeLength; } - private: // ===== Members ManifoldSurfaceMesh& mesh; @@ -39,9 +36,8 @@ class PoissonDiskSampler { int kCandidates; // number of candidate points chosen from the (r,2r)-annulus around each sample std::vector pointsToAvoid; - double rMinDist; // the actual minimum distance between samples - double meanEdgeLength; // the mean edge length - double sideLength; // side length of each bucket + double rMinDist; // the minimum distance between samples + double sideLength; // side length of each bucket std::vector faceFromEachComponent; // holds one face for each connected component in the mesh std::vector activeList; // holds candidate points @@ -59,7 +55,7 @@ class PoissonDiskSampler { void addNewSample(const SurfacePoint& sample); SpatialKey positionKey(const Vector3& position) const; void addPointToSpatialLookup(const Vector3& newPos); - void addPointToSpatialLookupWithRadius(const SurfacePoint& newPoint, int R = 0, bool use3DAvoidanceRadius = true); + void addPointToSpatialLookupWithRadius(const SurfacePoint& newPoint, double radius = 0, bool use3DAvoidance = true); bool isCandidateValid(const SurfacePoint& candidate) const; void sampleOnConnectedComponent(const Face& f); void clearData(); diff --git a/src/surface/poisson_disk_sampler.cpp b/src/surface/poisson_disk_sampler.cpp index c05196ea..49296dc7 100644 --- a/src/surface/poisson_disk_sampler.cpp +++ b/src/surface/poisson_disk_sampler.cpp @@ -6,15 +6,6 @@ namespace surface { PoissonDiskSampler::PoissonDiskSampler(ManifoldSurfaceMesh& mesh_, VertexPositionGeometry& geometry_) : mesh(mesh_), geometry(geometry_) { - // Compute mean edge length. - geometry.requireEdgeLengths(); - meanEdgeLength = 0.; - for (Edge e : mesh.edges()) { - meanEdgeLength += geometry.edgeLengths[e]; - } - meanEdgeLength /= mesh.nEdges(); - geometry.unrequireEdgeLengths(); - // Prepare spatial lookup Vector3 bboxMin, bboxMax; std::tie(bboxMin, bboxMax) = boundingBox(); @@ -124,17 +115,15 @@ void PoissonDiskSampler::addPointToSpatialLookup(const Vector3& newPos) { } /* - * Mark all buckets with a radius of buckets as occupied, as well. + * Mark all buckets within a radius of as occupied. */ -void PoissonDiskSampler::addPointToSpatialLookupWithRadius(const SurfacePoint& newPoint, int R, - bool use3DAvoidanceRadius) { +void PoissonDiskSampler::addPointToSpatialLookupWithRadius(const SurfacePoint& newPoint, double radius, + bool use3DAvoidance) { Vector3 newPos = newPoint.interpolate(geometry.vertexPositions); addPointToSpatialLookup(newPos); - if (R <= 0) return; - - if (!use3DAvoidanceRadius) { + if (!use3DAvoidance) { // This places fictitious points in a metric ball approximately of radius R*r centered at . // The solid ball is built by constructing R layers, radially outward; each layer is spaced r apart, and // points in each layer are spaced approx. r apart around the circular layer. The idea is that no point can be added @@ -144,21 +133,22 @@ void PoissonDiskSampler::addPointToSpatialLookupWithRadius(const SurfacePoint& n // "spiky" meshes. SurfacePoint pathEndpoint; TraceGeodesicResult trace; - for (int rIter = 0; rIter <= R; rIter++) { - double dist = rIter * rMinDist; - for (double theta = 0.; theta <= 2. * PI; theta += rMinDist / dist / 2.) { - trace = traceGeodesic(geometry, newPoint, dist * Vector2::fromAngle(theta)); + double r = rMinDist; + while (r < radius) { + for (double theta = 0.; theta <= 2. * PI; theta += rMinDist / r / 2.) { + trace = traceGeodesic(geometry, newPoint, r * Vector2::fromAngle(theta)); pathEndpoint = trace.endPoint; addPointToSpatialLookup(pathEndpoint.interpolate(geometry.vertexPositions)); } + r += rMinDist; } } else { // This places fictitious points in a *solid 3D ball* approximately of radius R*r centered at . // The solid ball is built by constructing R layers, radially outward; each layer is spaced r apart, and // points in each layer are spaced approx. r apart in a "grid" (a mapping from the cylinder to the sphere that has // been scaled s.t. projected points end up being approx. r apart.) - for (int rIter = 0; rIter <= R; rIter++) { - double r = rIter * rMinDist; + double r = rMinDist; + while (r < radius) { for (double z = -0.99; z <= 0.99; z += rMinDist) { double coeff = std::sqrt(1. - z * z); for (double theta = 0.0; theta <= 2.0 * PI; theta += rMinDist / coeff) { @@ -167,6 +157,7 @@ void PoissonDiskSampler::addPointToSpatialLookupWithRadius(const SurfacePoint& n addPointToSpatialLookup(pos); } } + r += rMinDist; } } } @@ -261,18 +252,14 @@ std::vector PoissonDiskSampler::sample(const PoissonDiskOptions& o clearData(); // Set parameters. - if (options.rCoef > 0.0) { - rMinDist = options.rCoef * meanEdgeLength; - } else if (options.absoluteRadius > 0.0) { - rMinDist = options.absoluteRadius; - } + rMinDist = options.r; sideLength = rMinDist / std::sqrt(3.0); kCandidates = options.kCandidates; pointsToAvoid = options.pointsToAvoid; // Add points to avoid. for (const SurfacePoint& pt : pointsToAvoid) { - addPointToSpatialLookupWithRadius(pt, options.rAvoidance - 1, options.use3DAvoidanceRadius); + addPointToSpatialLookupWithRadius(pt, options.rAvoidance, options.use3DAvoidance); } // Carry out sampling process for each connected component. From b97d026c26ecca04f0e4edde2a626ddb4d046d33 Mon Sep 17 00:00:00 2001 From: nzfeng Date: Sat, 24 Aug 2024 16:35:22 -0400 Subject: [PATCH 7/8] update the PoissonDiskSampler test --- test/src/poisson_disk_sampler_test.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/test/src/poisson_disk_sampler_test.cpp b/test/src/poisson_disk_sampler_test.cpp index 963f3a0d..9e2690c5 100644 --- a/test/src/poisson_disk_sampler_test.cpp +++ b/test/src/poisson_disk_sampler_test.cpp @@ -23,16 +23,8 @@ size_t sampleSquareDisk(double width, double sampling_distance) { std::unique_ptr geometry; std::tie(mesh, geometry) = makeManifoldSurfaceMeshAndGeometry(simpleMesh.polygons, simpleMesh.vertexCoordinates); - geometry->requireEdgeLengths(); - double meanEdgeLength = 0.; - for (auto e : mesh->edges()) { - meanEdgeLength += geometry->edgeLengths[e]; - } - meanEdgeLength /= mesh->nEdges(); - geometry->unrequireEdgeLengths(); - PoissonDiskOptions options; - options.rCoef = sampling_distance / meanEdgeLength; + options.r = sampling_distance; // make tests reproducible geometrycentral::util_mersenne_twister.seed(101); From aa53792b21ca2a1b02ca38a9f14411d721f4c20e Mon Sep 17 00:00:00 2001 From: nzfeng Date: Sun, 25 Aug 2024 10:52:01 -0400 Subject: [PATCH 8/8] Change parameter names --- docs/docs/surface/algorithms/surface_sampling.md | 8 ++++---- include/geometrycentral/surface/poisson_disk_sampler.h | 4 ++-- src/surface/poisson_disk_sampler.cpp | 4 ++-- test/src/poisson_disk_sampler_test.cpp | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/docs/surface/algorithms/surface_sampling.md b/docs/docs/surface/algorithms/surface_sampling.md index 379e0ec4..3eba7d50 100644 --- a/docs/docs/surface/algorithms/surface_sampling.md +++ b/docs/docs/surface/algorithms/surface_sampling.md @@ -46,7 +46,7 @@ std::vector samples = poissonSampler.sample(); // sample using def // Sample with some different parameters. PoissonDiskOptions sampleOptions; -sampleOptions.r = 2.; +sampleOptions.minDist = 2.; std::vector newSamples = poissonSampler.sample(sampleOptions); ``` ## Helper Types @@ -56,10 +56,10 @@ Options are passed in to `options` via a `PoissonDiskOptions` object. | Field | Default value |Meaning| |---|---|---| -| `#!cpp double r`| `1` | The minimum distance between samples, expressed in world-space units. | +| `#!cpp double minDist`| `1` | The minimum distance `r` between samples, expressed in world-space units. | | `#!cpp int kCandidates`| `30` | The number of candidate points chosen from the (`r`,2`r`)-annulus around each sample. | | `#!cpp std::vector pointsToAvoid`| `std::vector()` | Points which samples should avoid. | -| `#!cpp double rAvoidance`| `1` | The radius of avoidance around each point to avoid, expressed in world-space units. | +| `#!cpp double minDistAvoidance`| `1` | The radius of avoidance around each point to avoid, expressed in world-space units. | | `#!cpp bool use3DAvoidance`| `true` | If true, the radius of avoidance will specify a solid ball in 3D space around which samples are avoided. Otherwise, samples are avoided within a geodesic ball on the surface. | -Using the `use3DAvoidance` option, the radius of avoidance `rAvoidance` can specify either the radius in 3D space, or in terms of distance along the surface. The former will produce a radius of avoidance that will appear perfectly round, and is likely more visually pleasing, but for very large radii may occlude samples from opposite sides of the mesh. The latter will restrict the radius of avoidance to only be along the surface, but such a metric ball may not appear perfectly round, especially in areas with very large changes in curvature. \ No newline at end of file +Using the `use3DAvoidance` option, the radius of avoidance `minDistAvoidance` can specify either the radius in 3D space, or in terms of distance along the surface. The former will produce a radius of avoidance that will appear perfectly round, and is likely more visually pleasing, but for very large radii may occlude samples from opposite sides of the mesh. The latter will restrict the radius of avoidance to only be along the surface, but such a metric ball may not appear perfectly round, especially in areas with very large changes in curvature. \ No newline at end of file diff --git a/include/geometrycentral/surface/poisson_disk_sampler.h b/include/geometrycentral/surface/poisson_disk_sampler.h index 2451be05..2d34c821 100644 --- a/include/geometrycentral/surface/poisson_disk_sampler.h +++ b/include/geometrycentral/surface/poisson_disk_sampler.h @@ -8,10 +8,10 @@ namespace geometrycentral { namespace surface { struct PoissonDiskOptions { - double r = 1.0; // minimum distance between samples + double minDist = 1.0; // minimum distance r between samples int kCandidates = 30; // number of candidate points chosen from the (r,2r)-annulus around each sample std::vector pointsToAvoid; - double rAvoidance = 1.0; // radius of avoidance + double minDistAvoidance = 1.0; // radius of avoidance bool use3DAvoidance = true; }; diff --git a/src/surface/poisson_disk_sampler.cpp b/src/surface/poisson_disk_sampler.cpp index 49296dc7..afb70cdd 100644 --- a/src/surface/poisson_disk_sampler.cpp +++ b/src/surface/poisson_disk_sampler.cpp @@ -252,14 +252,14 @@ std::vector PoissonDiskSampler::sample(const PoissonDiskOptions& o clearData(); // Set parameters. - rMinDist = options.r; + rMinDist = options.minDist; sideLength = rMinDist / std::sqrt(3.0); kCandidates = options.kCandidates; pointsToAvoid = options.pointsToAvoid; // Add points to avoid. for (const SurfacePoint& pt : pointsToAvoid) { - addPointToSpatialLookupWithRadius(pt, options.rAvoidance, options.use3DAvoidance); + addPointToSpatialLookupWithRadius(pt, options.minDistAvoidance, options.use3DAvoidance); } // Carry out sampling process for each connected component. diff --git a/test/src/poisson_disk_sampler_test.cpp b/test/src/poisson_disk_sampler_test.cpp index 9e2690c5..71db4aa4 100644 --- a/test/src/poisson_disk_sampler_test.cpp +++ b/test/src/poisson_disk_sampler_test.cpp @@ -24,7 +24,7 @@ size_t sampleSquareDisk(double width, double sampling_distance) { std::tie(mesh, geometry) = makeManifoldSurfaceMeshAndGeometry(simpleMesh.polygons, simpleMesh.vertexCoordinates); PoissonDiskOptions options; - options.r = sampling_distance; + options.minDist = sampling_distance; // make tests reproducible geometrycentral::util_mersenne_twister.seed(101);