AngusJohnson / Clipper2

Polygon Clipping and Offsetting - C++, C# and Delphi
Boost Software License 1.0
1.45k stars 266 forks source link

RectClipLines seems to be broken when lines go over the rect boundary (C#, 1.3.0, Clipper2). #828

Closed GeneralGDA closed 5 months ago

GeneralGDA commented 5 months ago

Consider the trivial test of self clipping:

using Clipper2Lib;
var rect = new Rect64(l: 0, t: 0, r: 19000, b: 27700);

var polyline = new Path64
([
        new (0    , 0     ), new (0    , 27700 ), new (19000, 27700 ), new (19000, 0     ),
]);

var clipped = Clipper.RectClipLines(rect, polyline);

foreach (var part in clipped)
{
    Console.Out.Write("[");
    Console.Out.Write(string.Join(';', part));
    Console.Out.WriteLine("]");
}

The output seems somewhat unusual: [0,0 ;0,27700 ;19000,27700 ;19000,0 ;0,27700 ;19000,27700 ] I would expect either an empty output if Clipper considers the rectangle as an open set, or the entire input if Clipper treats the rectangle as a closed set. By the way, I couldn't find in the documentation how Clipper treats the boundary of a rectangle.

The test above indicates that Clipper attempts to treat the rectangle as a closed set. This contradicts the following test, where the polyline's end segment is completely clipped:

using Clipper2Lib;
var rect = new Rect64(l: 0, t: 0, r: 19000, b: 27700);

var polyline = new Path64
(
    [
        new (0    , 15000),
        new (11400, 3000 ),
        new (81400, 3000 ),
        new (0    , 84000),
        new (0    , 27700),
        new (0    , 14900),
    ]
);

var clipped = Clipper.RectClipLines(rect, polyline);

foreach (var part in clipped)
{
    Console.Out.Write("[");
    Console.Out.Write(string.Join(';', part));
    Console.Out.WriteLine("]");
}
GeneralGDA commented 5 months ago

Another example, where RectClipLines introduces unexpected point in the end of clipped geometry:

using Clipper2Lib;

var rect = new Rect64(l: 0, t: 0, r: 19000, b: 27700);

var polyline = new Path64
(
    [
        new (0,     14487),
        new (1141,  14336),
        new (2214,  14128),
        new (3222,  13866),
        new (4165,  13551),
        new (5045,  13186),
        new (5863,  12773),
        new (6620,  12312),
        new (7317,  11808),
        new (7956,  11260),
        new (8538,  10673),
        new (9064,  10046),
        new (9535,   9383),
        new (9953,   8686),
        new (10319,  7955),
        new (10633,  7195),
        new (10898,  6405),
        new (11114,  5589),
        new (11283,  4748),
        new (11406,  3885),
        new (11483,  3001),
        new (81483,  3001),
        new (80745, 10098),
        new (79389, 16959),
        new (77458, 23571),
        new (74998, 29919),
        new (72053, 35990),
        new (68668, 41771),
        new (64887, 47247),
        new (60755, 52405),
        new (56316, 57231),
        new (51615, 61711),
        new (46697, 65832),
        new (41606, 69580),
        new (36387, 72941),
        new (31083, 75902),
        new (25741, 78448),
        new (20404, 80567),
        new (15117, 82244),
        new (9924,  83465),
        new (4870,  84217),
        new (0,     84487),
        new (0,     14487),
    ]
);

var clipped = Clipper.RectClipLines(rect, polyline);

foreach (var part in clipped)
{
    Console.Out.Write("[");
    Console.Out.Write(string.Join(';', part));
    Console.Out.WriteLine("]");
}
AngusJohnson commented 5 months ago

"Unlike closed path clipping, there's not always an obvious or "right way" to clip open path segments when they overlap clipping boundaries. In Clipper2, sometimes these segments will be included in clipping solutions, and sometimes not. When the adjacent (ie preceding and succeeding) segments are both inside or both outside the clipping region, then overlapping segments will be included or excluded respectively. However, for segments overlapping clipping boundaries with one adjacent segment inside and the other outside, their inclusion or otherwise in solutions remains undefined."

See https://angusj.com/clipper2/Docs/Overview.htm

GeneralGDA commented 5 months ago

@AngusJohnson, did you see the example with self-clipping? Comment from documentation does not explain why the output contains contour twice. Or you state that any non-closed path overlapping boundary clipping result is undefined?

GeneralGDA commented 5 months ago

@AngusJohnson, also, take a look at the big example in the comment. The result of RectClipLines is strange even with your explanation from documentation: Clipper2 does not output two paths (one with zero length). It generates single path that is not a subpath by any means of the input path.

AngusJohnson commented 5 months ago

Comment from documentation does not explain why the output contains contour twice

Sorry, I tested this in C++, and the output was as expected. However, I agree there is a problem in the C# code (though I don't see the contour being drawn twice).

C++:

void Test()
{
  Rect64 rec(0, 0, 19000, 27700);
  Path64 line = MakePath({ 0, 14487, 1141, 14336, 2214, 14128, 3222, 13866, 4165, 13551, 5045,
    13186, 5863, 12773, 6620, 12312, 7317, 11808, 7956, 11260, 8538, 10673, 9064, 10046, 9535,
    9383, 9953, 8686, 10319, 7955, 10633, 7195, 10898, 6405, 11114, 5589, 11283, 4748, 11406,
    3885, 11483, 3001, 81483, 3001, 80745, 10098, 79389, 16959, 77458, 23571, 74998, 29919, 72053,
    35990, 68668, 41771, 64887, 47247, 60755, 52405, 56316, 57231, 51615, 61711, 46697, 65832, 41606,
    69580, 36387, 72941, 31083, 75902, 25741, 78448, 20404, 80567, 15117, 82244, 9924, 83465, 4870,
    84217, 0, 84487, 0, 14487 });

    Paths64 solution = RectClipLines(rec, line);
    std::cout << solution;

  Paths64 subject;
  subject.push_back(line);

  SvgWriter svg;
  SvgAddOpenSubject(svg, subject, FillRule::NonZero);
  SvgAddClip(svg, Paths64({ rec.AsPath() }), FillRule::NonZero);
  SvgAddOpenSolution(svg, solution, FillRule::NonZero, false);
  std::string filename = "test_cpp.svg";
  SvgSaveToFile(svg, filename, 400, 400, 10);
  System(filename);
}

Solution: 0,14487, 1141,14336, 2214,14128, 3222,13866, 4165,13551, 5045,13186, 5863,12773, 6620,12312, 7317,11808, 7956,11260, 8538,10673, 9064,10046, 9535,9383, 9953,8686, 10319,7955, 10633,7195, 10898,6405, 11114,5589, 11283,4748, 11406,3885, 11483,3001, 19000,3001

test_cpp

C#:

    public static void Test()
    {
      string filename = @"..\..\..\test.svg";
      Paths64 subject = new();
      subject.Add(Clipper.MakePath(new int[] {
        0, 14487, 1141, 14336, 2214, 14128, 3222, 13866, 4165, 13551, 5045,
        13186, 5863, 12773, 6620, 12312, 7317, 11808, 7956, 11260, 8538, 10673, 9064, 10046, 9535,
        9383, 9953, 8686, 10319, 7955, 10633, 7195, 10898, 6405, 11114, 5589, 11283, 4748, 11406,
        3885, 11483, 3001, 81483, 3001, 80745, 10098, 79389, 16959, 77458, 23571, 74998, 29919, 72053,
        35990, 68668, 41771, 64887, 47247, 60755, 52405, 56316, 57231, 51615, 61711, 46697, 65832, 41606,
        69580, 36387, 72941, 31083, 75902, 25741, 78448, 20404, 80567, 15117, 82244, 9924, 83465, 4870,
        84217, 0, 84487, 0, 14487 }));

      Rect64 rec = new(0, 0, 19000, 27700);
      Paths64 solution = Clipper.RectClipLines(rec, subject);
      Console.WriteLine(solution.ToString());

      SvgWriter svg = new SvgWriter();
      SvgUtils.AddOpenSubject(svg, subject);
      SvgUtils.AddClip(svg, rec.AsPath());
      SvgUtils.AddOpenSolution(svg, solution, false);
      SvgUtils.SaveToFile(svg, filename, FillRule.NonZero, 400, 400, 10);
      ClipperFileIO.OpenFileWithDefaultApp(filename);
    }

Solution: 0,14487 1141,14336 2214,14128 3222,13866 4165,13551 5045,13186 5863,12773 6620,12312 7317,11808 7956,11260 8538,10673 9064,10046 9535,9383 9953,8686 10319,7955 10633,7195 10898,6405 11114,5589 11283,4748 11406,3885 11483,3001 19000,3001 0,27700

test

GeneralGDA commented 5 months ago

@AngusJohnson, thanks for investigating the problem. "The contour being drawn twice" — I was talking about trivial example of self-clipping (the very first code snippet).

AngusJohnson commented 5 months ago

"The contour being drawn twice" — I was talking about trivial example of self-clipping (the very first code snippet).

OK, another oddity just in the C# code.

AngusJohnson commented 5 months ago

Hopefully fixed now 🤞.