Skip to content

Commit 6e9b22f

Browse files
committed
import poisson disk sampling from Brad's branch:
gsoc2024-triangle_mesh_sampling-bmccoy@8db7ae84e5 Comment out all the geodesic part for now
1 parent 96d543a commit 6e9b22f

File tree

7 files changed

+544
-1
lines changed

7 files changed

+544
-1
lines changed

Polygon_mesh_processing/examples/Polygon_mesh_processing/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ create_single_source_cgal_program("polyhedral_envelope.cpp" )
2121
create_single_source_cgal_program("polyhedral_envelope_of_triangle_soup.cpp" )
2222
create_single_source_cgal_program("polyhedral_envelope_mesh_containment.cpp" )
2323
create_single_source_cgal_program("sample_example.cpp" )
24+
create_single_source_cgal_program("poisson_sampling_sm_example.cpp" )
2425
create_single_source_cgal_program("self_intersections_example.cpp" )
2526
create_single_source_cgal_program("volume_connected_components.cpp")
2627

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
2+
#include <CGAL/Surface_mesh.h>
3+
#include <CGAL/Polygon_mesh_processing/distance.h>
4+
5+
#include <CGAL/Polygon_mesh_processing/IO/polygon_mesh_io.h>
6+
#include <iostream>
7+
#include <string>
8+
#include <vector>
9+
10+
typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
11+
typedef K::Point_3 Point;
12+
13+
typedef CGAL::Surface_mesh<Point> Mesh;
14+
typedef boost::graph_traits<Mesh>::vertex_descriptor vertex_descriptor;
15+
typedef boost::graph_traits<Mesh>::face_descriptor face_descriptor;
16+
17+
namespace PMP = CGAL::Polygon_mesh_processing;
18+
19+
int main(int argc, char* argv[])
20+
{
21+
const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/eight.off");
22+
23+
Mesh mesh;
24+
if(!PMP::IO::read_polygon_mesh(filename, mesh))
25+
{
26+
std::cerr << "Invalid input." << std::endl;
27+
return 1;
28+
}
29+
30+
const double sampling_radius = (argc > 2) ? std::atof(argv[2]) : 0.009;
31+
32+
const std::size_t number_of_darts = (argc > 3) ? std::atof(argv[3]) : 20;
33+
34+
CGAL::Random random_seed = (argc > 4) ? std::atof(argv[4]) : CGAL::get_default_random();
35+
36+
std::vector<Point> points;
37+
PMP::sample_triangle_mesh(mesh,
38+
std::back_inserter(points),
39+
CGAL::parameters::use_poisson_disk_sampling_euclidean(true)
40+
.sampling_radius(sampling_radius)
41+
.number_of_darts(number_of_darts)
42+
.random_seed(random_seed));
43+
44+
std::ofstream out("sampling.xyz");
45+
out << std::setprecision(17);
46+
std::copy(points.begin(), points.end(), std::ostream_iterator<Point>(out, "\n"));
47+
48+
std::cout << points.size() << std::endl;
49+
return 0;
50+
}

Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/distance.h

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <CGAL/Polygon_mesh_processing/internal/AABB_traversal_traits_with_Hausdorff_distance.h>
2020
#include <CGAL/Polygon_mesh_processing/measure.h>
2121
#include <CGAL/Polygon_mesh_processing/bbox.h>
22+
#include <CGAL/Polygon_mesh_processing/internal/poisson_disk_sampling.h>
2223

2324
#include <CGAL/AABB_tree.h>
2425
#include <CGAL/AABB_traits_3.h>
@@ -476,6 +477,33 @@ struct Triangle_structure_sampler_for_triangle_mesh
476477
min_sq_edge_length = (std::numeric_limits<double>::max)();
477478
}
478479

480+
void procede()
481+
{
482+
using parameters::choose_parameter;
483+
using parameters::get_parameter;
484+
using parameters::is_default_parameter;
485+
486+
bool use_pds_e = choose_parameter(get_parameter(this->np, internal_np::use_poisson_disk_sampling_euclidean), false);
487+
// bool use_pds_g = choose_parameter(get_parameter(this->np, internal_np::use_poisson_disk_sampling_geodesic), false);
488+
double sampling_radius = choose_parameter(get_parameter(this->np, internal_np::sampling_radius), 1.);
489+
std::size_t number_of_darts = choose_parameter(get_parameter(this->np, internal_np::number_of_darts),30.);
490+
CGAL::Random random_seed = choose_parameter(get_parameter(this->np, internal_np::random_seed),CGAL::get_default_random());
491+
492+
493+
if (use_pds_e /* || use_pds_g */)
494+
{
495+
std::vector<typename GeomTraits::Point_3> points = /* use_pds_e ? */
496+
internal::poisson_disk_sampling<GeomTraits, internal::EUCLIDEAN_DISTANCE>(tm, sampling_radius,number_of_darts,random_seed)
497+
/* : internal::poisson_disk_sampling<GeomTraits, internal::GEODESIC_DISTANCE>(tm, sampling_radius,number_of_darts,random_seed) */;
498+
std::copy(points.begin(), points.end(), this->out);
499+
}
500+
else
501+
{
502+
static_cast<Base*>(this)->procede();
503+
}
504+
}
505+
506+
479507
std::pair<TriangleIterator, TriangleIterator> get_range()
480508
{
481509
return std::make_pair(faces(tm).begin(), faces(tm).end());
@@ -890,6 +918,36 @@ struct Triangle_structure_sampler_for_triangle_soup
890918
* \cgalParamType{unsigned int}
891919
* \cgalParamDefault{`0`}
892920
* \cgalParamNEnd
921+
*
922+
* \cgalParamNBegin{use_poisson_disk_sampling_euclidean}
923+
* \cgalParamDescription{if `true` is passed, the Euclidean distance is used to compute the distance between sampled points.}
924+
* \cgalParamType{Boolean}
925+
* \cgalParamDefault{`false`}
926+
* \cgalParamNEnd
927+
*
928+
* \cgalParamNBegin{use_poisson_disk_sampling_geodesic}
929+
* \cgalParamDescription{if `true` is passed, the approximate geodesic distance is used to compute the distance between
930+
* sampled points.}
931+
* \cgalParamType{Boolean}
932+
* \cgalParamDefault{`false`}
933+
* \cgalParamExtra{The geodesic distance is approximated using the 'locally_shortest_path'
934+
* function.}
935+
* \cgalParamNEnd
936+
*
937+
* \cgalParamNBegin{sampling_radius}
938+
* \cgalParamDescription{a value used by Poisson disk sampling to specify the minimum allowable distance between
939+
* points in the sample.}
940+
* \cgalParamType{double}
941+
* \cgalParamDefault{`1`}
942+
* \cgalParamNEnd
943+
*
944+
* \cgalParamNBegin{number_of_darts}
945+
* \cgalParamDescription{a value used by Poisson disk sampling to specify the number of attempts to find a point in the annulus
946+
* around a sample point that is sufficiently far from all other points in the sample.}
947+
* \cgalParamType{std::size_t}
948+
* \cgalParamDefault{`30`}
949+
* \cgalParamNEnd
950+
*
893951
* \cgalNamedParamsEnd
894952
*
895953
* @see `CGAL::Polygon_mesh_processing::sample_triangle_soup()`
@@ -970,7 +1028,7 @@ sample_triangle_mesh(const TriangleMesh& tm,
9701028
* of the smallest non-null edge of the soup or the value passed to the named parameter
9711029
* `grid_spacing`.}
9721030
* \cgalParamNEnd
973-
* \cgalParamNBegin{use_monte_carlo_sampling}
1031+
* \cgalParamNBegin{use_monte_carlo_sampling}
9741032
* \cgalParamDescription{if `true` is passed, points are generated randomly in each triangle.}
9751033
* \cgalParamType{Boolean}
9761034
* \cgalParamDefault{`false`}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// Copyright (c) 2025 GeometryFactory (France).
2+
// All rights reserved.
3+
//
4+
// This file is part of CGAL (www.cgal.org).
5+
//
6+
// $URL$
7+
// $Id$
8+
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
9+
//
10+
//
11+
// Author(s) : Sebastien Loriot
12+
// Bradley McCoy
13+
14+
#ifndef CGAL_POLYGON_MESH_PROCESSING_INTERNAL_POISSON_DISK_SAMPLING_H
15+
#define CGAL_POLYGON_MESH_PROCESSING_INTERNAL_POISSON_DISK_SAMPLING_H
16+
17+
18+
#include <CGAL/point_generators_3.h>
19+
#include <CGAL/Polygon_mesh_processing/locate.h>
20+
#include <CGAL/boost/graph/Face_filtered_graph.h>
21+
#include <CGAL/squared_distance_3.h>
22+
#include <CGAL/boost/graph/helpers.h>
23+
#include <CGAL/Dynamic_property_map.h>
24+
#ifdef CGAL_USE_BSURF
25+
#include <CGAL/Polygon_mesh_processing/Bsurf/locally_shortest_path.h>
26+
#endif
27+
28+
#include <vector>
29+
#include <queue>
30+
31+
namespace CGAL {
32+
namespace Polygon_mesh_processing {
33+
namespace internal {
34+
35+
enum Distance_version { EUCLIDEAN_DISTANCE, GEODESIC_DISTANCE };
36+
37+
template <class GeomTraits>
38+
double euclideanDistancePoints(const typename GeomTraits::Point_3& source,
39+
const typename GeomTraits::Point_3& target)
40+
{
41+
return sqrt(CGAL::squared_distance(source,target));
42+
}
43+
44+
#ifdef CGAL_USE_BSURF
45+
template <class GeomTraits, class TriangleMesh>
46+
double geodesiceApproximation(const Face_location<TriangleMesh, typename GeomTraits::FT>& source,
47+
const Face_location<TriangleMesh, typename GeomTraits::FT>& target,
48+
const TriangleMesh& mesh,
49+
const Dual_geodesic_solver<double>& solver)
50+
{
51+
std::vector<Edge_location<TriangleMesh, typename GeomTraits::FT>> edge_locations;
52+
CGAL::Polygon_mesh_processing::locally_shortest_path<double>(source, target, mesh, edge_locations, solver);
53+
54+
return path_length<GeomTraits>(edge_locations,source,target,mesh);
55+
}
56+
#endif
57+
58+
//function to switch between geodesic and Euclidean distance
59+
template <class GeomTraits, Distance_version V, class TriangleMesh>
60+
double distancePoints(const TriangleMesh& /* mesh */,
61+
const typename GeomTraits::Point_3& source,
62+
const typename GeomTraits::Point_3& target,
63+
const Face_location<TriangleMesh, typename GeomTraits::FT>& /* start */,
64+
const Face_location<TriangleMesh, typename GeomTraits::FT>& /* end */
65+
#ifdef CGAL_USE_BSURF
66+
, const Dual_geodesic_solver<double>& solver
67+
#endif
68+
)
69+
{
70+
#ifdef CGAL_USE_BSURF
71+
if constexpr (V==GEODESIC_DISTANCE)
72+
return geodesiceApproximation<GeomTraits>(start, end, mesh, solver);
73+
#endif
74+
if constexpr (V==EUCLIDEAN_DISTANCE)
75+
return euclideanDistancePoints<GeomTraits>(source, target);
76+
return 0;
77+
}
78+
79+
80+
template <class GeomTraits, Distance_version V, class TriangleMesh>
81+
std::vector<typename boost::graph_traits<TriangleMesh>::face_descriptor>
82+
faces_in_sub_mesh(const typename GeomTraits::Point_3& c,
83+
typename boost::graph_traits<TriangleMesh>::face_descriptor fc,
84+
const TriangleMesh& mesh,
85+
double minDistance)
86+
{
87+
// using Point = typename GeomTraits::Point_3;
88+
using Graph_traits = boost::graph_traits<TriangleMesh>;
89+
using face_descriptor = typename Graph_traits::face_descriptor;
90+
using halfedge_descriptor = typename Graph_traits::halfedge_descriptor;
91+
92+
//Begin flooding
93+
face_descriptor fd = fc;
94+
95+
std::vector<bool> selected(num_faces(mesh), false);
96+
std::vector<face_descriptor> selection;
97+
selected[fd] = true;
98+
selection.push_back(fd);
99+
100+
auto do_queue_edge = [&](halfedge_descriptor h)
101+
{
102+
halfedge_descriptor hopp=opposite(h, mesh);
103+
if (is_border(hopp, mesh) || selected[face(hopp, mesh)]) return false;
104+
105+
typename GeomTraits::Segment_3 edge(mesh.point(source(h,mesh)), mesh.point(target(h,mesh)));
106+
107+
// return (distancePoints<V>(mesh, mesh.point(source(h,mesh)), c, tree)< 3*minDistance ||
108+
// distancePoints<V>(mesh, mesh.point(target(h,mesh)), c, tree)< 3*minDistance);
109+
110+
111+
return typename GeomTraits::Compare_squared_distance_3()(c, edge, sqrt(3*minDistance))!=CGAL::LARGER;
112+
};
113+
114+
115+
std::vector<halfedge_descriptor> queue;
116+
for (halfedge_descriptor h : CGAL::halfedges_around_face(halfedge(fd, mesh), mesh))
117+
if (do_queue_edge(h))
118+
queue.push_back(opposite(h, mesh));
119+
120+
while (!queue.empty())
121+
{
122+
halfedge_descriptor h = queue.back();
123+
face_descriptor f = face(h, mesh);
124+
queue.pop_back();
125+
if (!selected[f])
126+
{
127+
selected[f]=true;
128+
selection.push_back(f);
129+
}
130+
131+
h=next(h, mesh);
132+
if (do_queue_edge(h)) queue.push_back(opposite(h, mesh));
133+
h=next(h, mesh);
134+
if (do_queue_edge(h)) queue.push_back(opposite(h, mesh));
135+
}
136+
137+
return selection;
138+
}
139+
140+
template <class GeomTraits, Distance_version V, class TriangleMesh, class PointsPerFaceMap>
141+
bool
142+
is_far_enough(const typename GeomTraits::Point_3 c,
143+
const Face_location<TriangleMesh, typename GeomTraits::FT> c_location,
144+
const TriangleMesh& mesh,
145+
double minDistance,
146+
std::vector<typename boost::graph_traits<TriangleMesh>::face_descriptor> selection,
147+
const PointsPerFaceMap& face_points
148+
#ifdef CGAL_USE_BSURF
149+
, const Dual_geodesic_solver<double>& solver
150+
#endif
151+
)
152+
{
153+
for (typename boost::graph_traits<TriangleMesh>::face_descriptor f : selection)
154+
{
155+
for(const typename GeomTraits::Point_3& p : face_points[f])
156+
{
157+
Face_location<TriangleMesh, typename GeomTraits::FT> p_location = locate_in_face(p, f, mesh);
158+
//Todo: Ask why is distancePoints for Euclidean so much slower then just
159+
//calling euclideanDistancePoints?
160+
if (distancePoints<GeomTraits,V>(mesh, c, p, c_location, p_location
161+
#ifdef CGAL_USE_BSURF
162+
, solver
163+
#endif
164+
) < minDistance)
165+
//if (euclideanDistancePoints(c, p) < minDistance)
166+
//if (geodesiceApproximation(c_location, p_location, mesh) < minDistance)
167+
return false;
168+
}
169+
}
170+
return true;
171+
}
172+
173+
template <class GeomTraits, Distance_version V, class TriangleMesh>
174+
std::vector<typename GeomTraits::Point_3>
175+
poisson_disk_sampling(const TriangleMesh& mesh,
176+
double minDistance,
177+
std::size_t kMaxTries,
178+
CGAL::Random& r)
179+
{
180+
using Point = typename GeomTraits::Point_3;
181+
using Graph_traits = boost::graph_traits<TriangleMesh>;
182+
using face_descriptor = typename Graph_traits::face_descriptor;
183+
using FT = typename GeomTraits::FT;
184+
using Face_location = Face_location<TriangleMesh, FT>;
185+
186+
#ifdef CGAL_USE_BSURF
187+
Dual_geodesic_solver<double> solver;
188+
if constexpr (V==GEODESIC_DISTANCE)
189+
init_geodesic_dual_solver(solver, mesh);
190+
#endif
191+
192+
std::vector<Point> points;
193+
std::queue<std::pair<Point, face_descriptor>> activePoints;
194+
195+
Random_points_in_triangle_mesh_3<TriangleMesh> g(mesh, r);
196+
Point c = *g;
197+
face_descriptor fc = g.last_item_picked();
198+
199+
std::vector<std::vector<Point>> face_points(num_faces(mesh));
200+
201+
face_points[fc].push_back(c);
202+
activePoints.push(std::make_pair(c, fc));
203+
points.push_back(c);
204+
205+
Point currentPoint;
206+
face_descriptor currentFace;
207+
while (!activePoints.empty())
208+
{
209+
std::tie(currentPoint, currentFace) = activePoints.front();
210+
activePoints.pop();
211+
212+
std::vector<face_descriptor> selection = faces_in_sub_mesh<GeomTraits,V>(currentPoint, currentFace, mesh, minDistance);
213+
Face_location current_location = locate_in_face(currentPoint, currentFace, mesh);
214+
if (current_location.second[0]<0) current_location.second[0]=0;
215+
if (current_location.second[1]<0) current_location.second[1]=0;
216+
if (current_location.second[2]<0) current_location.second[2]=0;
217+
std::size_t k = 0;
218+
while (k < kMaxTries)
219+
{
220+
double angle = 2 * CGAL_PI * r.get_double();
221+
double distance = minDistance + minDistance * r.get_double();
222+
typename GeomTraits::Vector_2 dir(cos(angle),sin(angle));
223+
#warning find alternative
224+
CGAL_USE(distance);
225+
std::vector<Face_location> path /* = straightest_geodesic<GeomTraits>(current_location, dir, distance, mesh) */ ;
226+
Face_location newLocation = path.back();
227+
Point newPoint = construct_point(path.back(), mesh);
228+
if(is_far_enough<GeomTraits,V>(newPoint, newLocation, mesh, minDistance, selection, make_random_access_property_map(face_points)
229+
#ifdef CGAL_USE_BSURF
230+
, solver
231+
#endif
232+
))
233+
{
234+
face_points[path.back().first].push_back(newPoint);
235+
activePoints.push(std::make_pair(newPoint,path.back().first));
236+
points.push_back(newPoint);
237+
}else
238+
{
239+
++k;
240+
}
241+
}
242+
}
243+
244+
return points;
245+
}
246+
247+
} } } // CGAL::Polygon_mesh_processing::internal
248+
249+
250+
#endif //CGAL_POLYGON_MESH_PROCESSING_INTERNAL_POISSON_DISK_SAMPLING_H

0 commit comments

Comments
 (0)