3131 * curves</b> that are offset a specified distance from their primary curves.
3232 * <p>
3333 * Library users will rarely need to access this class directly since it's
34- * generally easier to use the {@link Clipper#InflatePaths(Paths64, double, JoinType, EndType)
35- * InflatePaths()} function for polygon
36- * offsetting.
34+ * generally easier to use the
35+ * {@link Clipper# InflatePaths(Paths64, double, JoinType, EndType)
36+ * InflatePaths()} function for polygon offsetting.
3737 * <p>
3838 * <b>Notes:</b>
3939 * <ul>
6464 * point coordinates, the <b>InflatePaths</b> function will accept paths with
6565 * floating point coordinates.</li>
6666 * <li>Redundant segments should be removed before offsetting (see
67- * {@link Clipper#SimplifyPaths(Paths64, double)
68- * SimplifyPaths()}), and between offsetting operations too. These redundant
69- * segments not only slow down offsetting, but they can cause unexpected
70- * blemishes in offset solutions.</li>
67+ * {@link Clipper#SimplifyPaths(Paths64, double) SimplifyPaths()}), and between
68+ * offsetting operations too. These redundant segments not only slow down
69+ * offsetting, but they can cause unexpected blemishes in offset solutions.</li>
7170 * </ul>
7271 */
7372public class ClipperOffset {
@@ -76,7 +75,6 @@ public class ClipperOffset {
7675 private static final String COORD_RANGE_ERROR = "Error: Coordinate range." ;
7776
7877 private final List <Group > groupList = new ArrayList <>();
79- private final Path64 inPath = new Path64 ();
8078 private Path64 pathOut = new Path64 ();
8179 private final PathD normals = new PathD ();
8280 private final Paths64 solution = new Paths64 ();
@@ -283,7 +281,7 @@ public void Execute(double delta, PolyTree64 solutionTree) {
283281 c .setPreserveCollinear (preserveCollinear );
284282 // the solution should normally retain the orientation of the input
285283 c .setReverseSolution (reverseSolution != pathsReversed );
286- c .AddSubject (solution );
284+ c .AddSubject (this . solution );
287285 c .Execute (ClipType .Union , fillRule , solutionTree );
288286 }
289287
@@ -349,31 +347,6 @@ private static PointD GetUnitNormal(Point64 pt1, Point64 pt2) {
349347 return new PointD (dy , -dx );
350348 }
351349
352- private static void GetBoundsAndLowestPolyIdx (Paths64 paths , OutObject <Integer > index , OutObject <Rect64 > recRef ) {
353- final Rect64 rec = new Rect64 (false ); // ie invalid rect
354- recRef .argValue = rec ;
355- long lpX = Long .MIN_VALUE ;
356- index .argValue = -1 ;
357- for (int i = 0 ; i < paths .size (); i ++) {
358- for (Point64 pt : paths .get (i )) {
359- if (pt .y >= rec .bottom ) {
360- if (pt .y > rec .bottom || pt .x < lpX ) {
361- index .argValue = i ;
362- lpX = pt .x ;
363- rec .bottom = pt .y ;
364- }
365- } else if (pt .y < rec .top ) {
366- rec .top = pt .y ;
367- }
368- if (pt .x > rec .right ) {
369- rec .right = pt .x ;
370- } else if (pt .x < rec .left ) {
371- rec .left = pt .y ;
372- }
373- }
374- }
375- }
376-
377350 private static PointD TranslatePoint (PointD pt , double dx , double dy ) {
378351 return new PointD (pt .x + dx , pt .y + dy );
379352 }
@@ -499,7 +472,7 @@ private void DoRound(Path64 path, int j, int k, double angle) {
499472 // when deltaCallback is assigned, groupDelta won't be constant,
500473 // so we'll need to do the following calculations for *every* vertex.
501474 double absDelta = Math .abs (groupDelta );
502- double arcTol = arcTolerance > TOLERANCE ? Math . min ( absDelta , arcTolerance ) : Math .log10 (2 + absDelta ) * DEFAULT_ARC_TOLERANCE ;
475+ double arcTol = arcTolerance > 0.01 ? arcTolerance : Math .log10 (2 + absDelta ) * InternalClipper . DEFAULT_ARC_TOLERANCE ;
503476 double stepsPer360 = Math .PI / Math .acos (1 - arcTol / absDelta );
504477 stepSin = Math .sin ((2 * Math .PI ) / stepsPer360 );
505478 stepCos = Math .cos ((2 * Math .PI ) / stepsPer360 );
@@ -568,7 +541,7 @@ private void OffsetPoint(Group group, Path64 path, int j, RefObject<Integer> k)
568541 pathOut .add (GetPerpendic (path .get (j ), normals .get (j )));
569542 } else if (cosA > 0.999 && joinType != JoinType .Round ) {
570543 // almost straight - less than 2.5 degree (#424, #482, #526 & #724)
571- DoMiter (group , path , j , k .argValue , cosA );
544+ DoMiter (group , path , j , k .argValue , cosA );
572545 } else if (joinType == JoinType .Miter ) {
573546 // miter unless the angle is sufficiently acute to exceed ML
574547 if (cosA > mitLimSqr - 1 ) {
@@ -590,7 +563,7 @@ private void OffsetPoint(Group group, Path64 path, int j, RefObject<Integer> k)
590563 private void OffsetPolygon (Group group , Path64 path ) {
591564 pathOut = new Path64 ();
592565 int cnt = path .size ();
593- RefObject <Integer > prev = new RefObject <Integer >(cnt - 1 );
566+ RefObject <Integer > prev = new RefObject <Integer >(cnt - 1 );
594567 for (int i = 0 ; i < cnt ; i ++) {
595568 OffsetPoint (group , path , i , prev );
596569 }
@@ -661,12 +634,17 @@ private void OffsetOpenPath(Group group, Path64 path) {
661634 solution .add (pathOut );
662635 }
663636
637+ private static boolean ToggleBoolIf (boolean val , boolean condition ) {
638+ return condition ? !val : val ;
639+ }
640+
664641 private void DoGroupOffset (Group group ) {
665642 if (group .endType == EndType .Polygon ) {
643+ // a straight path (2 points) can now also be 'polygon' offset
644+ // where the ends will be treated as (180 deg.) joins
666645 if (group .lowestPathIdx < 0 ) {
667- return ;
646+ delta = Math . abs ( delta ) ;
668647 }
669- // if (area == 0) return; // probably unhelpful (#430)
670648 groupDelta = (group .pathsReversed ) ? -delta : delta ;
671649 } else {
672650 groupDelta = Math .abs (delta ); // * 0.5
@@ -698,7 +676,9 @@ private void DoGroupOffset(Group group) {
698676
699677 int i = 0 ;
700678 for (Path64 p : group .inPaths ) {
701- Rect64 pathBounds = group .boundsList .get (i ++);
679+ // NOTE use int i rather than 3 iterators
680+ Rect64 pathBounds = group .boundsList .get (i );
681+ boolean isHole = group .isHoleList .get (i ++);
702682 if (!pathBounds .IsValid ()) {
703683 continue ;
704684 }
@@ -723,12 +703,12 @@ private void DoGroupOffset(Group group) {
723703 }
724704 solution .add (pathOut );
725705 continue ;
726- } // end of offsetting a single (open path) point
706+ } // end of offsetting a single point
727707
728- // when shrinking, then make sure the path can shrink that far (#593)
729- if (groupDelta < 0 && Math .min (pathBounds .getWidth (), pathBounds .getHeight ()) < -groupDelta * 2 ) {
708+ // when shrinking outer paths, make sure they can shrink this far (#593)
709+ // also when shrinking holes, make sure they too can shrink this far (#715)
710+ if (((groupDelta > 0 ) == ToggleBoolIf (isHole , group .pathsReversed )) && (Math .min (pathBounds .getWidth (), pathBounds .getHeight ()) <= -groupDelta * 2 ))
730711 continue ;
731- }
732712
733713 if (cnt == 2 && group .endType == EndType .Joined ) {
734714 endType = (group .joinType == JoinType .Round ) ? EndType .Round : EndType .Square ;
0 commit comments