PolyFEM
Loading...
Searching...
No Matches
PolygonUtils.cpp
Go to the documentation of this file.
1
2#include "PolygonUtils.hpp"
4#include <geogram/numerics/predicates.h>
5#include <igl/barycenter.h>
7
8namespace
9{
10
11 inline GEO::Sign point_is_in_half_plane(
12 const Eigen::RowVector2d &p, const Eigen::RowVector2d &q1, const Eigen::RowVector2d &q2)
13 {
14 return GEO::PCK::orient_2d(q1.data(), q2.data(), p.data());
15 }
16
17 inline bool intersect_segments(
18 const Eigen::RowVector2d &p1, const Eigen::RowVector2d &p2,
19 const Eigen::RowVector2d &q1, const Eigen::RowVector2d &q2,
20 Eigen::RowVector2d &result)
21 {
22
23 Eigen::RowVector2d Vp = p2 - p1;
24 Eigen::RowVector2d Vq = q2 - q1;
25 Eigen::RowVector2d pq = q1 - p1;
26
27 double a = Vp(0);
28 double b = -Vq(0);
29 double c = Vp(1);
30 double d = -Vq(1);
31
32 double delta = a * d - b * c;
33 if (delta == 0.0)
34 {
35 return false;
36 }
37
38 double tp = (d * pq(0) - b * pq(1)) / delta;
39
40 result << (1.0 - tp) * p1(0) + tp * p2(0),
41 (1.0 - tp) * p1(1) + tp * p2(1);
42
43 return true;
44 }
45
46} // anonymous namespace
47
48// -----------------------------------------------------------------------------
49
50void polyfem::mesh::clip_polygon_by_half_plane(const Eigen::MatrixXd &P_in,
51 const Eigen::RowVector2d &q1, const Eigen::RowVector2d &q2, Eigen::MatrixXd &P_out)
52{
53 using namespace GEO;
54 assert(P_in.cols() == 2);
55 std::vector<Eigen::RowVector2d> result;
56
57 if (P_in.rows() == 0)
58 {
59 P_out.resize(0, 2);
60 return;
61 }
62
63 if (P_in.rows() == 1)
64 {
65 if (point_is_in_half_plane(P_in.row(0), q1, q2))
66 {
67 P_out.resize(1, 2);
68 P_out << P_in.row(0);
69 }
70 else
71 {
72 P_out.resize(0, 2);
73 }
74 return;
75 }
76
77 Eigen::RowVector2d prev_p = P_in.row(P_in.rows() - 1);
78 Sign prev_status = point_is_in_half_plane(prev_p, q1, q2);
79
80 for (unsigned int i = 0; i < P_in.rows(); ++i)
81 {
82 Eigen::RowVector2d p = P_in.row(i);
83 Sign status = point_is_in_half_plane(p, q1, q2);
84 if (status != prev_status && status != ZERO && prev_status != ZERO)
85 {
86 Eigen::RowVector2d intersect;
87 if (intersect_segments(prev_p, p, q1, q2, intersect))
88 {
89 result.push_back(intersect);
90 }
91 }
92
93 switch (status)
94 {
95 case NEGATIVE:
96 break;
97 case ZERO:
98 result.push_back(p);
99 break;
100 case POSITIVE:
101 result.push_back(p);
102 break;
103 default:
104 break;
105 }
106
107 prev_p = p;
108 prev_status = status;
109 }
110
111 P_out.resize((int)result.size(), 2);
112 for (size_t i = 0; i < result.size(); ++i)
113 {
114 P_out.row((int)i) = result[i];
115 }
116}
117
119
120void polyfem::mesh::compute_visibility_kernel(const Eigen::MatrixXd &IV, Eigen::MatrixXd &OV)
121{
122 assert(IV.cols() == 2 || IV.cols() == 3);
123
124 // 1) Start from the bounding box of the input points
125 Eigen::MatrixXd src, dst;
126 const auto &minV = IV.colwise().minCoeff().array();
127 const auto &maxV = IV.colwise().maxCoeff().array();
128 src.resize(4, 2);
129 src.row(0) << minV(0), minV(1);
130 src.row(1) << maxV(0), minV(1);
131 src.row(2) << maxV(0), maxV(1);
132 src.row(3) << minV(0), maxV(1);
133 // std::cout << IV << std::endl;
134 // std::cout << minV << ' ' << maxV << std::endl;
135
136 // 2) Clip by half planes until we are left with the kernel
137 for (unsigned int i = 0; i < IV.rows(); ++i)
138 {
139 unsigned int j = ((i + 1) % (int)IV.rows());
140 const Eigen::RowVector2d &q1 = IV.row(i).head<2>();
141 const Eigen::RowVector2d &q2 = IV.row(j).head<2>();
142 clip_polygon_by_half_plane(src, q1, q2, dst);
143 std::swap(src, dst);
144 }
145 OV = src;
146}
147
149
150bool polyfem::mesh::is_star_shaped(const Eigen::MatrixXd &IV, Eigen::RowVector3d &bary)
151{
152 Eigen::MatrixXd OV;
154 if (OV.rows() == 0)
155 {
156 return false;
157 }
158 else
159 {
160 Eigen::MatrixXi F(1, OV.rows());
161 for (int i = 0; i < OV.rows(); ++i)
162 {
163 F(0, i) = i;
164 }
165 Eigen::MatrixXd BC;
166 igl::barycenter(OV, F, BC);
167 // std::cout << BC.rows() << 'x' << BC.cols() << std::endl;
168 // std::cout << BC << std::endl;
169 int n = std::min(3, (int)BC.cols());
170 bary.setZero();
171 bary.head(n) = BC.row(0).head(n);
172 return true;
173 }
174}
175
176// -----------------------------------------------------------------------------
177
178void polyfem::mesh::offset_polygon(const Eigen::MatrixXd &IV, Eigen::MatrixXd &OV, double eps)
179{
180#ifdef POLYFEM_WITH_CLIPPER
181 using namespace ClipperLib;
182 using namespace polyfem::utils;
183
184 // Convert input polygon to integer grid
185 ClipperOffset co;
186 co.AddPath(PolygonClipping::toClipperPolygon(IV), jtSquare, etClosedPolygon);
187
188 // Compute offset in the integer grid
189 Paths solution;
190 co.Execute(solution, cInt(eps * DOUBLE_TO_INT_SCALE_FACTOR));
191 assert(solution.size() == 1);
192
193 // Convert back to double
194 OV = PolygonClipping::fromClipperPolygon(solution.front());
195#else
196 throw "Compile with clipper!";
197#endif
198}
199
201
202namespace
203{
204
205 typedef Eigen::RowVector2d Vec2d;
206
207 double inline det(Vec2d u, Vec2d v)
208 {
209 return u[0] * v[1] - u[1] * v[0];
210 }
211
212 // Return true iff [a,b] intersects [c,d], and store the intersection in ans
213 bool intersect_segment(const Vec2d &a, const Vec2d &b, Vec2d c, Vec2d d, Vec2d &ans)
214 {
215 const double eps = 1e-10; // small epsilon for numerical precision
216 double x = det(c - a, d - c);
217 double y = det(b - a, a - c);
218 double z = det(b - a, d - c);
219 if (std::abs(z) < eps || x * z < 0 || x * z > z * z || y * z < 0 || y * z > z * z)
220 return false;
221 ans = c + (d - c) * y / z;
222 return true;
223 }
224
225 bool is_point_inside(const Eigen::MatrixXd &poly, const Vec2d &outside, const Vec2d &query)
226 {
227 int n = poly.rows();
228 bool tmp, ans = false;
229 for (long i = 0; i < poly.rows(); ++i)
230 {
231 Vec2d m; // Coordinates of intersection point
232 tmp = intersect_segment(query, outside, poly.row(i), poly.row((i + 1) % n), m);
233 ans = (ans != tmp);
234 }
235 return ans;
236 }
237
238} // anonymous namespace
239
240// -----------------------------------------------------------------------------
241
242int polyfem::mesh::is_inside(const Eigen::MatrixXd &IV, const Eigen::MatrixXd &Q, std::vector<bool> &inside)
243{
244 assert(IV.cols() == 2);
245 Eigen::RowVector2d minV = IV.colwise().minCoeff().array();
246 Eigen::RowVector2d maxV = IV.colwise().maxCoeff().array();
247 Eigen::RowVector2d center = 0.5 * (maxV + minV);
248 Eigen::RowVector2d outside(2.0 * minV(0) - center(0), center(1));
249 inside.resize(Q.rows());
250 int num_inside = 0;
251 for (long i = 0; i < Q.rows(); ++i)
252 {
253 inside[i] = is_point_inside(IV, outside, Q.row(i));
254 if (inside[i])
255 {
256 ++num_inside;
257 }
258 }
259 return num_inside;
260}
261
263
264void polyfem::mesh::sample_polygon(const Eigen::MatrixXd &poly, int num_samples, Eigen::MatrixXd &S)
265{
266 assert(poly.rows() >= 2);
267 int n = poly.rows();
268
269 auto length = [&](int i) {
270 return (poly.row(i) - poly.row((i + 1) % n)).norm();
271 };
272
273 // Step 1: compute starting edge + total length of the polygon
274 int i0 = 0;
275 double max_length = 0;
276 double total_length = 0;
277 for (int i = 0; i < n; ++i)
278 {
279 double len = length(i);
280 total_length += len;
281 if (len > max_length)
282 {
283 max_length = len;
284 i0 = i;
285 }
286 }
287
288 // Step 2: place a sample at regular intervals along the polygon,
289 // starting from the middle of edge i0
290 S.resize(num_samples, poly.cols());
291 double spacing = total_length / num_samples; // sampling distance
292 double offset = length(i0) / 2.0; // distance from first sample to vertex i0
293 double distance_to_next = length(i0);
294 for (int s = 0, i = i0; s < num_samples; ++s)
295 {
296 double distance_to_sample = s * spacing + offset; // next sample length
297 while (distance_to_sample > distance_to_next)
298 {
299 i = (i + 1) % n;
300 distance_to_next += length(i);
301 }
302 double t = (distance_to_next - distance_to_sample) / length(i);
303 S.row(s) = t * poly.row(i) + (1.0 - t) * poly.row((i + 1) % n);
304 }
305}
int y
int z
int x
list tmp
Definition p_bases.py:339
void clip_polygon_by_half_plane(const Eigen::MatrixXd &P, const Eigen::RowVector2d &q1, const Eigen::RowVector2d &q2, Eigen::MatrixXd &result)
Clip a polygon by a half-plane.
void sample_polygon(const Eigen::MatrixXd &IV, int num_samples, Eigen::MatrixXd &S)
Sample points on a polygon, evenly spaced from each other.
void offset_polygon(const Eigen::MatrixXd &IV, Eigen::MatrixXd &OV, double eps)
Compute offset polygon.
void compute_visibility_kernel(const Eigen::MatrixXd &IV, Eigen::MatrixXd &OV)
Determine the kernel of the given polygon.
bool is_star_shaped(const Eigen::MatrixXd &IV, Eigen::RowVector3d &bary)
Determine whether a polygon is star-shaped or not.
int is_inside(const Eigen::MatrixXd &IV, const Eigen::MatrixXd &Q, std::vector< bool > &inside)
Compute whether points are inside a polygon.