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.ctNone;
  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.


