|
2 | 2 |
|
3 | 3 | import static micycle.pgs.PGS_Conversion.fromPShape;
|
4 | 4 | import static micycle.pgs.PGS_Conversion.toPShape;
|
| 5 | +import static processing.core.PConstants.GROUP; |
5 | 6 | import static micycle.pgs.PGS_Construction.createEllipse;
|
6 | 7 |
|
7 | 8 | import java.util.ArrayList;
|
|
15 | 16 | import org.locationtech.jts.algorithm.MinimumDiameter;
|
16 | 17 | import org.locationtech.jts.algorithm.construct.LargestEmptyCircle;
|
17 | 18 | import org.locationtech.jts.algorithm.construct.MaximumInscribedCircle;
|
| 19 | +import org.locationtech.jts.algorithm.locate.IndexedPointInAreaLocator; |
18 | 20 | import org.locationtech.jts.geom.Coordinate;
|
| 21 | +import org.locationtech.jts.geom.Envelope; |
19 | 22 | import org.locationtech.jts.geom.Geometry;
|
20 | 23 | import org.locationtech.jts.geom.LineString;
|
| 24 | +import org.locationtech.jts.geom.Location; |
21 | 25 | import org.locationtech.jts.geom.Point;
|
22 | 26 | import org.locationtech.jts.geom.Polygon;
|
23 | 27 | import org.locationtech.jts.operation.distance.DistanceOp;
|
| 28 | +import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; |
24 | 29 | import org.locationtech.jts.util.GeometricShapeFactory;
|
25 | 30 |
|
| 31 | +import almadina.rectpacking.RBPSolution; |
| 32 | +import almadina.rectpacking.Rect; |
| 33 | +import almadina.rectpacking.RectPacking.PackingHeuristic; |
26 | 34 | import micycle.pgs.color.RGB;
|
27 | 35 | import micycle.pgs.commons.ClosestPointPair;
|
28 | 36 | import micycle.pgs.commons.FarthestPointPair;
|
@@ -136,6 +144,71 @@ public static PShape maximumInscribedAARectangle(PShape shape, boolean fast) {
|
136 | 144 | return toPShape(shapeFactory.createRectangle());
|
137 | 145 | }
|
138 | 146 |
|
| 147 | + /** |
| 148 | + * Finds the largest area <i>perimeter square</i> of the input. A <i>perimeter |
| 149 | + * square</i> is a square whose 4 vertices each lie on the perimeter of the |
| 150 | + * input shape (within the given tolerance). |
| 151 | + * <p> |
| 152 | + * If the input is convex, the output forms a fully inscribed square; if the |
| 153 | + * input is concave the output is not necessarily inscribed. |
| 154 | + * <p> |
| 155 | + * Does not respect holes (for now...). |
| 156 | + * |
| 157 | + * @param shape |
| 158 | + * @param tolerance a value of 2-5 is usually suitable |
| 159 | + * @return shape representing the maximum square |
| 160 | + * @since 1.3.1 |
| 161 | + */ |
| 162 | + public static PShape maximumPerimeterSquare(PShape shape, double tolerance) { |
| 163 | + shape = PGS_Morphology.simplify(shape, tolerance / 2); |
| 164 | + final Polygon p = (Polygon) PGS_Conversion.fromPShape(shape); |
| 165 | + Geometry buffer = p.getExteriorRing().buffer(tolerance / 2, 4); |
| 166 | + final Envelope e = buffer.getEnvelopeInternal(); |
| 167 | + buffer = DouglasPeuckerSimplifier.simplify(buffer, tolerance / 2); |
| 168 | + final IndexedPointInAreaLocator pia = new IndexedPointInAreaLocator(buffer); |
| 169 | + shape = PGS_Processing.densify(shape, Math.max(0.5, tolerance)); // min of 0.5 |
| 170 | + final List<PVector> points = PGS_Conversion.toPVector(shape); |
| 171 | + |
| 172 | + double maxDiagonal = 0; |
| 173 | + PVector[] maxAreaVertices = new PVector[0]; |
| 174 | + for (int i = 0; i < points.size(); i++) { |
| 175 | + for (int j = 0; j < points.size(); j++) { |
| 176 | + final PVector a = points.get(i); |
| 177 | + final PVector b = points.get(j); |
| 178 | + double dist = PGS.distanceSq(a, b); |
| 179 | + |
| 180 | + if (dist < maxDiagonal) { |
| 181 | + continue; |
| 182 | + } |
| 183 | + |
| 184 | + final PVector m = PVector.add(a, b).div(2); |
| 185 | + final PVector n = new PVector(b.y - a.y, a.x - b.x).div(2); |
| 186 | + final PVector c = PVector.sub(m, n); |
| 187 | + |
| 188 | + final PVector d = PVector.add(m, n); |
| 189 | + // do envelope checks first -- slightly faster |
| 190 | + if (within(c, e) && within(d, e)) { |
| 191 | + if (pia.locate(new Coordinate(c.x, c.y)) != Location.EXTERIOR) { |
| 192 | + if (pia.locate(new Coordinate(d.x, d.y)) != Location.EXTERIOR) { |
| 193 | + maxDiagonal = dist; |
| 194 | + maxAreaVertices = new PVector[] { a, c, b, d, a }; // closed vertices |
| 195 | + } |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | + } |
| 200 | + |
| 201 | + PShape out = PGS_Conversion.fromPVector(maxAreaVertices); |
| 202 | + out.setStroke(true); |
| 203 | + out.setStroke(micycle.pgs.color.RGB.PINK); |
| 204 | + out.setStrokeWeight(4); |
| 205 | + return out; |
| 206 | + } |
| 207 | + |
| 208 | + private static boolean within(PVector p, Envelope rect) { |
| 209 | + return p.x >= rect.getMinX() && p.x <= rect.getMaxX() && p.y <= rect.getMaxY() && p.y >= rect.getMinY(); |
| 210 | + } |
| 211 | + |
139 | 212 | /**
|
140 | 213 | * Computes the Minimum Bounding Circle (MBC) for the points in a Geometry. The
|
141 | 214 | * MBC is the smallest circle which covers all the vertices of the input shape
|
@@ -245,6 +318,74 @@ public static PShape largestEmptyCircle(PShape obstacles, double tolerance) {
|
245 | 318 | Polygon circle = createEllipse(PGS.coordFromPoint(lec.getCenter()), wh, wh);
|
246 | 319 | return toPShape(circle);
|
247 | 320 | }
|
| 321 | + |
| 322 | + public enum RectPackHeuristic { |
| 323 | + |
| 324 | + /** |
| 325 | + * Packs rectangles such that the wasted/empty area within the bin is minimised. |
| 326 | + * This heuristic tends to generate packings that are less dense but are better |
| 327 | + * at covering the whole area (particularly if there is much spare area) than |
| 328 | + * the other heuristics. |
| 329 | + */ |
| 330 | + BestAreaFit(PackingHeuristic.BestAreaFit), |
| 331 | + /** |
| 332 | + * Packs rectangles such that the total touching perimeter length is maximised. |
| 333 | + * In practice, rectangles are packed against the left-most and upper-most |
| 334 | + * boundary of the bin first. |
| 335 | + */ |
| 336 | + TouchingPerimeter(PackingHeuristic.TouchingPerimeter), |
| 337 | + /** |
| 338 | + * Packs rectangles such that the distance between the top-right corner of each |
| 339 | + * rectangle and that of the bin is maximised. |
| 340 | + */ |
| 341 | + TopRightCornerDistance(PackingHeuristic.TopRightCornerDistance); |
| 342 | + |
| 343 | + private final PackingHeuristic h; |
| 344 | + |
| 345 | + private RectPackHeuristic(PackingHeuristic h) { |
| 346 | + this.h = h; |
| 347 | + } |
| 348 | + } |
| 349 | + |
| 350 | + /** |
| 351 | + * Packs a collection of rectangles, according to the given packing heuristic, |
| 352 | + * into rectangular 2D bin(s). Within each bin rectangles are packed flush with |
| 353 | + * each other, having no overlap. Each rectangle is packed parallel to the edges |
| 354 | + * of the plane. |
| 355 | + * <p> |
| 356 | + * When packed rectangles fill one bin, any remaining rectangles will be packed |
| 357 | + * into additional bin(s). |
| 358 | + * |
| 359 | + * @param rectangles a collection of rectangles (represented by PVectors), |
| 360 | + * specifying their width (.x) and height (.y) |
| 361 | + * @param binWidth the width of each bin's area in which to pack the |
| 362 | + * rectangles |
| 363 | + * @param binHeight the height of each bin's area in which to pack the |
| 364 | + * rectangles |
| 365 | + * @param heuristic the packing heuristic to use. The heuristic determines |
| 366 | + * rules for how every subsequent rectangle is placed |
| 367 | + * @since 1.3.1 |
| 368 | + * @return a GROUP PShape, where each immediate child is a GROUP shape |
| 369 | + * corresponding to a bin; the child shapes of each bin are rectangles. |
| 370 | + * Bins are positioned at (0, 0). |
| 371 | + */ |
| 372 | + public static PShape rectPack(List<PVector> rectangles, int binWidth, int binHeight, RectPackHeuristic heuristic) { |
| 373 | + RBPSolution packer = new RBPSolution(binWidth, binHeight); |
| 374 | + List<Rect> rects = rectangles.stream().map(p -> Rect.of((int) Math.round(p.x), (int) Math.round(p.y))) |
| 375 | + .collect(Collectors.toList()); |
| 376 | + |
| 377 | + packer.pack(rects, heuristic.h); |
| 378 | + |
| 379 | + PShape bins = new PShape(GROUP); |
| 380 | + packer.getBins().forEach(bin -> { |
| 381 | + PShape binGroup = new PShape(GROUP); |
| 382 | + bin.getPackedRects().forEach(r -> { |
| 383 | + binGroup.addChild(PGS.createRect(r.x, r.y, r.width, r.height)); |
| 384 | + }); |
| 385 | + bins.addChild(binGroup); |
| 386 | + }); |
| 387 | + return bins; |
| 388 | + } |
248 | 389 |
|
249 | 390 | /**
|
250 | 391 | * Returns the nearest point of the shape to the given point. If the shape is
|
|
0 commit comments