/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/geometry/concavePolygon2D/clippingAndMerging/PolygonClippingAndMerging.java

https://github.com/ihmcrobotics/ihmc-open-robotics-software · Java · 359 lines · 280 code · 66 blank · 13 comment · 56 complexity · 41c7aa27c87c175d43fbc4547c31126f MD5 · raw file

  1. package us.ihmc.robotics.geometry.concavePolygon2D.clippingAndMerging;
  2. import us.ihmc.log.LogTools;
  3. import us.ihmc.robotics.geometry.concavePolygon2D.*;
  4. import java.util.ArrayList;
  5. import java.util.Collection;
  6. import java.util.List;
  7. public class PolygonClippingAndMerging
  8. {
  9. public static void removeHolesFromList(List<ConcavePolygon2DBasics> regionsToFilter)
  10. {
  11. int i = 0;
  12. // first, remove all the polygons contained in another polygon
  13. while (i < regionsToFilter.size())
  14. {
  15. ConcavePolygon2DBasics polygonA = regionsToFilter.get(i);
  16. boolean shouldRemoveA = false;
  17. int j = 0;
  18. while (j < regionsToFilter.size())
  19. {
  20. if (i == j)
  21. {
  22. j++;
  23. continue;
  24. }
  25. ConcavePolygon2DBasics polygonB = regionsToFilter.get(j);
  26. if (GeometryPolygonTools.isPolygonInsideOtherPolygon(polygonB, polygonA))
  27. {
  28. regionsToFilter.remove(j);
  29. break;
  30. }
  31. if (GeometryPolygonTools.isPolygonInsideOtherPolygon(polygonA, polygonB))
  32. {
  33. shouldRemoveA = true;
  34. break;
  35. }
  36. j++;
  37. }
  38. if (shouldRemoveA)
  39. regionsToFilter.remove(i);
  40. else
  41. i++;
  42. }
  43. }
  44. public static void mergeAllPossible(List<ConcavePolygon2DBasics> regionsToMerge)
  45. {
  46. removeHolesFromList(regionsToMerge);
  47. int i = 0;
  48. // don't need to iterate on the last one
  49. while (i < regionsToMerge.size() - 1)
  50. {
  51. int j = i + 1;
  52. while (j < regionsToMerge.size())
  53. {
  54. ConcavePolygon2DBasics polygonA = regionsToMerge.get(i);
  55. ConcavePolygon2DBasics polygonB = regionsToMerge.get(j);
  56. if (GeometryPolygonTools.doPolygonsIntersect(polygonA, polygonB))
  57. {
  58. ConcavePolygon2D newPolygon = new ConcavePolygon2D();
  59. try
  60. {
  61. PolygonClippingAndMerging.merge(polygonA, polygonB, newPolygon);
  62. }
  63. catch (ComplexPolygonException exception)
  64. {
  65. try
  66. {
  67. // sometimes, because of errors, the numerics screw up and we have to try again.
  68. PolygonClippingAndMerging.merge(polygonA, polygonB, newPolygon);
  69. }
  70. catch (ComplexPolygonException repeatException)
  71. {
  72. j++;
  73. LogTools.info("Caught an error when trying to merge.");
  74. continue;
  75. }
  76. }
  77. regionsToMerge.set(i, newPolygon);
  78. regionsToMerge.remove(j);
  79. // reset the search, as we modified the first polygon
  80. j = i + 1;
  81. }
  82. else
  83. {
  84. j++;
  85. }
  86. }
  87. i++;
  88. }
  89. }
  90. // TODO clean this up so that it works when the merge makes a hole.
  91. public static void merge(ConcavePolygon2DReadOnly polygonA, ConcavePolygon2DReadOnly polygonB, ConcavePolygon2DBasics mergedPolygon)
  92. {
  93. List<ConcavePolygon2DBasics> partialListOfHoles = new ArrayList<>();
  94. if (GeometryPolygonTools.isPolygonInsideOtherPolygon(polygonA, polygonB))
  95. {
  96. // FIXME this might have a hole
  97. mergedPolygon.set(polygonB);
  98. return;
  99. }
  100. else if (GeometryPolygonTools.isPolygonInsideOtherPolygon(polygonB, polygonA))
  101. {
  102. // FIXME this might have a hole
  103. mergedPolygon.set(polygonA);
  104. return;
  105. }
  106. else if (polygonA.epsilonEquals(polygonB, 1e-5))
  107. {
  108. mergedPolygon.set(polygonA);
  109. return;
  110. }
  111. ConcavePolygon2DClippingTools.LinkedPointList polygonAList = ConcavePolygon2DClippingTools.createLinkedPointList(polygonA);
  112. ConcavePolygon2DClippingTools.LinkedPointList polygonBList = ConcavePolygon2DClippingTools.createLinkedPointList(polygonB);
  113. ConcavePolygon2DClippingTools.insertIntersectionsIntoList(polygonAList, polygonB);
  114. ConcavePolygon2DClippingTools.insertIntersectionsIntoList(polygonBList, polygonA);
  115. ConcavePolygon2DClippingTools.linkSharedVertices(polygonAList, polygonBList, 5e-3);
  116. Collection<ConcavePolygon2DClippingTools.LinkedPoint> unassignedAPoints = polygonAList.getPointsCopy();
  117. Collection<ConcavePolygon2DClippingTools.LinkedPoint> unassignedBPoints = polygonBList.getPointsCopy();
  118. ConcavePolygon2DClippingTools.LinkedPointListHolder listHolder = new ConcavePolygon2DClippingTools.LinkedPointListHolder(unassignedAPoints, unassignedBPoints);
  119. ConcavePolygon2DClippingTools.LinkedPoint startPoint = findVertexOutsideOfPolygon(polygonB, unassignedAPoints);
  120. if (startPoint == null)
  121. {
  122. startPoint = findVertexOutsideOfPolygon(polygonA, unassignedBPoints);
  123. }
  124. mergedPolygon.clear();
  125. mergedPolygon.update();
  126. while (startPoint != null)
  127. {
  128. ConcavePolygon2D polygon = new ConcavePolygon2D();
  129. boolean failed = !walkAlongEdgeOfPolygon(startPoint, listHolder, polygon, PolygonClippingAndMerging::shouldSwitchWhenMerging);
  130. if (failed)
  131. {
  132. mergedPolygon.set(polygonA);
  133. return;
  134. }
  135. // We want the biggest of the polygons; that is, we want the outer most perimeter polygon.
  136. if (Double.isNaN(mergedPolygon.getArea()))
  137. {
  138. mergedPolygon.set(polygon);
  139. }
  140. else if (polygon.getArea() > mergedPolygon.getArea())
  141. {
  142. partialListOfHoles.add(new ConcavePolygon2D(mergedPolygon));
  143. mergedPolygon.set(polygon);
  144. }
  145. else
  146. {
  147. partialListOfHoles.add(polygon);
  148. }
  149. // check to see if there are any points outside the merged polygon on either list
  150. startPoint = findVertexOutsideOfPolygon(mergedPolygon, unassignedAPoints);
  151. if (startPoint == null)
  152. {
  153. startPoint = findVertexOutsideOfPolygon(mergedPolygon, unassignedBPoints);
  154. }
  155. }
  156. }
  157. public static List<ConcavePolygon2DBasics> removeAreaInsideClip(ConcavePolygon2DReadOnly clippingPolygon, ConcavePolygon2DReadOnly polygonToClip)
  158. {
  159. List<ConcavePolygon2DBasics> clippedPolygonsToReturn = new ArrayList<>();
  160. if (GeometryPolygonTools.isPolygonInsideOtherPolygon(clippingPolygon, polygonToClip))
  161. {
  162. ConcavePolygon2D polygonToReturn = new ConcavePolygon2D(clippingPolygon);
  163. clippedPolygonsToReturn.add(polygonToReturn);
  164. return clippedPolygonsToReturn;
  165. }
  166. else if (!GeometryPolygonTools.doPolygonsIntersect(clippingPolygon, polygonToClip))
  167. {
  168. ConcavePolygon2D polygonToReturn = new ConcavePolygon2D(polygonToClip);
  169. clippedPolygonsToReturn.add(polygonToReturn);
  170. return clippedPolygonsToReturn;
  171. }
  172. ConcavePolygon2DClippingTools.LinkedPointList clippingPolygonList = ConcavePolygon2DClippingTools.createLinkedPointList(clippingPolygon);
  173. ConcavePolygon2DClippingTools.LinkedPointList polygonToClipList = ConcavePolygon2DClippingTools.createLinkedPointList(polygonToClip);
  174. ConcavePolygon2DClippingTools.insertIntersectionsIntoList(polygonToClipList, clippingPolygon);
  175. ConcavePolygon2DClippingTools.insertIntersectionsIntoList(clippingPolygonList, polygonToClip);
  176. ConcavePolygon2DClippingTools.linkSharedVertices(polygonToClipList, clippingPolygonList, 5e-3);
  177. // gotta make the clipping list is counter clockwise
  178. clippingPolygonList.reverseOrder();
  179. Collection<ConcavePolygon2DClippingTools.LinkedPoint> unassignedToClipPoints = polygonToClipList.getPointsCopy();
  180. Collection<ConcavePolygon2DClippingTools.LinkedPoint> unassignedClippingPoints = clippingPolygonList.getPointsCopy();
  181. ConcavePolygon2DClippingTools.LinkedPointListHolder listHolder = new ConcavePolygon2DClippingTools.LinkedPointListHolder(unassignedClippingPoints, unassignedToClipPoints);
  182. ConcavePolygon2DClippingTools.LinkedPoint startPoint = findVertexOutsideOfPolygon(clippingPolygon, unassignedToClipPoints);
  183. // There aren't any that are outside from the start. Instead, find a shared vertex, and make sure it's an "outgoing edge"
  184. if (startPoint == null)
  185. {
  186. for (ConcavePolygon2DClippingTools.LinkedPoint point : unassignedToClipPoints)
  187. {
  188. if (!point.isPointAfterInsideOther() && point.isPointBeforeInsideOther())
  189. {
  190. startPoint = point;
  191. break;
  192. }
  193. }
  194. }
  195. while (startPoint != null)
  196. {
  197. ConcavePolygon2D clippedPolygon = new ConcavePolygon2D();
  198. boolean failed = !walkAlongEdgeOfPolygon(startPoint, listHolder, clippedPolygon, PolygonClippingAndMerging::shouldSwitchWhenClipping);
  199. clippedPolygonsToReturn.add(clippedPolygon);
  200. if (failed)
  201. {
  202. clippedPolygon.set(polygonToClip);
  203. return clippedPolygonsToReturn;
  204. }
  205. startPoint = findVertexOutsideOfPolygon(clippingPolygon, unassignedToClipPoints);
  206. }
  207. return clippedPolygonsToReturn;
  208. }
  209. static boolean walkAlongEdgeOfPolygon(ConcavePolygon2DClippingTools.LinkedPoint startVertex, ConcavePolygon2DClippingTools.LinkedPointListHolder listHolder, ConcavePolygon2DBasics polygonToPack, SwitchingFunction switchFunction)
  210. {
  211. ConcavePolygon2DClippingTools.LinkedPoint linkedPoint = startVertex;
  212. ConcavePolygon2DClippingTools.LinkedPoint previousPoint = linkedPoint;
  213. int maxPoints = listHolder.getNumberOfPoints();
  214. polygonToPack.addVertex(linkedPoint.getPoint());
  215. boolean isOnOtherList = false;
  216. int counter = 0;
  217. while (counter < maxPoints)
  218. {
  219. linkedPoint = linkedPoint.getSuccessor();
  220. listHolder.removePoint(previousPoint);
  221. if (linkedPoint.getPoint().epsilonEquals(startVertex.getPoint(), 1e-5))
  222. break;
  223. polygonToPack.addVertex(linkedPoint.getPoint());
  224. boolean shouldSwitch = switchFunction.apply(linkedPoint, isOnOtherList);
  225. if (shouldSwitch)
  226. {
  227. // we're switching polygons
  228. isOnOtherList = !isOnOtherList;
  229. previousPoint = linkedPoint;
  230. linkedPoint = linkedPoint.getPointOnOtherList();
  231. if (linkedPoint == null)
  232. throw new RuntimeException("Was unable to find the intersection point in the other list.");
  233. }
  234. else
  235. {
  236. previousPoint = linkedPoint;
  237. }
  238. counter++;
  239. }
  240. if (counter >= maxPoints)
  241. {
  242. return false;
  243. }
  244. // throw new RuntimeException("Bad.");
  245. polygonToPack.update();
  246. return true;
  247. }
  248. private static boolean shouldSwitch(ConcavePolygon2DClippingTools.LinkedPoint linkedPoint)
  249. {
  250. return linkedPoint.isPointAfterInsideOther() != linkedPoint.isPointBeforeInsideOther();
  251. }
  252. @FunctionalInterface
  253. private interface SwitchingFunction
  254. {
  255. boolean apply(ConcavePolygon2DClippingTools.LinkedPoint point, boolean isOnOtherList);
  256. }
  257. private static boolean shouldSwitchWhenMerging(ConcavePolygon2DClippingTools.LinkedPoint linkedPoint, boolean isOnOtherList)
  258. {
  259. boolean shouldSwitch = shouldSwitch(linkedPoint);
  260. boolean outgoingPoint = linkedPoint.isPointBeforeInsideOther() && !linkedPoint.isPointAfterInsideOther();
  261. shouldSwitch |= linkedPoint.isLinkedToOtherList() && shouldSwitch(linkedPoint.getPointOnOtherList());
  262. if (!linkedPoint.isPointAfterInsideOther() && !linkedPoint.isPointBeforeInsideOther())
  263. shouldSwitch = false;
  264. if (outgoingPoint)
  265. shouldSwitch = false;
  266. return shouldSwitch;
  267. }
  268. private static boolean shouldSwitchWhenClipping(ConcavePolygon2DClippingTools.LinkedPoint linkedPoint, boolean isOnOtherList)
  269. {
  270. boolean shouldSwitch = shouldSwitch(linkedPoint);
  271. shouldSwitch |= linkedPoint.isLinkedToOtherList() && shouldSwitch(linkedPoint.getPointOnOtherList());
  272. if (linkedPoint.isLinkedToOtherList())
  273. {
  274. ConcavePolygon2DClippingTools.LinkedPoint clippingPoint = isOnOtherList ? linkedPoint : linkedPoint.getPointOnOtherList();
  275. ConcavePolygon2DClippingTools.LinkedPoint clippedPoint = isOnOtherList ? linkedPoint.getPointOnOtherList() : linkedPoint;
  276. if (clippingPoint.isPointBeforeInsideOther() && clippingPoint.isPointAfterInsideOther() && (!clippedPoint.isPointBeforeInsideOther()
  277. && !clippedPoint.isPointAfterInsideOther()))
  278. {
  279. shouldSwitch = true;
  280. }
  281. }
  282. return shouldSwitch;
  283. }
  284. private static ConcavePolygon2DClippingTools.LinkedPoint findVertexOutsideOfPolygon(ConcavePolygon2DReadOnly polygon, Collection<ConcavePolygon2DClippingTools.LinkedPoint> points)
  285. {
  286. for (ConcavePolygon2DClippingTools.LinkedPoint point : points)
  287. {
  288. if (!polygon.isPointInsideEpsilon(point.getPoint(), 1e-5))
  289. return point;
  290. }
  291. return null;
  292. }
  293. }