Skip to content

Commit df1c129

Browse files
committed
Fixed minor offsetting bug. (#418)
1 parent 044d4d1 commit df1c129

File tree

4 files changed

+106
-80
lines changed

4 files changed

+106
-80
lines changed

CPP/Clipper2Lib/src/clipper.offset.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*******************************************************************************
22
* Author : Angus Johnson *
3-
* Date : 18 February 2023 *
3+
* Date : 23 February 2023 *
44
* Website : http://www.angusj.com *
55
* Copyright : Angus Johnson 2010-2023 *
66
* Purpose : Path Offset (Inflate/Shrink) *
@@ -319,6 +319,7 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
319319
if (AlmostZero(cos_a - 1, 0.01)) // almost straight
320320
{
321321
group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
322+
group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); // (#418)
322323
}
323324
else if (!AlmostZero(cos_a + 1, 0.01) && (sin_a * group_delta_ < 0))
324325
{

CSharp/Clipper2Lib/Clipper.Offset.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*******************************************************************************
22
* Author : Angus Johnson *
3-
* Date : 21 February 2023 *
3+
* Date : 23 February 2023 *
44
* Website : http://www.angusj.com *
55
* Copyright : Angus Johnson 2010-2023 *
66
* Purpose : Path Offset (Inflate/Shrink) *
@@ -405,6 +405,7 @@ private void OffsetPoint(Group group, Path64 path, int j, ref int k)
405405
if (AlmostZero(cosA - 1, 0.01)) // almost straight
406406
{
407407
group.outPath.Add(GetPerpendic(path[j], _normals[k]));
408+
group.outPath.Add(GetPerpendic(path[j], _normals[j])); // (#418)
408409
}
409410
else if (!AlmostZero(cosA + 1, 0.01) &&
410411
(sinA * _group_delta < 0)) // is concave

DLL/Delphi_TestApp/Test_DLL.dpr

Lines changed: 100 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ program Test_DLL;
1010

1111
uses
1212
Windows,
13+
Math,
1314
ShellApi,
1415
SysUtils,
1516
{$IFDEF USING_CLIPPER2_SRC}
@@ -475,13 +476,6 @@ begin
475476
end;
476477
end;
477478

478-
function RectToPath64(const rec: TRect64): TPath64;
479-
begin
480-
with rec do
481-
Result := MakePath64([left, top, right, top,
482-
right, bottom, left, bottom]);
483-
end;
484-
485479
procedure WriteCPath64(p: CPath64);
486480
var
487481
i, len: integer;
@@ -798,23 +792,25 @@ begin
798792
DisposeLocalCPathsD(cclp_local);
799793
end;
800794

801-
procedure Test_InflatePaths64(delta: double);
795+
procedure Test_InflatePathsD(edgeCnt: integer; delta: double);
802796
var
803-
sub: TPaths64;
804-
csub_local: CPaths64;
805-
csol_extern: CPaths64;
797+
sub: TPathsD;
798+
csub_local: CPathsD;
799+
csol_extern: CPathsD;
806800
svg: TSvgWriter;
807801
begin
808802
// setup
809803
WriteLn(#10'Testing InflatePaths64');
810804
SetLength(sub, 1);
811-
sub[0] := MakeRandomPath(displayWidth, displayHeight, 7);
812-
// convert path into DLL structure (will require local clean up)
813-
csub_local := TPaths64ToCPaths64(sub);
805+
sub[0] := MakeRandomPathD(displayWidth, displayHeight, edgeCnt);
806+
// and because offsetting self-intersecting paths is unpredictable ...
807+
sub := Union(sub, frNonZero);
808+
// convert path into required DLL structure (also requires local clean up)
809+
csub_local := TPathsDToCPathsD(sub);
814810

815811
// do the DLL operation
816-
csol_extern := InflatePaths64(csub_local, delta,
817-
UInt8(TJoinType.jtMiter), UInt8(TEndType.etPolygon), 2, 4);
812+
csol_extern := InflatePathsD(csub_local, delta,
813+
UInt8(TJoinType.jtRound), UInt8(TEndType.etPolygon), 2, 4);
818814

819815
// optionally display result on the console
820816
//WriteCPaths64(csol_extern);
@@ -823,66 +819,102 @@ begin
823819
svg := TSvgWriter.Create(frNonZero);
824820
try
825821
AddSubject(svg, sub);
826-
AddSolution(svg, CPaths64ToPaths64(csol_extern));
822+
AddSolution(svg, CPathsDToPathsD(csol_extern));
827823
SaveSvg(svg, 'InflatePaths64.svg', displayWidth, displayHeight);
828824
ShowSvgImage('InflatePaths64.svg');
829825
finally
830826
svg.Free;
831827
end;
832828

833-
DisposeLocalCPaths64(csub_local);
834-
DisposeExportedCPaths64(csol_extern);
829+
DisposeLocalCPathsD(csub_local);
830+
DisposeExportedCPathsD(csol_extern);
831+
end;
832+
833+
function RotatePath(const path: TPathD;
834+
const focalPoint: TPointD; angle: double): TPathD;
835+
var
836+
i: integer;
837+
sinA, cosA, x,y: double;
838+
begin
839+
SetLength(Result, length(path));
840+
SinCos(angle, sinA, cosA);
841+
for i := 0 to high(path) do
842+
begin
843+
x := path[i].X - focalPoint.X;
844+
y := path[i].Y - focalPoint.Y;
845+
Result[i].X := x * cosA - y * sinA + focalPoint.X;
846+
Result[i].Y := x * sinA + y * cosA + focalPoint.Y;
847+
end;
835848
end;
836849

837-
procedure Test_RectClip64(edgeCnt: integer);
850+
851+
procedure Test_RectClipD(shapeCount: integer);
838852
var
839853
i, sol2_len, rec_margin: Integer;
840-
sub, clp, sol1, sol2: TPaths64;
841-
csub_local: CPaths64;
842-
csol_extern: CPaths64;
854+
sub, clp, sol1, sol2: TPathsD;
855+
csub_local: CPathsD;
856+
csol_extern: CPathsD;
843857
penClr, fillClr: TColor32;
844-
frac: Double;
845-
rec: TRect64;
858+
scaleRnd, maxOffX, maxOffY, frac: Double;
859+
rec: TRectD;
846860
fillrule: TFillRule;
847861
svg: TSvgWriter;
862+
shapes: array [0..3] of TPathD;
863+
const
864+
w = 300;
865+
h = 300;
848866
begin
849-
fillrule := frEvenOdd;
867+
// four simple concave polygons
868+
shapes[0] := MakePathD([20,20, 20,0, 40,0, 40,20, 60,20, 60,40,
869+
40,40, 40,60, 20,60, 20,40, 0,40, 0,20]);
870+
shapes[1] := MakePathD([0,0, 60,0, 60,20, 20,20, 20,40, 60,40,
871+
60,60, 0,60]);
872+
shapes[2] := MakePathD([0,0, 20,0, 20,20, 40,20, 40,0, 60,0,
873+
60,60, 40,60, 40,40, 20,40, 20,60, 0,60]);
874+
shapes[3] := MakePathD([20,60, 20,20, 0,20, 0,0, 60,0, 60,20,
875+
40,20, 40,60]);
876+
877+
fillrule := frNonZero;
850878

851879
// setup
852880
WriteLn(#10'Testing RectClip64:');
853-
SetLength(sub, 1);
854-
855-
sub[0] := MakeRandomPath(displayWidth, displayHeight, edgeCnt);
856-
csub_local := TPaths64ToCPaths64(sub);
857881

858-
rec_margin := displayHeight div 3;
882+
rec_margin := Min(w,h) div 3;
859883
rec.Left := rec_margin;
860884
rec.Top := rec_margin;
861-
rec.Right := displayWidth - rec_margin;
862-
rec.Bottom := displayHeight -rec_margin;
863-
864-
// display 'sub' and 'rec' on the console
865-
WriteLn(#10' subject:');
866-
WritePath64(sub[0]);
885+
rec.Right := w - rec_margin;
886+
rec.Bottom := h -rec_margin;
867887

868-
// display 'rec' on the console
869-
WriteLn(#10' rect path:');
870-
WritePath64(rec.AsPath);
888+
SetLength(sub, shapeCount);
889+
for i := 0 to shapeCount -1 do
890+
begin
891+
scaleRnd := (60 + Random(w div 4)) / 120;
892+
maxOffX := w - (scaleRnd * 60);
893+
maxOffY := h - (scaleRnd * 60);
894+
sub[i] := ScalePathD(shapes[Random(4)], scaleRnd);
895+
sub[i] := TranslatePath(sub[i],
896+
Random(Round(maxOffX)), Random(Round(maxOffY)));
897+
end;
871898

872-
// do the DLL operation (twice :))
873-
csol_extern := RectClip64(rec, csub_local, true);
874-
sol1 := CPaths64ToPaths64(csol_extern);
875-
DisposeExportedCPaths64(csol_extern);
899+
// do the DLL operation with ConvexOnly enabled
900+
{$IFDEF USING_CLIPPER2_SRC}
901+
sol1 := RectClip( rec, sub, true, 2);
902+
sol2 := RectClip( rec, sub, false, 2);
903+
csub_local := nil;
904+
csol_extern := nil;
905+
{$ELSE}
906+
csub_local := TPathsDToCPathsD(sub);
907+
csol_extern := RectClipD(rec, csub_local, 2, true);
908+
sol1 := CPathsDToPathsD(csol_extern);
909+
DisposeExportedCPathsD(csol_extern);
876910

877-
csol_extern := RectClip64(rec, csub_local, false);
878-
sol2 := CPaths64ToPaths64(csol_extern);
911+
// do the DLL operation again with ConvexOnly disabled
912+
csol_extern := RectClipD(rec, csub_local, 2, false);
913+
sol2 := CPathsDToPathsD(csol_extern);
914+
{$ENDIF}
879915

880-
// display result on the console
881-
WriteLn(#10' solution:');
882-
WriteCPaths64(csol_extern);
883916

884917
// display and clean up
885-
886918
sol2_len := Length(sol2);
887919
if sol2_len = 0 then
888920
frac := 0 else
@@ -892,47 +924,35 @@ begin
892924
fillClr := (penClr and $FFFFFF) or $20000000;
893925

894926
SetLength(clp, 1);
895-
clp[0] := RectToPath64(rec);
927+
clp[0] := rec.AsPath;
896928

897929
svg := TSvgWriter.Create(fillrule);
898930
try
899931
AddSubject(svg, sub);
900932
AddClip(svg, clp);
901933

902934
// display the unclipped paths
903-
SaveSvg(svg, 'RectClip64_1.svg', displayWidth, displayHeight);
935+
SaveSvg(svg, 'RectClip64_1.svg', w, h);
904936
ShowSvgImage('RectClip64_1.svg');
905937

906-
// display the first RectClip operation (ignores concavity)
938+
// display the first RectClip operation (assumes ConvexOnly)
907939
AddSolution(svg, sol1);
908-
SaveSvg(svg, 'RectClip64_2.svg', displayWidth, displayHeight);
940+
SaveSvg(svg, 'RectClip64_2.svg', w, h);
909941
ShowSvgImage('RectClip64_2.svg');
910942

911943
svg.ClearPaths;
912944
// display the second RectClip operation (manages convexity)
913945
AddSubject(svg, sub);
914946
AddClip(svg, clp);
915947
AddSolution(svg, sol2);
916-
SaveSvg(svg, 'RectClip64_3.svg', displayWidth, displayHeight);
948+
SaveSvg(svg, 'RectClip64_3.svg', w, h);
917949
ShowSvgImage('RectClip64_3.svg');
918-
919-
svg.ClearPaths;
920-
// display the second RectClip operation (manages convexity)
921-
// but this time showing solutions with multi-colored paths
922-
AddSubject(svg, sub);
923-
AddClip(svg, clp);
924-
for i := 0 to sol2_len -1 do
925-
svg.AddPath(sol2[i], false, fillClr, penClr, 1.5, false);
926-
927-
SaveSvg(svg, 'RectClip64_4.svg', displayWidth, displayHeight);
928-
ShowSvgImage('RectClip64_4.svg');
929-
930950
finally
931951
svg.Free;
932952
end;
933953

934-
DisposeLocalCPaths64(csub_local);
935-
DisposeExportedCPaths64(csol_extern);
954+
DisposeLocalCPathsD(csub_local);
955+
DisposeExportedCPathsD(csol_extern);
936956
end;
937957

938958
procedure Test_RectClipLines64(edgeCnt: integer);
@@ -964,7 +984,7 @@ begin
964984
// finally, display and clean up
965985

966986
SetLength(clp, 1);
967-
clp[0] := RectToPath64(rec);
987+
clp[0] := rec.AsPath;
968988

969989
svg := TSvgWriter.Create(frNonZero);
970990
try
@@ -989,6 +1009,9 @@ var
9891009
csub_local, cclp_local: CPaths64;
9901010
csol_extern, csolo_extern: CPaths64;
9911011
svg: TSvgWriter;
1012+
const
1013+
w = 800;
1014+
h = 600;
9921015
begin
9931016
csolo_extern := nil;
9941017
WriteLn(#10'Testing Performance');
@@ -997,9 +1020,9 @@ begin
9971020
Write(format(#10' C++ DLL - %d edges: ', [i*1000]));
9981021
// setup
9991022
SetLength(sub, 1);
1000-
sub[0] := MakeRandomPath(displayWidth, displayHeight, i*1000);
1023+
sub[0] := MakeRandomPath(w, h, i*1000);
10011024
SetLength(clp, 1);
1002-
clp[0] := MakeRandomPath(displayWidth, displayHeight, i*1000);
1025+
clp[0] := MakeRandomPath(w, h, i*1000);
10031026
// convert paths into DLL structures (will require local clean up)
10041027
csub_local := TPaths64ToCPaths64(sub);
10051028
cclp_local := TPaths64ToCPaths64(clp);
@@ -1031,7 +1054,7 @@ begin
10311054
AddSubject(svg, sub);
10321055
AddClip(svg, clp);
10331056
AddSolution(svg, CPaths64ToPaths64(csol_extern));
1034-
SaveSvg(svg, 'Performance.svg', displayWidth, displayHeight);
1057+
SaveSvg(svg, 'Performance.svg', w, h);
10351058
ShowSvgImage('Performance.svg');
10361059
finally
10371060
svg.Free;
@@ -1059,11 +1082,11 @@ begin
10591082
// Test_BooleanOp64(50);
10601083
// Test_BooleanOpD(75);
10611084
// Test_BooleanOpPtD(20);
1062-
// Test_InflatePaths64(-10);
1063-
Test_RectClip64(25);
1085+
Test_InflatePathsD(20, -10); // edgeCount, offsetDist
1086+
// Test_RectClipD(15);
10641087
// Test_RectClipLines64(25);
1065-
//Test_Performance(1, 5); // 1000 t0 5000
1066-
//Test_MegaStress(10000);
1088+
// Test_Performance(1, 5); // 1000 to 5000
1089+
// Test_MegaStress(10000);
10671090

10681091
WriteLn(#10'Press Enter to quit.');
10691092
ReadLn(s);

Delphi/Clipper2Lib/Clipper.Offset.pas

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
(*******************************************************************************
44
* Author : Angus Johnson *
5-
* Date : 21 February 2023 *
5+
* Date : 23 February 2023 *
66
* Website : http://www.angusj.com *
77
* Copyright : Angus Johnson 2010-2023 *
88
* Purpose : Path Offset (Inflate/Shrink) *
@@ -761,6 +761,7 @@ procedure TClipperOffset.OffsetPoint(j: Integer; var k: integer);
761761
if ValueAlmostZero(cosA - 1, 0.01) then // almost straight
762762
begin
763763
AddPoint(GetPerpendic(fInPath[j], fNorms[k], fGroupDelta));
764+
AddPoint(GetPerpendic(fInPath[j], fNorms[j], fGroupDelta)); // (#418)
764765
end
765766
else if not ValueAlmostZero(cosA + 1, 0.01) and
766767
(sinA * fGroupDelta < 0) then // is concave

0 commit comments

Comments
 (0)