VPTissue Reference Manual
PolygonUtils.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2011-2016 Universiteit Antwerpen
3  *
4  * Licensed under the EUPL, Version 1.1 or as soon they will be approved by
5  * the European Commission - subsequent versions of the EUPL (the "Licence");
6  * You may not use this work except in compliance with the Licence.
7  * You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl5
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the Licence is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the Licence for the specific language governing
13  * permissions and limitations under the Licence.
14  */
20 #include "PolygonUtils.h"
21 
23 
24 #include <QPainterPath>
25 #include <QVector>
26 #include <cassert>
27 #include <cmath>
28 #include <list>
29 #include <map>
30 
31 using namespace SimPT_Sim::Container;
32 
33 namespace SimPT_Editor {
34 
35 bool PolygonUtils::IsClockwise(const QPolygonF& polygon)
36 {
37  //The algorithm used to determine this has been adapted from:
38  // [http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order]
39 
40  assert(!polygon.isClosed() && "The polygon isn't open.");
41  assert(polygon.size() >= 3 && "The polygon isn't valid.");
42  assert(IsSimplePolygon(polygon) && "The polygon isn't simple.");
43 
44  double double_area = 0;
45  auto polygon_std = polygon.toStdVector();
46  auto cit = make_const_circular(polygon_std);
47  do {
48  double_area += (next(cit)->x() - cit->x()) * (next(cit)->y() + cit->y());
49  } while (++cit != polygon_std.begin());
50 
51  return (double_area > 0);
52 }
53 
54 bool PolygonUtils::IsSimplePolygon(const QPolygonF& polygon)
55 {
56  assert(!polygon.isClosed() && "The polygon isn't open.");
57  assert(polygon.size() >= 3 && "The polygon isn't valid.");
58 
59  auto polygon_std = polygon.toStdVector();
60  auto cit = make_const_circular(polygon_std);
61 
62  std::list<QLineF> edges;
63  do {
64  edges.push_back(QLineF((*cit), *next(cit)));
65  } while (++cit != polygon_std.begin());
66 
67  for (auto e1 = edges.begin(); e1 != edges.end(); e1++) {
68  for (auto e2 = std::next(e1); e2 != edges.end(); e2++) {
69  QPointF intersection;
70  if (e1->intersect(*e2, &intersection) == QLineF::BoundedIntersection
71  && intersection != e1->p1()
72  && intersection != e1->p2()
73  && intersection != e2->p1()
74  && intersection != e2->p2()) {
75  return false;
76  }
77  }
78  }
79 
80  return true;
81 }
82 
83 QPolygonF PolygonUtils::OpenPolygon(const QPolygonF &polygon)
84 {
85  assert(polygon.size() >= 3 && "The polygon isn't valid.");
86 
87  if (polygon.isClosed()) {
88  QPolygonF opened = polygon;
89  opened.pop_back();
90  return opened;
91  } else {
92  return polygon;
93  }
94 }
95 
96 QPolygonF PolygonUtils::Counterclockwise(const QPolygonF& polygon)
97 {
98  assert(!polygon.isClosed() && "The polygon isn't open.");
99  assert(polygon.size() >= 3 && "The polygon isn't valid.");
100  assert(IsSimplePolygon(polygon) && "The polygon isn't simple.");
101 
102  if (PolygonUtils::IsClockwise(polygon)) {
103  QPolygonF reversed;
104  std::copy(polygon.begin(), polygon.end(), std::front_inserter(reversed));
105  return reversed;
106  } else {
107  return polygon;
108  }
109 }
110 
111 double PolygonUtils::CalculateArea(const QPolygonF& polygon)
112 {
113  assert(!polygon.isClosed() && "The polygon isn't open.");
114  assert(polygon.size() >= 3 && "The polygon isn't valid.");
115  assert(IsSimplePolygon(polygon) && "The polygon isn't simple.");
116 
117  double area = 0;
118 
119  auto polygonStd = polygon.toStdVector();
120  auto cit = make_const_circular(polygonStd);
121  do {
122  area += (*cit).x() * (*next(cit)).y() - (*next(cit)).x() * (*cit).y();
123  } while (++cit != polygonStd.begin());
124 
125  area /= 2;
126 
127  return fabs(area);
128 }
129 
130 std::list<QPolygonF> PolygonUtils::ClipPolygon(const QPolygonF& polygon1, const QPolygonF& polygon2)
131 {
132  assert(!polygon1.isClosed() && !polygon2.isClosed() && "The polygon aren't open.");
133  assert(polygon1.size() >= 3 && polygon2.size() >= 3 && "The polygon isn't valid.");
134  assert(IsSimplePolygon(polygon1) && IsSimplePolygon(polygon2) && "The polygon isn't simple.");
135 
136 
137 
138  QPainterPath path1;
139  path1.addPolygon(polygon1);
140  QPainterPath path2;
141  path2.addPolygon(polygon2);
142 
143  std::list<QPolygonF> subpathPolygons = path1.intersected(path2).toSubpathPolygons().toStdList();
144  std::transform(subpathPolygons.begin(), subpathPolygons.end(), subpathPolygons.begin(), &OpenPolygon);
145 
146 
147 
148  // Lambda to check whether to points are equal with a small perturbation.
149  auto is_perturbed = [](const QPointF& p1, const QPointF& p2) {
150  return fabs(p1.x() - p2.x()) < g_accuracy && fabs(p1.y() - p2.y()) < g_accuracy;
151  };
152 
153  // Lambda to return a perturbed line.
154  auto perturb_line = [](QLineF line) {
155  line.setLength(line.length() + g_accuracy);
156  QLineF reversed(line.p2(), line.p1());
157  reversed.setLength(reversed.length() + g_accuracy);
158  return QLineF(reversed.p2(), reversed.p1());
159  };
160 
161  // Lambda to check whether a given point is located on the given line.
162  auto point_on_line = [perturb_line](const QPointF& p, const QLineF& l) {
163  QLineF normal = l.normalVector();
164  QLineF perpendicular(p, normal.p2() - normal.p1() + p);
165 
166  QPointF intersection;
167  if (perturb_line(perpendicular).intersect(perturb_line(l), &intersection) == QLineF::BoundedIntersection) {
168  return QLineF(intersection, p).length() < g_accuracy;
169  }
170  else {
171  return false;
172  }
173  };
174 
175 
176 
177  //Add omitted (during intersection) points to the subpathPolygons.
178  for (auto polygon_it = subpathPolygons.begin(); polygon_it != subpathPolygons.end(); polygon_it++) {
179  std::list<QPointF> polygonStd = polygon_it->toList().toStdList();
180 
181  auto c_prev = [&polygonStd](const decltype(polygonStd.begin())& it) {
182  return (it == polygonStd.begin() ? std::prev(polygonStd.end()) : std::prev(it));
183  };
184 
185  //Can't use circular iterator for insertion at the start (probably due to the inheritance of prev).
186  for (auto cit = polygonStd.begin(); cit != polygonStd.end(); cit++) {
187  QLineF line(*c_prev(cit), *cit);
188  for (const QPointF& p : polygon1) {
189  if (point_on_line(p, line) && !is_perturbed(p, *c_prev(cit)) && !is_perturbed(p, *cit)) {
190  polygonStd.insert(cit, p);
191  cit--;
192  line = QLineF(*c_prev(cit), *cit);
193  }
194  }
195  for (const QPointF& p : polygon2) {
196  if (point_on_line(p, line) && !is_perturbed(p, *c_prev(cit)) && !is_perturbed(p, *cit)) {
197  polygonStd.insert(cit, p);
198  cit--;
199  line = QLineF(*c_prev(cit), *cit);
200  }
201  }
202  }
203  *polygon_it = QPolygonF::fromList(QList<QPointF>::fromStdList(polygonStd));
204  }
205 
206 
207 
208  //Check for floating-point errors.
209  for (QPolygonF& polygon : subpathPolygons) {
210  for (int i = 0; i < polygon.size(); i++) {
211  for (const QPointF& p2 : polygon1) {
212  if (is_perturbed(polygon[i], p2)) {
213  polygon.replace(i, p2);
214  }
215  }
216  for (const QPointF& p2 : polygon2) {
217  if (is_perturbed(polygon[i], p2)) {
218  polygon.replace(i, p2);
219  }
220  }
221  }
222  }
223 
224  return subpathPolygons;
225 }
226 
227 std::list<QPolygonF> PolygonUtils::SlicePolygon(const QPolygonF& polygon, QLineF cut)
228 {
229  assert(!polygon.isClosed() && "The polygon isn't open.");
230  assert(polygon.size() >= 3 && "The polygon isn't valid.");
231  assert(IsSimplePolygon(polygon) && "The polygon isn't simple.");
232  assert((!polygon.containsPoint(cut.p1(), Qt::OddEvenFill) || polygon.contains(cut.p1()))
233  && (!polygon.containsPoint(cut.p2(), Qt::OddEvenFill) || polygon.contains(cut.p2()))
234  && "One of the points of the cut is located inside the polygon.");
235  assert(cut.p1() != cut.p2() && "The cut isn't a line.");
236 
237 
238  QPolygonF initialPolygon;
239  std::list<QPointF> intersections;
240  auto edges = polygon.toStdVector();
241 
242 
243  // Lambda to check whether to points are equal with a small perturbation.
244  auto is_perturbed = [](const QPointF& p1, const QPointF& p2) {
245  return fabs(p1.x() - p2.x()) < g_accuracy && fabs(p1.y() - p2.y()) < g_accuracy;
246  };
247 
248  // Lambda to return a perturbed line.
249  auto perturb_line = [](QLineF line) {
250  line.setLength(line.length() + g_accuracy);
251  QLineF reversed(line.p2(), line.p1());
252  reversed.setLength(reversed.length() + g_accuracy);
253  return QLineF(reversed.p2(), reversed.p1());
254  };
255 
256  // Lambda to check whether a given point is located on the given line.
257  auto point_on_line = [perturb_line](const QPointF& p, const QLineF& l) {
258  QLineF normal = l.normalVector();
259  QLineF perpendicular(p, normal.p2() - normal.p1() + p);
260 
261  QPointF intersection;
262  if (perturb_line(perpendicular).intersect(perturb_line(l), &intersection) == QLineF::BoundedIntersection) {
263  return QLineF(intersection, p).length() < g_accuracy;
264  }
265  else {
266  return false;
267  }
268  };
269 
270  // Lambda to check whether two given lines fall on top of each other (with one of the lines completely overlapping the other).
271  auto line_on_line = [point_on_line](const QLineF l1, const QLineF l2) {
272  return ((point_on_line(l1.p1(), l2) && point_on_line(l1.p2(), l2)) || (point_on_line(l2.p1(), l1) && point_on_line(l2.p2(), l1)));
273  };
274 
275  // Find the intersections with the cut.
276  auto cit = make_const_circular(edges);
277  do {
278  QPointF intersection;
279  QLineF edge(*cit, *next(cit));
280  bool onLine = line_on_line(cut, edge);
281 
282  initialPolygon.append(*cit);
283 
284  if (perturb_line(cut).intersect(perturb_line(edge), &intersection) == QLineF::BoundedIntersection
285  && !onLine) {
286  //The last check is necessary, because the intersection points could be *prev(cit) and *next(cit), so *cit itself won't be.
287 
288 
289  if (is_perturbed(*cit, intersection)) {
290  intersection = *cit;
291  }
292  else if (is_perturbed(*next(cit), intersection)) {
293  intersection = *next(cit);
294  }
295  else {
296  initialPolygon.append(intersection);
297  }
298 
299  if (intersection != *next(cit)) {
300  // Only consider *cit as an intersection, as *next(cit) will be found in the next iteration.
301  intersections.push_back(intersection);
302  }
303  }
304  else if (onLine) {
305  //Necessary for polygon partition check in the iteration over the intersections.
306  intersections.push_back(*cit);
307  }
308  } while (++cit != edges.begin());
309 
310  // If there are no intersections (or one), return the original polygon.
311  if (intersections.size() <= 1) {
312  return std::list<QPolygonF>({polygon});
313  }
314 
315  // Sort the intersection points by their distance to the first point in the cut.
316  auto cmp = [cut](const QPointF& p1, const QPointF& p2) {
317  return pow(cut.p1().x() - p1.x(), 2) + pow(cut.p1().y() - p1.y(), 2)
318  < pow(cut.p1().x() - p2.x(), 2) + pow(cut.p1().y() - p2.y(), 2);
319  };
320  intersections.sort(cmp);
321 
322 
323  // Create the subpolygons.
324  std::list<QPolygonF> subpolygons({initialPolygon});
325  for (auto it = intersections.begin(); it != std::prev(intersections.end()); it++) {
326  QLineF line(*it, *std::next(it));
327 
328  //Check whether this part of the cut is part of the polygon.
329  //In this case, do nothing with it, as it's not really separating two polygons.
330  auto firstPoint = std::find(edges.begin(), edges.end(), line.p1());
331  if (firstPoint != edges.end()) {
332  std::rotate(edges.begin(), firstPoint, edges.end());
333  if (*std::next(edges.begin()) == line.p2() || *std::prev(edges.end()) == line.p2()) {
334  continue;
335  }
336  }
337 
338  //If the center of this part of the cut falls outside of the polygon, do nothing,
339  //as the cut goes outside of the polygon.
340  QPointF center = (line.p1() + line.p2()) / 2;
341  if (!polygon.containsPoint(center, Qt::OddEvenFill) && !polygon.contains(center)) {
342  continue;
343  }
344 
345 
346  auto currentPolygon = std::find_if(subpolygons.begin(), subpolygons.end(), [line](const QPolygonF& p){
347  auto first = std::find(p.begin(), p.end(), line.p1());
348  auto second = std::find(p.begin(), p.end(), line.p2());
349  return first != p.end() && second != p.end();
350  });
351 
352 
353  QPolygonF polygon1;
354  QPolygonF polygon2;
355  QPolygonF* currentAppending = &polygon1;
356  for (const QPointF& p : *currentPolygon) {
357  currentAppending->append(p);
358 
359  if (p == line.p1() || p == line.p2()) {
360  currentAppending = (currentAppending == &polygon1 ? &polygon2 : &polygon1);
361  currentAppending->append(p);
362  }
363  }
364  subpolygons.erase(currentPolygon);
365  subpolygons.push_back(polygon1);
366  subpolygons.push_back(polygon2);
367  }
368 
369 
370  // If there are no (useful) intersections, return the original polygon.
371  if (subpolygons.size() == 0) {
372  return std::list<QPolygonF>({polygon});
373  }
374  else {
375  return subpolygons;
376  }
377 }
378 
379 int PolygonUtils::Turn(const QLineF& line1, const QLineF& line2)
380 {
381  QPointF u = line1.p2() - line1.p1();
382  QPointF v = line2.p2() - line2.p1();
383  double z = u.x() * v.y() - u.y() * v.x();
384  return (z > 0) - (z < 0); // Sign of z
385 }
386 
387 } // namespace
Namespace for SimPT tissue editor package.
Definition: Cell.h:32
Combo header for circular iterator.
Namespace for container related classes.
CircularIterator< T > next(CircularIterator< T > i)
Helper yields the position the iterator would have if moved forward (in circular fashion) by 1 positi...
ConstCircularIterator< typename T::const_iterator > make_const_circular(const T &c)
Helper produces const circular iterator whose range corresponds to the begin and end iterators of a c...
Interface for PolygonUtils.
CircularIterator< T > prev(CircularIterator< T > i)
Helper yields the position the iterator would have if moved backward (in circular fashion) by 1 posit...