Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion CPP/Clipper2Lib/src/clipper.engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,32 @@ namespace Clipper2Lib {
static_cast<double>(pt2.y + pt3.y) * static_cast<double>(pt2.x - pt3.x));
}

// returns true when the triangle's centroid has a non-zero winding number
// relative to the closed path at op (the centroid is kept in doubles so that
// it isn't rounded outside a very thin triangle)
inline bool TriangleInsidePath(const OutPt* op,
const Point64& pt1, const Point64& pt2, const Point64& pt3)
{
const double x = (static_cast<double>(pt1.x) + pt2.x + pt3.x) / 3;
const double y = (static_cast<double>(pt1.y) + pt2.y + pt3.y) / 3;
int winding = 0;
const OutPt* op2 = op;
do
{
const Point64& p = op2->pt;
const Point64& q = op2->next->pt;
const double cross = static_cast<double>(q.x - p.x) * (y - p.y) -
(x - p.x) * static_cast<double>(q.y - p.y);
if (p.y <= y)
{
if (q.y > y && cross > 0) ++winding;
}
else if (q.y <= y && cross < 0) --winding;
op2 = op2->next;
} while (op2 != op);
return winding != 0;
}

void ReverseOutPts(OutPt* op)
{
if (!op) return;
Expand Down Expand Up @@ -1607,8 +1633,13 @@ namespace Clipper2Lib {
// So the only way for these areas to have the same sign is if
// the split triangle is larger than the path containing prevOp or
// if there's more than one self-intersection.
// A smaller split of opposite orientation is usually a spurious twist and
// is discarded. However, when it lies inside the remaining path it is a
// hole that the remaining path already covers, and discarding it would
// fill that hole. So such a split is kept as a separate path.
if (absArea2 >= 1 &&
(absArea2 > absArea1 || (area2 > 0) == (area1 > 0)))
(absArea2 > absArea1 || (area2 > 0) == (area1 > 0) ||
TriangleInsidePath(prevOp, ip, splitOp->pt, splitOp->next->pt)))
{
OutRec* newOr = NewOutRec();
newOr->owner = outrec->owner;
Expand Down
31 changes: 30 additions & 1 deletion CSharp/Clipper2Lib/Clipper.Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,30 @@ private static double AreaTriangle(Point64 pt1, Point64 pt2, Point64 pt3)
(double) (pt2.Y + pt3.Y) * (pt2.X - pt3.X);
}

// returns true when the triangle's centroid has a non-zero winding number
// relative to the closed path at op (the centroid is kept in doubles so that
// it isn't rounded outside a very thin triangle)
private static bool TriangleInsidePath(OutPt op, Point64 pt1, Point64 pt2, Point64 pt3)
{
double x = ((double) pt1.X + pt2.X + pt3.X) / 3;
double y = ((double) pt1.Y + pt2.Y + pt3.Y) / 3;
int winding = 0;
OutPt op2 = op;
do
{
Point64 p = op2.pt;
Point64 q = op2.next!.pt;
double cross = (double) (q.X - p.X) * (y - p.Y) - (x - p.X) * (q.Y - p.Y);
if (p.Y <= y)
{
if (q.Y > y && cross > 0) winding++;
}
else if (q.Y <= y && cross < 0) winding--;
op2 = op2.next;
} while (op2 != op);
return winding != 0;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static OutRec? GetRealOutRec(OutRec? outRec)
{
Expand Down Expand Up @@ -2930,9 +2954,14 @@ private void DoSplitOp(OutRec outrec, OutPt splitOp)
// So the only way for these areas to have the same sign is if
// the split triangle is larger than the path containing prevOp or
// if there's more than one self=intersection.
// A smaller split of opposite orientation is usually a spurious twist and
// is discarded. However, when it lies inside the remaining path it is a
// hole that the remaining path already covers, and discarding it would
// fill that hole. So such a split is kept as a separate path.
if (!(absArea2 > 1) ||
(!(absArea2 > absArea1) &&
((area2 > 0) != (area1 > 0)))) return;
((area2 > 0) != (area1 > 0)) &&
!TriangleInsidePath(prevOp, ip, splitOp.pt, splitOp.next.pt))) return;
OutRec newOutRec = NewOutRec();
newOutRec.owner = outrec.owner;
splitOp.outrec = newOutRec;
Expand Down
35 changes: 34 additions & 1 deletion Delphi/Clipper2Lib/Clipper.Engine.pas
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,34 @@ function AreaTriangle(const pt1, pt2, pt3: TPoint64): double;
end;
//------------------------------------------------------------------------------

function TriangleInsidePath(op: POutPt; const pt1, pt2, pt3: TPoint64): Boolean;
var
op2: POutPt;
x, y, cross: double;
winding: integer;
begin
// returns true when the triangle's centroid has a non-zero winding number
// relative to the closed path at op (the centroid is kept in doubles so
// that it isn't rounded outside a very thin triangle)
x := pt1.X / 3 + pt2.X / 3 + pt3.X / 3;
y := pt1.Y / 3 + pt2.Y / 3 + pt3.Y / 3;
winding := 0;
op2 := op;
repeat
cross := (op2.next.pt.X - op2.pt.X) * (y - op2.pt.Y) -
(x - op2.pt.X) * (op2.next.pt.Y - op2.pt.Y);
if op2.pt.Y <= y then
begin
if (op2.next.pt.Y > y) and (cross > 0) then inc(winding);
end
else if (op2.next.pt.Y <= y) and (cross < 0) then
dec(winding);
op2 := op2.next;
until op2 = op;
Result := winding <> 0;
end;
//------------------------------------------------------------------------------

function OutrecIsAscending(hotEdge: PActive): Boolean;
{$IFDEF INLINING} inline; {$ENDIF}
begin
Expand Down Expand Up @@ -2189,9 +2217,14 @@ procedure TClipperBase.DoSplitOp(outrec: POutRec; splitOp: POutPt);
// negative areas), and given area2 is the area of a new triangle containing
// splitOp, splitOp.next and the intesect pt, if area1 and area2 have the
// same sign then area2 must be the larger of the splits.
// A smaller split of opposite orientation is usually a spurious twist and
// is discarded. However, when it lies inside the remaining path it is a
// hole that the remaining path already covers, and discarding it would
// fill that hole. So such a split is kept as a separate path.

if (absArea2 < 1) or
((absArea2 < absArea1) and ((area2 > 0) <> (area1 > 0))) then
((absArea2 < absArea1) and ((area2 > 0) <> (area1 > 0)) and
not TriangleInsidePath(prevOp, ip, splitOp.pt, splitOp.next.pt)) then
begin
Dispose(splitOp.next);
Dispose(splitOp);
Expand Down
12 changes: 11 additions & 1 deletion Tests/Polygons.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1922,4 +1922,14 @@ SUBJECTS
795,573 795,575 794,575 794,578 795,578 795,579 804,579 804,578 806,578 806,578 806,573
750,569 750,572 756,572 756,569
750,566 750,569 756,569 756,566
765,566 765,569 768,569 768,566
765,566 765,569 768,569 768,566

CAPTION: 196.
CLIPTYPE: UNION
FILLRULE: NONZERO
SOL_AREA: 1254
SOL_COUNT: 2
SUBJECTS
91,7, -145,-11, -141,-15
-28,75, -33,76, 1,0
-22,51, -39,76, -25,-2