angusjohnson / clipper2 Goto Github PK
View Code? Open in Web Editor NEWPolygon Clipping and Offsetting - C++, C# and Delphi
License: Boost Software License 1.0
Polygon Clipping and Offsetting - C++, C# and Delphi
License: Boost Software License 1.0
Like I mentioned in #27 there now seems to be a crash when building the solution tree after the clip operation in the function:
BuildTree
It seems to be related to the way owner_polypath
gets assigned?
When it gets to the last line outrec->polypath = owner_polypath->AddChild(path);
and it tries to add the path as a child. It looks like the owner_polypath
is invalid and the operation crashes.
This error shows up in the example I shared with you in #27
For example, this used to be possible and is not allowed now due to the readonly struct (forcing readonly for all properties)
private void prox_ZFillCallback(Point64 bot1, Point64 top1, Point64 bot2, Point64 top2, ref Point64 pt)
{
pt.Z = bot1.Z;
}
./Clipper2/CPP/Clipper2Lib/clipper.engine.cpp:3296:6: error: no viable overloaded '='
op = op.next;
~~ ^ ~~~~~~~
./Clipper2/CPP/Clipper2Lib/clipper.engine.h:63:9: note: candidate function (the implicit copy assignment operator) not viable: no known conversion from 'Clipper2Lib::OutPt *' to
'const Clipper2Lib::OutPt' for 1st argument; dereference the argument with *
struct OutPt {
^
1 error generated.
This simple example will cause Execute to loop endlessly:
Clipper64 clipper;
Paths64 squares;
Path64 square = MakePath( "3, 3, 3, 0, 0, 0, 0, 3" );
squares.push_back( square );
clipper.AddClip( squares );
Paths64 lines;
Path64 line = MakePath( "2, 0, 1, 0, 0, 0" );
lines.push_back( line );
clipper.AddOpenSubject( lines );
PolyTree64 clippedPolygons;
Paths64 clippedLines;
clipper.Execute( ClipType::Difference, FillRule::EvenOdd, clippedPolygons, clippedLines );
The double clipper with precision=8 produces a range check error. I can provide you with the path data. Is there no upload opportunity in github?
Clp := TClipperD.Create(8);
function LocMinListSort(item1, item2: Pointer): Integer;
var
dy: Int64;
lm1: PLocalMinima absolute item1;
lm2: PLocalMinima absolute item2;
begin
dy := lm2.Vertex.Pt.Y - lm1.Vertex.Pt.Y;
if dy < 0 then Result := -1
else if dy > 0 then Result := 1
else Result := lm2.Vertex.Pt.X - lm1.Vertex.Pt.X; //<--Range Check Error, res too big for Integer
end;
Path64 lPoly = Clipper.MakePath(new [] {
0,0,
0,500000,
100000,500000,
100000,200000,
600000,200000,
600000,0,
0,0
});
Path64 t = Clipper.MakePath(new[] {
100000,200000,
100000,-9800000
});
c.AddOpenSubject(t);
c.AddClip(lPoly);
PolyTree64 pt = new();
Paths64 p = new();
c.Execute(ClipType.Intersection, FillRule.EvenOdd, pt, p);
As of Jul 18, this yielded :
[0] = {Point64} 100000,200000,0
[1] = {Point64} 100000,0,0
but now the result in p is the same as the input t, which would appear to be wrong.
I enabled the Nullable checks for the project which has the unfortunate issue of causing warnings to appear throughout the codebase. One of the nice benefits is it helps the compiler determine what can be null and what can't be.
I ran into the code below for the private Active? DoMaxima(Active ae)
method and it appears that on line 2356, the compiler is telling me that maxPair can never be null at this point due to the return if null above on line 2340. See the code snippets below.
Since there appears there is a desire for private OutPt AddOutPt(Active ae, Point64 pt)
to be run in some circumstance, but it never can actually run in the code, I figured I would raise the issue for discussion instead of just removing the un-runnable code.
if (maxPair != null) // <-- Is always true
AddLocalMaxPoly(ae, maxPair, ae.top);
else
AddOutPt(ae, ae.top); // <- Never runs
I've been using a version of Clipper2 from April 19, to clip open polylines with a polygon to find the intersection points of the lines on the clipping polygon. It worked fine--but I just downloaded the latest commit and my code now fails and returns a null solution. The example test code below should return an intersection in openPaths64 of (340,50),(340,250),(140,50),(140,250).
Doing comparisions of previous versions, it seems the problem I'm having was introduced on 5/4 somewhere in Commit 8e8925b which fails in my code. However, Commit 04c591e also on 5/4, works Ok and returns the expected solution.
...
var
clipPoly64,subjOpen64,closedPaths64,openPaths64: tpaths64;
setlength(clipPoly64,1);
setlength(subjOpen64,4);
clipPoly64[0] := MakePath([350,50,50,50,50,250,350,250,350,50]);
subjOpen64[0] := MakePath([-60,-20,-60,380]);
subjOpen64[1] := MakePath([140,-20,140,380]);
subjOpen64[2] := MakePath([340,-20,340,380]);
subjOpen64[3] := MakePath([540,-20,540,380]);
with TClipper.Create do
try
AddOpenSubject(subjOpen64);
AddClip(clipPoly64);
Execute(ctIntersection,frEvenOdd,closedPaths64,openPaths64);
finally
Free;
end;
function DistanceSqr(const pt1, pt2: TPoint64): double;
var
d: Double;
begin
//Result := Sqr(pt1.X - pt2.X) + Sqr(pt1.Y - pt2.Y); //integer overflow
d := Sqr(pt1.X - pt2.X);
Result := d + Sqr(pt1.Y - pt2.Y); //okay
end;
function IntersectListSort(node1, node2: Pointer): Integer;
var
i1: PIntersectNode absolute node1;
i2: PIntersectNode absolute node2;
begin
result := i2.Pt.Y - i1.Pt.Y;
//Sort by X too. While not essential, this significantly speed up the
//secondary sort in ProcessIntersectList (which ensures edge adjacency).
if (result = 0) and (i1 <> i2) then
result := i1.Pt.X - i2.Pt.X; //<------------------Range Check Error
end;
function GetIntersectPoint(const ln1a, ln1b, ln2a, ln2b: TPoint64): TPointD;
-->
Result.X := (ln1a.X + ln1b.X)/2;
Result.Y := (ln1a.Y + ln1b.Y)/2;
-->
Result.X := (ln1a.X + ln1b.X) * 0.5;
Result.Y := (ln1a.Y + ln1b.Y) * 0.5;
procedure TClipperOffset.DoGroupOffset(pathGroup: TPathGroup; delta: double);
-->
fStepsPerRad := steps / Two_Pi;
-->
const
Two_Pi_Invers = 1/2*PI;
fStepsPerRad := steps * Two_Pi_Invers;
Hi Angus,
Referring the the C-Sharp implementation, the last commit (fixed my reported other issue: THANKS!), however it also introduced some regressions:
-Fribur
@AngusJohnson
I am writing to request what random intersecting polygon generator algorithm you used for the performance so that I can compare performance on my end.
I apologize as the Issues system is not the best way to contact someone , but I don't see any other method to contact you since there not discussions enabled for the project.
I suggest to rename left
, right
, top
, bottom
to something that makes sense regardless of y-axis orientation / REVERSE_ORIENTATION
.
Hello!
I follow this project with interest, but I am not able to write examples by myself. I still see no examples in the repository. I tried to convert an example shipped with the first version of the library, but without success.
Could you please include some examples, and (if possible) some simple examples, so we can understand the principles?
Regards.
Roland
P.-S. In case you are interested, I attach a modified version of the Cairo demo shipped with Clipper 1. I changed the Windows application in a simple console program, because I wished to be able to test the library under Linux.
I made a simple test case of a small rectangle inside a larger one. Then substracted the smaller one from the large one.
I expected the resulting tree to have a large rectangle at the root of the tree (not hole) and the smaller one to be a child of the bigger one (is hole).
Instead they are both at the root and they are both (not hole).
I was trying to do a boolean operation on a very large polygon array that runs fine on Clipper 1 C++, but takes around 3min.
When I tried the same operation with the same settings (Intersection, closed polygons (Path64), NonZero, polytree output) the code is crashing in the following function:
inline bool IsOuter(const OutRec& outrec)
{
return (outrec.state == OutRecState::Outer);
}
It seems to be trying to read outrec even though it is set to NULL.
I realize a minimum working example would be nice. But I have this pretty integrated into much larger code that reads the polygons off a GDS file. So I'm not sure how to share the polygon structures with you. (If you have any preferences let me know)
PS. When running the operation on a smaller set of polygons, there are no issues.
Does not compile:
{$IFDEF REVERSE_ORIENTATION}
const fillPos = TFillRule.frNegative;
const fillNeg = TFillRule.frPositive;
{$ELSE}
const fillPos = TFillRule.frPositive;
const fillNeg = TFillRule.frNegative;
{$ENDIF}
Does compile:
{$IFDEF REVERSE_ORIENTATION}
const fillPos = frNegative;
const fillNeg = frPositive;
{$ELSE}
const fillPos = frPositive;
const fillNeg = frNegative;
{$ENDIF}
Unit Classes is not needed in Clipper.core.pas
I think today's commit (51979a43) may have broken something (in the C++ version).
I will try to isolate the issue to some reproducible example. In the meanwhile, however, here is how the problem is appearing: one half of the results are missing (top left in my visualizations, but I guess that depends on the coordinate system), as if clipped by a triangle:
The expected result, from the previous version:
(Please disregard any pixel-level differences between the above images. This is from a larger test, originally intended to test something completely different. That thing is a bit stochastic, so the results are not completely deterministic.)
I'm certainly not saying I'm doing everything right. But anyway nothing except ClipperLib version changed between obtaining these two results.
As a developer I would like to use the C++ library also in projects with disabled exception support.
I'm getting differing results when attempting to create offset lines.
I am attempting to offset to the left and right to a center line without closing the ends.
The following does not actually display any offset lines., but does kind of work for more complex lines but with lines closing.
Clipper2Lib::PathD linePath;
linePath.emplace_back(PointD(0, 0));
linePath.emplace_back(PointD(1000, 1000));
linePath.emplace_back(PointD(2000, 2000));
linePath.emplace_back(PointD(3000, 3000));
// center line
svg.AddPath(linePath, true, 0x00000000, 0x50ff0050, 0.8, false);
ClipperOffset co;
co.AddPath(linePath, JoinType::Square, EndType::Polygon);
const auto leftLine = co.Execute(1000);
svg.AddPaths(leftLine, true, 0x00000000, 0xffff0050, 0.8, false);
co.Clear();
co.AddPath(linePath, JoinType::Square, EndType::Polygon);
const auto rightLine = co.Execute(-1000);
svg.AddPaths(rightLine, true, 0x00000000, 0xff00ff50, 0.8, false);
SvgSaveToFile(svg, "offset_line_test.svg", 300, 300, 0);
With a more complex line I actually get the left and right lines but they are closed, where I'd just like the individual lines openended and butted. I gather Clipper1 had the ability to do this.
Just wondering if I'm doing something wrong or its just not possible to do yet.
When I do an InflatePaths operation on a closed rectangular path. I expect a rectangle with a hole, instead I get a single outer rectangle.
I'm using the endtype "Butt"
Compiler: clang version 14.0.0
OS: Linux (Ubuntu 22.04)
Build steps:
cd CPP
mkdir build_clang
cd build_clang
export CC=/usr/bin/clang
export CXX=/usr/bin/clang++
cmake ../ -DCMAKE_BUILD_TYPE=Debug
Errors produced:
[ 3%] Building CXX object CMakeFiles/Clipper2.dir/Clipper2Lib/clipper.engine.cpp.o
In file included from /home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.cpp:17:
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:422:8: error: 'Clipper2Lib::ClipperD::Execute' hides overloaded virtual functions [-Werror,-Woverloaded-virtual]
bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:255:16: note: hidden overloaded virtual function 'Clipper2Lib::ClipperBase::Execute' declared here: type mismatch at 3rd parameter ('Clipper2Lib::Paths64 &' (aka 'vector<vector<Point<long>>> &') vs 'Clipper2Lib::PathsD &' (aka 'vector<vector<Point<double>>> &'))
virtual bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:257:16: note: hidden overloaded virtual function 'Clipper2Lib::ClipperBase::Execute' declared here: different number of parameters (4 vs 3)
virtual bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:259:16: note: hidden overloaded virtual function 'Clipper2Lib::ClipperBase::Execute' declared here: different number of parameters (4 vs 3)
virtual bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:430:8: error: 'Clipper2Lib::ClipperD::Execute' hides overloaded virtual functions [-Werror,-Woverloaded-virtual]
bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:255:16: note: hidden overloaded virtual function 'Clipper2Lib::ClipperBase::Execute' declared here: different number of parameters (3 vs 4)
virtual bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:257:16: note: hidden overloaded virtual function 'Clipper2Lib::ClipperBase::Execute' declared here: type mismatch at 3rd parameter ('Clipper2Lib::Paths64 &' (aka 'vector<vector<Point<long>>> &') vs 'Clipper2Lib::PathsD &' (aka 'vector<vector<Point<double>>> &'))
virtual bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:259:16: note: hidden overloaded virtual function 'Clipper2Lib::ClipperBase::Execute' declared here: type mismatch at 3rd parameter ('Clipper2Lib::PolyTree64 &' (aka 'PolyPath<long> &') vs 'Clipper2Lib::PathsD &' (aka 'vector<vector<Point<double>>> &'))
virtual bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:442:8: error: 'Clipper2Lib::ClipperD::Execute' hides overloaded virtual functions [-Werror,-Woverloaded-virtual]
bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:255:16: note: hidden overloaded virtual function 'Clipper2Lib::ClipperBase::Execute' declared here: different number of parameters (3 vs 4)
virtual bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:257:16: note: hidden overloaded virtual function 'Clipper2Lib::ClipperBase::Execute' declared here: type mismatch at 3rd parameter ('Clipper2Lib::Paths64 &' (aka 'vector<vector<Point<long>>> &') vs 'Clipper2Lib::PolyTreeD &' (aka 'PolyPath<double> &'))
virtual bool Execute(ClipType clip_type,
^
/home/damian/projects/checks/Clipper2/CPP/Clipper2Lib/clipper.engine.h:259:16: note: hidden overloaded virtual function 'Clipper2Lib::ClipperBase::Execute' declared here: type mismatch at 3rd parameter ('Clipper2Lib::PolyTree64 &' (aka 'PolyPath<long> &') vs 'Clipper2Lib::PolyTreeD &' (aka 'PolyPath<double> &'))
virtual bool Execute(ClipType clip_type,
^
3 errors generated.
This is a little bit difficult to solve correctly as what I think needs to be done is to template ClipperBase.
The other approach is to ignore the issue for now using the following approach:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Woverloaded-virtual"
code....
#pragma clang diagnostic pop
There are a number of other issues that clang finds.
I've just discovered several problems with the recent (major) changes to the Delphi code that relate to merging solution polygons.
These will be fixed ASAP but until further notice I'd recommend holding off downloading any commits later than 25 March (if you intend to use the Delphi code).
A couple of test cases.
A line with a positive offset:
A line with a negative offset:
Some observations:
What I'm looking for is to be able to offset a line on one side or the other based on the offset being positive or negative.
The test code is as follows:
void testLineOffsetsPositive()
{
using namespace Clipper2Lib;
SvgWriter svg(FillRule::EvenOdd);
svg.Clear();
Clipper2Lib::PathD linePath;
linePath.emplace_back(PointD(0, 0));
linePath.emplace_back(PointD(0, 1000));
linePath.emplace_back(PointD(1000, 1000));
linePath.emplace_back(PointD(1000, 0));
linePath.emplace_back(PointD(2500, 0));
linePath.emplace_back(PointD(2000, 3000));
// center line
svg.AddPath(linePath, true, 0x00000000, 0xffff00ff, 0.8, false);
ClipperOffset co;
co.AddPath(linePath, JoinType::Square, EndType::Square);
const auto leftLine = co.Execute(1000);
svg.AddPaths(leftLine, true, 0x00000000, 0xffff0050, 0.8, false);
SvgSaveToFile(svg, "offset_line_test_positive.svg", 300, 300, 0);
}
void testLineOffsetsNegative()
{
using namespace Clipper2Lib;
SvgWriter svg(FillRule::EvenOdd);
svg.Clear();
Clipper2Lib::PathD linePath;
linePath.emplace_back(PointD(0, 0));
linePath.emplace_back(PointD(0, 1000));
linePath.emplace_back(PointD(1000, 1000));
linePath.emplace_back(PointD(1000, 0));
linePath.emplace_back(PointD(2500, 0));
linePath.emplace_back(PointD(2000, 3000));
// center line
svg.AddPath(linePath, true, 0x00000000, 0xffff00ff, 0.8, false);
ClipperOffset co;
co.AddPath(linePath, JoinType::Square, EndType::Square);
const auto rightLine = co.Execute(-1000);
svg.AddPaths(rightLine, true, 0x00000000, 0xff00ff50, 0.8, false);
SvgSaveToFile(svg, "offset_line_test_negative.svg", 300, 300, 0);
}
Changing to EndType::Butt
I get the following:
Changing to EndType::Round
or EndType::Joined
I get:
Changing to EndType::Polygon
I get:
This one does not look correct.
Note that the above three images show that the generated offset line has an open point on the top right which seems odd.
The clipping code and polygon union is exceptionally fast and accurate ๐
I have a comparison test here that is simplified from a real world use case where I run into trouble with Clipper 2 compared to Clipper 1
namespace ClipperLibTest;
using Path64 = List<ClipperLib2.Point64>;
using Paths64 = List<List<ClipperLib2.Point64>>;
using Path = List<ClipperLib1.IntPoint>;
using Paths = List<List<ClipperLib1.IntPoint>>;
public static class OffsetTest
{
private static Path64 test = new()
{
new (0, 0),
new (0, 8),
new (4, 8),
new (4, 4),
new (16, 4),
new (16, 16),
new (4, 16),
new (4, 10),
new (0, 10),
new (0,20),
new (20,20),
new (20,0)
};
public static void compare()
{
Path test1 = new();
for (int pt = 0; pt < test.Count; pt++)
{
test1.Add(new ClipperLib1.IntPoint(test[pt].X, test[pt].Y));
}
ClipperLib1.ClipperOffset co1 = new();
co1.AddPath(test1, ClipperLib1.JoinType.jtMiter, ClipperLib1.EndType.etClosedPolygon);
Paths c1up = new();
co1.Execute(ref c1up, 2.0);
co1.Clear();
co1.AddPaths(c1up, ClipperLib1.JoinType.jtMiter, ClipperLib1.EndType.etClosedPolygon);
Paths c1down = new();
co1.Execute(ref c1down, -2.0);
ClipperLib2.ClipperOffset co2 = new();
co2.AddPath(test, ClipperLib2.JoinType.Miter, ClipperLib2.EndType.Polygon);
Paths64 c2up = ClipperLib2.ClipperFunc.Paths64(co2.Execute(2.0));
co2.Clear();
co2.AddPaths(c2up, ClipperLib2.JoinType.Miter, ClipperLib2.EndType.Polygon);
Paths64 c2down = ClipperLib2.ClipperFunc.Paths64(co2.Execute(-2.0));
}
}
using_polytree
remains true
after Execute
was called with a PolyTree
. So subsequent Execute
s without PolyTree
run with using_polytree = true
.
I'm now getting a crash on the BuildTree
function. It's at the following line at the end of the function:
outrec->polypath = owner_polypath->AddChild(path);
owner_polypath
seems to be invalid.
Error shows up in the example I previously shared in #27
Here is test data for what appears to be another infinite loop (using the polypaths interface).
On line 3365 of clipper.engine.cpp, you have:
if (pt.x < result.left) result.left = pt.x;
else if (pt.x > result.right) result.right = pt.x;
If the very first vertex is also the right-most one, that "else" prevents result.right from being updated, and the final bounding box is incorrect. Same thing for result.bottom, two lines below.
(caught that little error through my C rewrite producing slightly different results)
This code will cause a crash at line 1511 of clipper.engine.cpp because op_front is nullptr.
Clipper64 clipper;
Paths64 squares0;
Path64 square0 = MakePath( "6, 8, 4, 8, 4, 6, 6, 6" );
squares0.push_back( square0 );
clipper.AddClip( squares0 );
Paths64 squares1;
Path64 square1 = MakePath( "5, 5, 7, 5, 7, 7, 5, 7" );
squares1.push_back( square1 );
clipper.AddSubject( squares1 );
Paths64 lines;
Path64 line = MakePath( "5, 7, 5, 5" );
lines.push_back( line );
clipper.AddOpenSubject( lines );
PolyTree64 clippedPolygons;
Paths64 clippedLines;
clipper.Execute( ClipType::Difference, FillRule::EvenOdd, clippedPolygons, clippedLines );
Clipper 1 seems to handle this correctly, but Clipper 2 appears to return iPoly as the result of the difference
namespace ClipperLibTest;
using Paths64 = List<List<ClipperLib2.Point64>>;
using Paths = List<List<ClipperLib1.IntPoint>>;
public class STest
{
public static void compare()
{
List<ClipperLib2.Point64> BP = new()
{
new(1000, 27000),
new(1000, 2000),
new(27000, 2000),
new(27000, 27000),
new(1000, 27000)
};
Paths64 iPoly = new()
{
new()
{
new(1000, 2000),
new(1000, 7000),
new(6000, 7000),
new(6000, 14000),
new(1000, 14000),
new(1000, 27000),
new(27000, 27000),
new(27000, 23000),
new(16000, 23000),
new(16000, 16000),
new(27000, 16000),
new(27000, 2000),
}
};
List<ClipperLib1.IntPoint> BP1 = new();
for (int pt = 0; pt < BP.Count; pt++)
{
BP1.Add(new (BP[pt].X, BP[pt].Y));
}
Paths iPoly1 = new();
for (int p = 0; p < iPoly.Count; p++)
{
List<ClipperLib1.IntPoint> t = new();
for (int pt = 0; pt < iPoly[p].Count; pt++)
{
t.Add(new (iPoly[p][pt].X, iPoly[p][pt].Y));
}
iPoly1.Add(t);
}
ClipperLib1.Clipper c1 = new() {PreserveCollinear = false};
c1.AddPath(BP1, ClipperLib1.PolyType.ptSubject, true);
c1.AddPaths(iPoly1, ClipperLib1.PolyType.ptClip, true);
Paths o1 = new();
c1.Execute(ClipperLib1.ClipType.ctDifference, o1);
ClipperLib2.Clipper c2 = new() {PreserveCollinear = false};
c2.AddSubject(BP);
c2.AddClip(iPoly);
Paths64 o2 = new();
c2.Execute(ClipperLib2.ClipType.Difference, ClipperLib2.FillRule.EvenOdd, o2);
}
}
With reference to https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2013, the use of ReferenceEquals is indicated as being problematic in the Clipper.Engine.cs file.
hi Angus!
please port MinkowskiSum and MinkowskiDiff from clipper1
I'm using InflatePaths on a triangle, and using the PathsD format doesn't work properly. Using Paths64 and upscaling the polygon gives the expected result, a bigger triangle with 'rounded edges'
PathsD polyD = new PathsD();
PathsD resD;
Paths64 poly64 = new Paths64();
Paths64 res64;
polyD.Add(ClipperFunc.MakePath(new double[] { 218.57, 500, 618.57, 500, 218.57, 800 }));
resD = ClipperFunc.InflatePaths(polyD, 5, JoinType.Round, EndType.Joined);
poly64.Add(ClipperFunc.MakePath(new int[] { 2185700, 5000000, 6185700, 5000000, 2185700, 8000000 }));
res64 = ClipperFunc.InflatePaths(poly64, 50000, JoinType.Round, EndType.Joined);
The result using double is snapped into integer values, so I'm guessing this is caused by converting PathsD into Paths64 inside the InflatePaths function
If DEFAULT_ORIENTATION_IS_REVERSED
is set to false, the union of a single positive path with positive fill rule is negative and the union of a single negative path with negative fill rule is negative. Both results should be positive paths.
No issues if DEFAULT_ORIENTATION_IS_REVERSED
is true.
D:\Delphi\Clipper2\Delphi\Clipper2Lib\Clipper.Engine.pas(356,3) Hint: (5028) Local const "rsClipper_OpenPathErr" is not used
D:\Delphi\Clipper2\Delphi\Clipper2Lib\Clipper.Offset.pas(450,3) Note: (5025) Local variable "scale" not used
D:\Delphi\Clipper2\Delphi\Clipper2Lib\Clipper.pas(288,3) Note: (5025) Local variable "co" not used
D:\Delphi\Clipper2\Delphi\Clipper2Lib\Clipper.pas(18,3) Hint: (5023) Unit "Math" not used in Clipper
D:\Delphi\Clipper2\Delphi\Clipper2Lib\Clipper.Minkowski.pas(17,42) Hint: (5023) Unit "Clipper.Engine" not used in Clipper.Minkowski
So unless REVERSE_ORIENTATION
is defined, Clipper2 paths' orientation is opposite compared to Clipper1. In Clipper1 paths with mathematical counter-clockwise orientation are positive and paths with mathematical clockwise orientation are negative. This is the behavior I want to have, and therefore I define REVERSE_ORIENTATION
. To me clockwise/counter-clockwise does not depend on the orientation of the y-axis of the display. So I expect IsClockwise
to return false
for positively oriented paths, which are mathematically counter-clockwise. Of course on a y-axis down display it looks clockwise to the observer. So it depends on how you look at it. I like the Orientation
function of Clipper1 better, because the name makes sense regardless of how you look at it.
consider using classes instead of aliases.
this
using Path64 = List<Point64>;
using PathD = List<PointD>;
using Paths64 = List<List<Point64>>;
using PathsD = List<List<PointD>>;
replaced with this, no more need to define aliases in every file
public class Path64 : List<Point64>
{
public Path64()
{
}
public Path64(int capacity) : base(capacity)
{
}
}
public class Paths64 : List<Path64>
{
public Paths64()
{
}
public Paths64(int capacity) : base(capacity)
{
}
}
public class PathD : List<PointD>
{
public PathD()
{
}
public PathD(int capacity) : base(capacity)
{
}
}
public class PathsD : List<PathD>
{
public PathsD()
{
}
public PathsD(int capacity) : base(capacity)
{
}
}
http://www.angusj.com/clipper2/Docs/Units/Clipper/Functions/RamerDouglasPeucker.htm
No only does this function remove potential offset artefacts, but it also significantly speeds up the offsetting process.
should be
Not only does this function remove potential offset artefacts, but it also significantly speeds up the offsetting process.
As a template, the Distance function won't compile if used. It should be this instead:
template <typename T>
inline double Distance(const Point<T> pt1, const Point<T> pt2)
{
return std::sqrt(DistanceSqr(pt1, pt2));
}
By the way, I've written my own line-length functions, shown here (and I hope they're correct). Would these be a nice addition to Clipper2?
double MyLength( const Path64& path )
{
double length = 0.0;
Point64 point;
for ( auto it = path.begin(); it != path.end(); ++it ) {
if ( it != path.begin() )
length += Distance( point, *it );
point = *it;
}
return length;
}
double MyLength( const Paths64& paths )
{
double length = 0.0;
for ( auto it = paths.begin(); it != paths.end(); ++it )
length += MyLength( *it );
return length;
}
It seems that structures built internally during clipping solution generation already contain most of the information needed to perform triangulation, but that is lost when polytree is built. Is there some simple way to generate triangulation that I'm missing? If not, is such a feature planned?
Dear Angus,
Thank you for your amazing library. I'm a long-time user of Clipper1, and generally very happy with it. However, will certainly welcome the speed and other improvements that may come as part of Clipper2.
I'm really on C++ only. Is there anything I can help with?
In particular, I would propose that I could create CI tests that would automatically be run by GitHub Actions (or similar) for each and every new commit (and/or pull request).
I'll need to test any new versions on my side anyway. So I thought I would happily contribute the necessary bits upstream, so that you and other contributors could get feedback as quickly as possible, hopefully making your job easier. Having a framework ready should also encourage adding more tests for specific corner cases and whatnot.
I would personally add it for C++ only (at least initially), but I'd leave room for doing something similar for Delphi and C#.
Here is an example from another library I've been working with. So it could look more or less like this: https://github.com/reunanen/dlib/actions
If any thoughts, please let me know. I can also just post a proposal (as a pull request), and then we can take it from there.
Or if there's already something like that working or under construction, and I have missed it, please let me know.
On a related note, I noticed that you wrote:
Things like Makefiles are beyond me and I'm hope others will contribute so Clipper2 C++ will more easily compile in environments other than MS Visual Studio.
I'm regularly compiling Clipper1 on Linux (gcc), and all is good. When adopting Clipper2, I'll definitely make sure that is still the case. Since it's just a couple of .cpp
files, I think non-Windows users should not require any special treatment, as long as the code itself will compile. In order to make sure that that continues to be the case, I could add Linux (and maybe even MacOS) jobs to the CI pipelines, if you want. But I could well start with Visual Studio, so it'll be more accessible to you.
With all the discussion about orientation I'm not sure I have this right, but what used to work for me no longer works. Consider this code:
Clipper64 clipper;
Paths64 squares0;
Path64 square0 = MakePath( "4, 6, 4, 8, 6, 8, 6, 6" );
squares0.push_back( square0 );
clipper.AddClip( squares0 );
ASSERT( Area( square0 ) > 0.0 );
ASSERT( Area( squares0 ) > 0.0 );
Paths64 squares1;
Path64 square1 = MakePath( "7, 5, 5, 5, 5, 7, 7, 7" );
squares1.push_back( square1 );
clipper.AddSubject( squares1 );
ASSERT( Area( square1 ) > 0.0 );
ASSERT( Area( squares1 ) > 0.0 );
PolyTree64 clippedPolygons;
Paths64 clippedLines;
clipper.Execute( ClipType::Difference, FillRule::EvenOdd, clippedPolygons, clippedLines );
ASSERT( MyArea( clippedPolygons ) < 0.0 );
ASSERT( clippedPolygons.polygon.size() == 0 );
All ASSERTS execute without error, but the last two appear to be wrong, and would have failed in the previous release. The result seems to be inside-out. By the way, I'm using my own area function for polytrees; wouldn't this be a nice addition to Clipper2?
double MyArea( const PolyTree64& polytree )
{
double area = Area( polytree.polygon );
for ( auto it = polytree.childs.begin(); it != polytree.childs.end(); ++it )
area += MyArea( **it );
return area;
}
In my use of Clipper, I routinely encounter annular structures (i.e., a circle within a circle, or a circle with a hole). Using these in Clipper1 works fine regardless of how they are defined. It doesn't seem to matter if do a "SimplifyPolygon" before doing a Union or Difference operation. However, I am having issues trying to process these annular structures in Clipper2.
If I use 2 unconnected concentric circles, with the inner circle oriented opposite the outer circle, Clipper2 seems to work fine. However, most of my annular structures are defined as a single path with the inner circle connected to the outer circle with a radial edge (actually 2 coincident edges, one pointing outward and one pointing inward). If I try to use such an annular structure in Clipper2, it does not work at all. Am I missing something? I don't see a "SimplifyPolygon" routine as you had in Clipper1 or I would try that.
FYI, I use Lazarus/FreePascal.
Clipper2Lib::Paths64 paths;
Clipper2Lib::ClipperOffset co;
co.AddPath(Clipper2Lib::MakePath("0,0, 10,0, 10,10, 0,10"), Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon);
paths = co.Execute(+1);
paths = co.Execute(-1);
for (auto &path : paths)
{
for (auto &point : path)
{
std::cout << point << " ";
}
std::cout << std::endl;
}
Output:
11,-1 -1,-1 -1,11 11,11
9,9 10,9 10,10 9,10
0,9 1,9 1,10 0,10
9,0 10,0 10,1 9,1
0,0 1,0 1,1 0,1
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.