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