doublecmd/components/Image32/source/Clipper2/Clipper.pas
2025-03-23 21:11:06 +03:00

972 lines
30 KiB
ObjectPascal

unit Clipper;
(*******************************************************************************
* Author : Angus Johnson *
* Date : 7 May 2024 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2024 *
* Purpose : This module provides a simple interface to the Clipper Library *
* License : http://www.boost.org/LICENSE_1_0.txt *
*******************************************************************************)
interface
{$I Clipper.inc}
uses
Math, SysUtils, Classes,
Clipper.Core, Clipper.Engine, Clipper.Offset, Clipper.RectClip;
// A number of structures defined in other units are redeclared here
// so those units won't also need to be declared in your own units clauses.
type
TClipper = Clipper.Engine.TClipper64;
TClipper64 = Clipper.Engine.TClipper64;
TPoint64 = Clipper.Core.TPoint64;
TRect64 = Clipper.Core.TRect64;
TPath64 = Clipper.Core.TPath64;
TPaths64 = Clipper.Core.TPaths64;
TPointD = Clipper.Core.TPointD;
TRectD = Clipper.Core.TRectD;
TPathD = Clipper.Core.TPathD;
TPathsD = Clipper.Core.TPathsD;
TFillRule = Clipper.Core.TFillRule;
TPolyTree64 = Clipper.Engine.TPolyTree64;
TPolyTreeD = Clipper.Engine.TPolyTreeD;
TJoinType = Clipper.Offset.TJoinType;
TEndType = Clipper.Offset.TEndType;
TArrayOfInt64 = array of Int64;
const
frEvenOdd = Clipper.Core.frEvenOdd;
frNonZero = Clipper.Core.frNonZero;
frPositive = Clipper.Core.frPositive;
frNegative = Clipper.Core.frNegative;
jtBevel = Clipper.Offset.jtBevel;
jtSquare = Clipper.Offset.jtSquare;
jtRound = Clipper.Offset.jtRound;
jtMiter = Clipper.Offset.jtMiter;
etPolygon = Clipper.Offset.etPolygon;
etJoined = Clipper.Offset.etJoined;
etButt = Clipper.Offset.etButt;
etSquare = Clipper.Offset.etSquare;
etRound = Clipper.Offset.etRound;
ctNone = Clipper.Core.ctNoClip;
ctIntersection = Clipper.Core.ctIntersection;
ctUnion = Clipper.Core.ctUnion;
ctDifference = Clipper.Core.ctDifference;
ctXor = Clipper.Core.ctXor;
function BooleanOp(clipType: TClipType;
const subjects, clips: TPaths64; fillRule: TFillRule): TPaths64; overload;
function BooleanOp(clipType: TClipType; const subjects, clips:
TPathsD; fillRule: TFillRule; decimalPrec: integer = 2): TPathsD; overload;
procedure BooleanOp(clipType: TClipType; const subjects, clips: TPaths64;
fillRule: TFillRule; polytree: TPolyTree64); overload;
function Intersect(const subjects, clips: TPaths64;
fillRule: TFillRule): TPaths64; overload;
function Union(const subjects, clips: TPaths64;
fillRule: TFillRule): TPaths64; overload;
function Union(const subjects: TPaths64;
fillRule: TFillRule): TPaths64; overload;
function Difference(const subjects, clips: TPaths64;
fillRule: TFillRule): TPaths64; overload;
function XOR_(const subjects, clips: TPaths64;
fillRule: TFillRule): TPaths64; overload;
function Intersect(const subjects, clips: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD; overload;
function Union(const subjects: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD; overload;
function Union(const subjects, clips: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD; overload;
function Difference(const subjects, clips: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD; overload;
function XOR_(const subjects, clips: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD; overload;
function InflatePaths(const paths: TPaths64; delta: Double;
jt: TJoinType = jtRound; et: TEndType = etPolygon;
MiterLimit: double = 2.0; ArcTolerance: double = 0.0): TPaths64; overload;
function InflatePaths(const paths: TPathsD; delta: Double;
jt: TJoinType = jtRound; et: TEndType = etPolygon;
miterLimit: double = 2.0; precision: integer = 2;
ArcTolerance: double = 0.0): TPathsD; overload;
// RectClip: for closed paths only (otherwise use RectClipLines)
function RectClip(const rect: TRect64; const path: TPath64): TPath64; overload;
function RectClip(const rect: TRect64; const paths: TPaths64): TPaths64; overload;
function RectClip(const rect: TRectD; const path: TPathD; precision: integer = 2): TPathD; overload;
function RectClip(const rect: TRectD; const paths: TPathsD; precision: integer = 2): TPathsD; overload;
function RectClipLines(const rect: TRect64;
const path: TPath64): TPaths64; overload;
function RectClipLines(const rect: TRect64;
const paths: TPaths64): TPaths64; overload;
function RectClipLines(const rect: TRectD; const path: TPathD;
precision: integer = 2): TPathsD; overload;
function RectClipLines(const rect: TRectD; const paths: TPathsD;
precision: integer = 2): TPathsD; overload;
function TranslatePath(const path: TPath64; dx, dy: Int64): TPath64; overload;
function TranslatePath(const path: TPathD; dx, dy: double): TPathD; overload;
function TranslatePaths(const paths: TPaths64; dx, dy: Int64): TPaths64; overload;
function TranslatePaths(const paths: TPathsD; dx, dy: double): TPathsD; overload;
function MinkowskiSum(const pattern, path: TPath64;
pathIsClosed: Boolean): TPaths64; overload;
function MinkowskiSum(const pattern, path: TPathD;
pathIsClosed: Boolean): TPathsD; overload;
function PolyTreeToPaths64(PolyTree: TPolyTree64): TPaths64;
function PolyTreeToPathsD(PolyTree: TPolyTreeD): TPathsD;
function PathToString(const p: TPath64;
indentSpaces: integer = 0; pointsPerRow: integer = 0): string; overload;
function PathToString(const p: TPathD; decimals: integer;
indentSpaces: integer = 0; pointsPerRow: integer = 0): string; overload;
function PathsToString(const p: TPaths64;
indentSpaces: integer = 0; pointsPerRow: integer = 0): string; overload;
function PathsToString(const p: TPathsD; decimals: integer;
indentSpaces: integer = 0; pointsPerRow: integer = 0): string; overload;
//ShowPolyTreeStructure: only useful when debugging
procedure ShowPolyTreeStructure(polytree: TPolyTree64; strings: TStrings); overload;
procedure ShowPolyTreeStructure(polytree: TPolyTreeD; strings: TStrings); overload;
function MakePath(const ints: array of Int64): TPath64; overload;
function MakePathD(const dbls: array of double): TPathD; overload;
function TrimCollinear(const p: TPath64;
isOpenPath: Boolean = false): TPath64; overload;
function TrimCollinear(const path: TPathD;
precision: integer; isOpenPath: Boolean = false): TPathD; overload;
function PointInPolygon(const pt: TPoint64; const polygon: TPath64):
TPointInPolygonResult;
function SimplifyPath(const path: TPath64;
shapeTolerance: double; isClosedPath: Boolean = true): TPath64; overload;
function SimplifyPaths(const paths: TPaths64;
shapeTolerance: double; isClosedPath: Boolean = true): TPaths64; overload;
function SimplifyPath(const path: TPathD; shapeTolerance: double;
isClosedPath: Boolean = true; decimalPrecision: integer = 2): TPathD; overload;
function SimplifyPaths(const paths: TPathsD; shapeTolerance: double;
isClosedPath: Boolean = true; decimalPrecision: integer = 2): TPathsD; overload;
implementation
uses
Clipper.Minkowski;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
{$IFDEF USINGZ}
function MakePath(const ints: array of Int64): TPath64;
var
i, len: integer;
begin
len := length(ints) div 3;
SetLength(Result, len);
for i := 0 to len -1 do
begin
Result[i].X := ints[i*3];
Result[i].Y := ints[i*3 +1];
Result[i].z := ints[i*3 +2];
end;
end;
//------------------------------------------------------------------------------
function MakePathD(const dbls: array of double): TPathD; overload;
var
i, len: integer;
begin
len := length(dbls) div 3;
SetLength(Result, len);
for i := 0 to len -1 do
begin
Result[i].X := dbls[i*3];
Result[i].Y := dbls[i*3 +1];
Result[i].Z := Round(dbls[i*3 +2]);
end;
end;
//------------------------------------------------------------------------------
{$ELSE}
function MakePath(const ints: array of Int64): TPath64;
var
i, len: integer;
begin
len := length(ints) div 2;
SetLength(Result, len);
for i := 0 to len -1 do
begin
Result[i].X := ints[i*2];
Result[i].Y := ints[i*2 +1];
end;
end;
//------------------------------------------------------------------------------
function MakePathD(const dbls: array of double): TPathD; overload;
var
i, len: integer;
begin
len := length(dbls) div 2;
SetLength(Result, len);
for i := 0 to len -1 do
begin
Result[i].X := dbls[i*2];
Result[i].Y := dbls[i*2 +1];
end;
end;
//------------------------------------------------------------------------------
{$ENDIF}
procedure AddPolyNodeToPaths(Poly: TPolyPath64; var Paths: TPaths64);
var
i: Integer;
begin
if (Length(Poly.Polygon) > 0) then
begin
i := Length(Paths);
SetLength(Paths, i +1);
Paths[i] := Poly.Polygon;
end;
for i := 0 to Poly.Count - 1 do
AddPolyNodeToPaths(Poly[i], Paths);
end;
//------------------------------------------------------------------------------
function PolyTreeToPaths64(PolyTree: TPolyTree64): TPaths64;
begin
Result := nil;
AddPolyNodeToPaths(PolyTree, Result);
end;
//------------------------------------------------------------------------------
procedure AddPolyNodeToPathsD(Poly: TPolyPathD; var Paths: TPathsD);
var
i: Integer;
begin
if (Length(Poly.Polygon) > 0) then
begin
i := Length(Paths);
SetLength(Paths, i +1);
Paths[i] := Poly.Polygon;
end;
for i := 0 to Poly.Count - 1 do
AddPolyNodeToPathsD(Poly[i], Paths);
end;
//------------------------------------------------------------------------------
function PolyTreeToPathsD(PolyTree: TPolyTreeD): TPathsD;
begin
Result := nil;
AddPolyNodeToPathsD(PolyTree, Result);
end;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
function BooleanOp(clipType: TClipType;
const subjects, clips: TPaths64; fillRule: TFillRule): TPaths64;
begin
with TClipper64.Create do
try
AddSubject(subjects);
AddClip(clips);
Execute(clipType, fillRule, Result);
finally
Free;
end;
end;
//------------------------------------------------------------------------------
function BooleanOp(clipType: TClipType; const subjects, clips: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD;
begin
with TClipperD.Create(decimalPrec) do
try
AddSubject(subjects);
AddClip(clips);
Execute(clipType, fillRule, Result);
finally
Free;
end;
end;
//------------------------------------------------------------------------------
procedure BooleanOp(clipType: TClipType; const subjects, clips: TPaths64;
fillRule: TFillRule; polytree: TPolyTree64);
var
dummy: TPaths64;
begin
with TClipper64.Create do
try
AddSubject(subjects);
AddClip(clips);
Execute(clipType, fillRule, polytree, dummy);
finally
Free;
end;
end;
//------------------------------------------------------------------------------
function Intersect(const subjects, clips: TPaths64; fillRule: TFillRule): TPaths64;
begin
Result := BooleanOp(ctIntersection, subjects, clips, fillRule);
end;
//------------------------------------------------------------------------------
function Union(const subjects, clips: TPaths64; fillRule: TFillRule): TPaths64;
begin
Result := BooleanOp(ctUnion, subjects, clips, fillRule);
end;
//------------------------------------------------------------------------------
function Union(const subjects: TPaths64; fillRule: TFillRule): TPaths64;
begin
Result := BooleanOp(ctUnion, subjects, nil, fillRule);
end;
//------------------------------------------------------------------------------
function Difference(const subjects, clips: TPaths64; fillRule: TFillRule): TPaths64;
begin
Result := BooleanOp(ctDifference, subjects, clips, fillRule);
end;
//------------------------------------------------------------------------------
function XOR_(const subjects, clips: TPaths64; fillRule: TFillRule): TPaths64;
begin
Result := BooleanOp(ctXor, subjects, clips, fillRule);
end;
//------------------------------------------------------------------------------
function Intersect(const subjects, clips: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD;
begin
Result := BooleanOp(ctIntersection, subjects, clips, fillRule, decimalPrec);
end;
//------------------------------------------------------------------------------
function Union(const subjects, clips: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD;
begin
Result := BooleanOp(ctUnion, subjects, clips, fillRule, decimalPrec);
end;
//------------------------------------------------------------------------------
function Union(const subjects: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD;
begin
Result := BooleanOp(ctUnion, subjects, nil, fillRule, decimalPrec);
end;
//------------------------------------------------------------------------------
function Difference(const subjects, clips: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD;
begin
Result := BooleanOp(ctDifference, subjects, clips, fillRule, decimalPrec);
end;
//------------------------------------------------------------------------------
function XOR_(const subjects, clips: TPathsD;
fillRule: TFillRule; decimalPrec: integer = 2): TPathsD;
begin
Result := BooleanOp(ctXor, subjects, clips, fillRule, decimalPrec);
end;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
function InflatePaths(const paths: TPaths64; delta: Double;
jt: TJoinType; et: TEndType; MiterLimit: double;
ArcTolerance: double): TPaths64;
var
co: TClipperOffset;
begin
co := TClipperOffset.Create(MiterLimit, ArcTolerance);
try
co.AddPaths(paths, jt, et);
co.Execute(delta, Result);
finally
co.free;
end;
end;
//------------------------------------------------------------------------------
function InflatePaths(const paths: TPathsD; delta: Double;
jt: TJoinType; et: TEndType; miterLimit: double;
precision: integer; ArcTolerance: double): TPathsD;
var
pp: TPaths64;
scale, invScale: double;
begin
CheckPrecisionRange(precision);
scale := Power(10, precision);
invScale := 1/scale;
pp := ScalePaths(paths, scale, scale);
with TClipperOffset.Create(miterLimit, ArcTolerance) do
try
AddPaths(pp, jt, et);
Execute(delta * scale, pp); // reuse pp to receive the solution.
finally
free;
end;
Result := ScalePathsD(pp, invScale, invScale);
end;
//------------------------------------------------------------------------------
function RectClip(const rect: TRect64;
const path: TPath64): TPath64;
var
paths: TPaths64;
begin
SetLength(paths, 1);
paths[0] := path;
paths := RectClip(rect, paths);
if Assigned(paths) then
Result := paths[0] else
Result := nil;
end;
//------------------------------------------------------------------------------
function RectClip(const rect: TRect64; const paths: TPaths64): TPaths64;
begin
Result := nil;
if rect.IsEmpty then Exit;
with TRectClip64.Create(rect) do
try
Result := Execute(paths);
finally
Free;
end;
end;
//------------------------------------------------------------------------------
function RectClip(const rect: TRectD; const path: TPathD; precision: integer): TPathD;
var
scale: double;
tmpPath: TPath64;
rec: TRect64;
begin
Result := nil;
if not rect.Intersects(GetBounds(path)) then Exit;
CheckPrecisionRange(precision);
scale := Math.Power(10, precision);
rec := Rect64(ScaleRect(rect, scale));
tmpPath := ScalePath(path, scale);
tmpPath := RectClip(rec, tmpPath);
Result := ScalePathD(tmpPath, 1/scale);
end;
//------------------------------------------------------------------------------
function RectClip(const rect: TRectD; const paths: TPathsD; precision: integer): TPathsD;
var
scale: double;
tmpPaths: TPaths64;
rec: TRect64;
begin
CheckPrecisionRange(precision);
scale := Math.Power(10, precision);
rec := Rect64(ScaleRect(rect, scale));
tmpPaths := ScalePaths(paths, scale);
with TRectClip64.Create(rec) do
try
tmpPaths := Execute(tmpPaths);
finally
Free;
end;
Result := ScalePathsD(tmpPaths, 1/scale);
end;
//------------------------------------------------------------------------------
function RectClipLines(const rect: TRect64; const path: TPath64): TPaths64;
var
tmp: TPaths64;
begin
Result := nil;
SetLength(tmp, 1);
tmp[0] := path;
with TRectClipLines64.Create(rect) do
try
Result := Execute(tmp);
finally
Free;
end;
end;
//------------------------------------------------------------------------------
function RectClipLines(const rect: TRect64; const paths: TPaths64): TPaths64;
begin
Result := nil;
if rect.IsEmpty then Exit;
with TRectClipLines64.Create(rect) do
try
Result := Execute(paths);
finally
Free;
end;
end;
//------------------------------------------------------------------------------
function RectClipLines(const rect: TRectD;
const path: TPathD; precision: integer): TPathsD;
var
scale: double;
tmpPath: TPath64;
tmpPaths: TPaths64;
rec: TRect64;
begin
Result := nil;
if not rect.Intersects(GetBounds(path)) then Exit;
CheckPrecisionRange(precision);
scale := Math.Power(10, precision);
rec := Rect64(ScaleRect(rect, scale));
tmpPath := ScalePath(path, scale);
tmpPaths := RectClipLines(rec, tmpPath);
Result := ScalePathsD(tmpPaths, 1/scale);
end;
//------------------------------------------------------------------------------
function RectClipLines(const rect: TRectD; const paths: TPathsD;
precision: integer = 2): TPathsD;
var
scale: double;
tmpPaths: TPaths64;
rec: TRect64;
begin
Result := nil;
if rect.IsEmpty then Exit;
CheckPrecisionRange(precision);
scale := Math.Power(10, precision);
rec := Rect64(ScaleRect(rect, scale));
tmpPaths := ScalePaths(paths, scale);
with TRectClipLines64.Create(rec) do
try
tmpPaths := Execute(tmpPaths);
finally
Free;
end;
Result := ScalePathsD(tmpPaths, 1/scale);
end;
//------------------------------------------------------------------------------
function TranslatePath(const path: TPath64; dx, dy: Int64): TPath64;
var
i, len: integer;
begin
len := length(path);
setLength(result, len);
for i := 0 to len -1 do
begin
result[i].x := path[i].x + dx;
result[i].y := path[i].y + dy;
end;
end;
//------------------------------------------------------------------------------
function TranslatePath(const path: TPathD; dx, dy: double): TPathD;
var
i, len: integer;
begin
len := length(path);
setLength(result, len);
for i := 0 to len -1 do
begin
result[i].x := path[i].x + dx;
result[i].y := path[i].y + dy;
end;
end;
//------------------------------------------------------------------------------
function TranslatePaths(const paths: TPaths64; dx, dy: Int64): TPaths64;
var
i, len: integer;
begin
len := length(paths);
setLength(result, len);
for i := 0 to len -1 do
begin
result[i] := TranslatePath(paths[i], dx, dy);
end;
end;
//------------------------------------------------------------------------------
function TranslatePaths(const paths: TPathsD; dx, dy: double): TPathsD;
var
i, len: integer;
begin
len := length(paths);
setLength(result, len);
for i := 0 to len -1 do
begin
result[i] := TranslatePath(paths[i], dx, dy);
end;
end;
//------------------------------------------------------------------------------
function MinkowskiSum(const pattern, path: TPath64;
pathIsClosed: Boolean): TPaths64;
begin
Result := Clipper.Minkowski.MinkowskiSum(pattern, path, pathIsClosed);
end;
//------------------------------------------------------------------------------
function MinkowskiSum(const pattern, path: TPathD;
pathIsClosed: Boolean): TPathsD;
begin
Result := Clipper.Minkowski.MinkowskiSum(pattern, path, pathIsClosed);
end;
//------------------------------------------------------------------------------
function PathToString(const p: TPath64;
indentSpaces: integer; pointsPerRow: integer): string;
var
i, highI: Integer;
spaces: string;
begin
spaces := StringOfChar(' ', indentSpaces);
Result := spaces;
highI := high(p);
if highI < 0 then Exit;
for i := 0 to highI -1 do
begin
Result := Result + format('%d,%d, ',[p[i].X,p[i].Y]);
if (pointsPerRow > 0) and ((i + 1) mod pointsPerRow = 0) then
Result := Result + #10 + spaces;
end;
Result := Result + format('%d,%d',[p[highI].X,p[highI].Y]);
end;
//------------------------------------------------------------------------------
function PathToString(const p: TPathD; decimals: integer;
indentSpaces: integer; pointsPerRow: integer): string;
var
i, highI: Integer;
spaces: string;
begin
spaces := StringOfChar(' ', indentSpaces);
Result := '';
highI := high(p);
if highI < 0 then Exit;
for i := 0 to highI -1 do
Result := Result + format('%1.*n,%1.*n, ',
[decimals, p[i].X, decimals, p[i].Y]);
Result := Result + format('%1.*n,%1.*n',[
decimals, p[highI].X, decimals, p[highI].Y]);
end;
//------------------------------------------------------------------------------
function PathsToString(const p: TPaths64;
indentSpaces: integer = 0; pointsPerRow: integer = 0): string;
var
i: integer;
begin
Result := '';
for i := 0 to High(p) do
Result := Result + PathToString(p[i], indentSpaces, pointsPerRow) + #10#10;
end;
//------------------------------------------------------------------------------
function PathsToString(const p: TPathsD; decimals: integer;
indentSpaces: integer = 0; pointsPerRow: integer = 0): string;
var
i: integer;
begin
Result := '';
for i := 0 to High(p) do
Result := Result + PathToString(p[i], indentSpaces, pointsPerRow) + #10#10;
end;
//------------------------------------------------------------------------------
procedure ShowPolyPathStructure64(pp: TPolyPath64; level: integer;
strings: TStrings);
var
i: integer;
spaces, plural: string;
begin
spaces := StringOfChar(' ', level * 2);
if pp.Count = 1 then plural := '' else plural := 's';
if pp.IsHole then
strings.Add(Format('%sA hole containing %d polygon%s', [spaces, pp.Count, plural]))
else
strings.Add(Format('%sA polygon containing %d hole%s', [spaces, pp.Count, plural]));
for i := 0 to pp.Count -1 do
if pp.child[i].Count> 0 then
ShowPolyPathStructure64(pp.child[i], level + 1, strings);
end;
//------------------------------------------------------------------------------
procedure ShowPolyTreeStructure(polytree: TPolyTree64; strings: TStrings);
var
i: integer;
begin
if polytree.Count = 1 then
strings.Add('Polytree with just 1 polygon.') else
strings.Add(Format('Polytree with just %d polygons.', [polytree.Count]));
for i := 0 to polytree.Count -1 do
if polytree[i].Count > 0 then
ShowPolyPathStructure64(polytree[i], 1, strings);
end;
//------------------------------------------------------------------------------
procedure ShowPolyPathStructureD(pp: TPolyPathD; level: integer; strings: TStrings);
var
i: integer;
spaces, plural: string;
begin
spaces := StringOfChar(' ', level * 2);
if pp.Count = 1 then plural := '' else plural := 's';
if pp.IsHole then
strings.Add(Format('%sA hole containing %d polygon%s', [spaces, pp.Count, plural]))
else
strings.Add(Format('%sA polygon containing %d hole%s', [spaces, pp.Count, plural]));
for i := 0 to pp.Count -1 do
if pp.child[i].Count> 0 then
ShowPolyPathStructureD(pp.child[i], level + 1, strings);
end;
//------------------------------------------------------------------------------
procedure ShowPolyTreeStructure(polytree: TPolyTreeD; strings: TStrings);
var
i: integer;
begin
if polytree.Count = 1 then
strings.Add('Polytree with just 1 polygon.') else
strings.Add(Format('Polytree with just %d polygons.', [polytree.Count]));
for i := 0 to polytree.Count -1 do
if polytree[i].Count > 0 then
ShowPolyPathStructureD(polytree[i], 1, strings);
end;
//------------------------------------------------------------------------------
function TrimCollinear(const p: TPath64; isOpenPath: Boolean = false): TPath64;
var
i,j, len: integer;
begin
len := Length(p);
i := 0;
if not isOpenPath then
begin
while (i < len -1) and
IsCollinear(p[len -1], p[i], p[i+1]) do inc(i);
while (i < len -1) and
IsCollinear(p[len -2], p[len -1], p[i]) do dec(len);
end;
if (len - i < 3) then
begin
if not isOpenPath or (len < 2) or PointsEqual(p[0], p[1]) then
Result := nil else
Result := p;
Exit;
end;
SetLength(Result, len -i);
Result[0] := p[i];
j := 0;
for i := i+1 to len -2 do
if not IsCollinear(result[j], p[i], p[i+1]) then
begin
inc(j);
result[j] := p[i];
end;
if isOpenPath then
begin
inc(j);
result[j] := p[len-1];
end
else if not IsCollinear(result[j], p[len-1], result[0]) then
begin
inc(j);
result[j] := p[len-1];
end else
begin
while (j > 1) and
IsCollinear(result[j-1], result[j], result[0]) do dec(j);
if j < 2 then j := -1;
end;
SetLength(Result, j +1);
end;
//------------------------------------------------------------------------------
function TrimCollinear(const path: TPathD;
precision: integer; isOpenPath: Boolean = false): TPathD;
var
p: TPath64;
scale: double;
begin
scale := power(10, precision);
p := ScalePath(path, scale);
p := TrimCollinear(p, isOpenPath);
Result := ScalePathD(p, 1/scale);
end;
//------------------------------------------------------------------------------
function PointInPolygon(const pt: TPoint64;
const polygon: TPath64): TPointInPolygonResult;
begin
Result := Clipper.Core.PointInPolygon(pt, polygon);
end;
//------------------------------------------------------------------------------
function DistanceSqrd(const pt1, pt2: TPoint64): double;
{$IFDEF INLINE} inline; {$ENDIF}
var
x1,y1,x2,y2: double;
begin
// nb: older versions of Delphi don't allow explicit typcasting
x1 := pt1.X; y1 := pt1.Y;
x2 := pt2.X; y2 := pt2.Y;
result := Sqr(x1 - x2) + Sqr(y1 - y2);
end;
//------------------------------------------------------------------------------
function PerpendicDistSqrd(const pt, line1, line2: TPoint64): double;
{$IFDEF INLINE} inline; {$ENDIF}
var
a,b,c,d: double;
begin
a := pt.X - line1.X;
b := pt.Y - line1.Y;
c := line2.X - line1.X;
d := line2.Y - line1.Y;
result := Iif((c = 0) and (d = 0),
0, Sqr(a * d - c * b) / (c * c + d * d));
end;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
type
PSimplifyRec = ^TSimplifyRec;
TSimplifyRec = record
pt : TPoint64;
pdSqrd : double;
prev : PSimplifyRec;
next : PSimplifyRec;
//isEnd : Boolean;
end;
function SimplifyPath(const path: TPath64;
shapeTolerance: double; isClosedPath: Boolean): TPath64;
var
i, highI, minHigh: integer;
tolSqrd: double;
srArray: array of TSimplifyRec;
first, last: PSimplifyRec;
begin
Result := nil;
highI := High(path);
minHigh := Iif(isClosedPath, 2, 1);
if highI < minHigh then Exit;
SetLength(srArray, highI +1);
with srArray[0] do
begin
pt := path[0];
prev := @srArray[highI];
next := @srArray[1];
pdSqrd := Iif(isClosedPath,
PerpendicDistSqrd(path[0], path[highI], path[1]), invalidD);
end;
with srArray[highI] do
begin
pt := path[highI];
prev := @srArray[highI-1];
next := @srArray[0];
pdSqrd := Iif(isClosedPath,
PerpendicDistSqrd(path[highI], path[highI-1], path[0]), invalidD);
end;
for i := 1 to highI -1 do
with srArray[i] do
begin
pt := path[i];
prev := @srArray[i-1];
next := @srArray[i+1];
pdSqrd := PerpendicDistSqrd(path[i], path[i-1], path[i+1]);
end;
first := @srArray[0];
last := first.prev;
tolSqrd := Sqr(shapeTolerance);
while first <> last do
begin
if (first.pdSqrd > tolSqrd) or
(first.next.pdSqrd < first.pdSqrd) then
begin
first := first.next;
Continue;
end;
dec(highI);
first.prev.next := first.next;
first.next.prev := first.prev;
last := first.prev;
first := last.next;
if first.next = first.prev then break;
last.pdSqrd := PerpendicDistSqrd(last.pt, last.prev.pt, first.pt);
first.pdSqrd := PerpendicDistSqrd(first.pt, last.pt, first.next.pt);
end;
if highI < minHigh then Exit;
if not isClosedPath then first := @srArray[0];
SetLength(Result, highI +1);
for i := 0 to HighI do
begin
Result[i] := first.pt;
first := first.next;
end;
end;
//------------------------------------------------------------------------------
function SimplifyPaths(const paths: TPaths64;
shapeTolerance: double; isClosedPath: Boolean): TPaths64;
var
i, len: integer;
begin
len := Length(paths);
SetLength(Result, len);
for i := 0 to len -1 do
result[i] := SimplifyPath(paths[i], shapeTolerance, isClosedPath);
end;
//------------------------------------------------------------------------------
function SimplifyPath(const path: TPathD; shapeTolerance: double;
isClosedPath: Boolean; decimalPrecision: integer): TPathD;
var
p: TPath64;
scale: double;
begin
scale := power(10, decimalPrecision);
p := ScalePath(path, scale);
p := SimplifyPath(p, shapeTolerance, isClosedPath);
Result := ScalePathD(p, 1/scale);
end;
//------------------------------------------------------------------------------
function SimplifyPaths(const paths: TPathsD; shapeTolerance: double;
isClosedPath: Boolean; decimalPrecision: integer): TPathsD;
var
pp: TPaths64;
scale: double;
begin
scale := power(10, decimalPrecision);
pp := ScalePaths(paths, scale);
pp := SimplifyPaths(pp, shapeTolerance, isClosedPath);
Result := ScalePathsD(pp, 1/scale);
end;
//------------------------------------------------------------------------------
end.