Skip to content

Request: a release with the FixSelfIntersects fix (#1067). Difference loses most of its area in 2.0.0 and 2.0.1 (NonZero, C#) #1122

Open

Description

@jasharen

Summary

Clipper.Difference with FillRule.NonZero, on a simple five-vertex subject and a simple triangle clip, returns 29,655,090.5 square units where the exact answer is 135,974,247. Almost 80% of the result is missing, including points well inside the subject and outside the clip. Commit 4da1564 on main ("Fixed a bug in ClipperBase::FixSelfIntersects (#1067)", 2026-02-22) fixes this input. No release contains that commit yet: NuGet's newest is 2.0.0 and the newest tag is 2.0.1. This issue asks for one, and offers the input below as a regression test.

Versions

Version Result
NuGet Clipper2 2.0.0 (newest on NuGet as of 2026-10-23) fails as below
Tag Clipper2_2.0.1 (21ebba0), C# library built from source fails, same numbers
main at 20f05b4, the parent of 4da1564 fails, same numbers
main at 4da1564, and at a6e7294 (2026-10-17) correct: area 135,927,331.5, within rounding of the exact area

Input

Integer coordinates, one path each, both simple.

subject: (-503, 4889) (40000, 20000) (34522, 23202) (61000, 41700) (-4000, 3200)
clip:    (63046, 38116) (0, 10000) (-556, 4862)

Expected and actual

Area
Exact NonZero difference, computed independently (signed ray-crossing winding, integrated exactly over horizontal slabs cut at every vertex and crossing) 135,974,247.0
Clipper.Difference(subject, clip, FillRule.NonZero), 2.0.0 and 2.0.1 29,655,090.5 (three paths)

The point (22716, 15000) is inside the subject and outside the clip, but outside every path of the result.

Reproduction (C#, NuGet Clipper2 2.0.0 only)

using Clipper2Lib;

// A simple five-vertex subject and a simple triangle clip.
var subject = new Paths64 { Clipper.MakePath(new long[]
    { -503, 4889, 40000, 20000, 34522, 23202, 61000, 41700, -4000, 3200 }) };
var clip = new Paths64 { Clipper.MakePath(new long[]
    { 63046, 38116, 0, 10000, -556, 4862 }) };
Paths64 difference = Clipper.Difference(subject, clip, FillRule.NonZero);

Console.WriteLine($"Clipper2 {typeof(Clipper).Assembly.GetName().Version}");
Console.WriteLine($"subject area:    {Clipper.Area(subject)}");
Console.WriteLine($"clip area:       {Clipper.Area(clip)}");
Console.WriteLine($"difference area: {Clipper.Area(difference)}");

// (22716, 15000) is inside the subject and outside the clip,
// so it belongs in the difference; the result leaves it out.
var probe = new Point64(22716, 15000);
Console.WriteLine($"probe vs subject: {Clipper.PointInPolygon(probe, subject[0])}");
Console.WriteLine($"probe vs clip:    {Clipper.PointInPolygon(probe, clip[0])}");
bool inResult = false;
foreach (Path64 path in difference)
{
    var where = Clipper.PointInPolygon(probe, path);
    Console.WriteLine($"probe vs output:  {where} (output path area {Clipper.Area(path)})");
    if (where == PointInPolygonResult.IsInside) inResult = !inResult;
}
Console.WriteLine(inResult ? "ok" : "FAIL: the difference lost a point it should keep");
return inResult ? 0 : 1;

Output with 2.0.0:

subject area:    200159500
clip area:       154148926
difference area: 29655090.5
probe vs subject: IsInside
probe vs clip:    IsOutside
probe vs output:  IsOutside (output path area 29058161)
probe vs output:  IsOutside (output path area -59761)
probe vs output:  IsOutside (output path area 656690.5)
FAIL: the difference lost a point it should keep

Notes

  • 4da1564 removes the "adjacent intersections (ie a micro self-intersection)" shortcut in FixSelfIntersects, so the cleanup goes through DoSplitOp. On this input the old shortcut drops a large part of the outline.
  • The input was reduced automatically from a larger real case: 31 vertices in three paths, losing about 190,000 units in 1,150,000. The reduction drops a path or a vertex, rounds a coordinate, or moves or scales the whole input, and keeps each change only while the difference still loses area. It is a local minimum, not necessarily the smallest failing input.
  • Checked against this repository on 2026-10-26: Clipper2_2.0.1 (21ebba0) is 7 commits behind 4da1564 and does not contain it.

Found by an automated soak test, and reduced and analysed with AI assistance; the repro and its numbers were run and checked as given above.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions