{
  rr.inc:	Include file, defines conditional compilation symbols
		for Borland Delphi / C++Builder RAD tools

  Author:       Robert RoЯmair, Robert.Rossmair@t-online.de

  Version:      1.5, 24-Mar-2001

  Predefined compiler version symbols are:
  (VERxyz means compiler version xy.z)

  VER80		Delphi 1.0
  VER90		Delphi 2.0
  VER93		C++Builder 1.0
  VER100	Delphi 3.0
  VER110	C++Builder 3.0
  VER120	Delphi 4.0
  VER125	C++Builder 4.0
  VER130	Delphi 5.0
  VER135	C++Builder 5.0
  VER140        Kylix 1.0
}

{$B-} { Complete Boolean Evaluation }
{$R-} { Range-Checking }
{$V-} { Var-String Checking }
{$T-} { Typed @ operator }
{$X+} { Extended syntax }
{$P+} { Open string params }
{$IFDEF WIN32}
{$J+} { Writeable structured consts }
{$H+} { Use long strings by default }
{$ENDIF}

{$DEFINE VER90_up}
{$DEFINE VER93_up}
{$DEFINE VER100_up}
{$DEFINE VER110_up}
{$DEFINE VER120_up}
{$DEFINE VER125_up}
{$DEFINE VER130_up}
{$DEFINE VER135_up}
{$DEFINE VER140_up}

{$IFNDEF VER140}
  {$UNDEF VER140_up}
  {$IFNDEF VER135}
    {$UNDEF VER135_up}
    {$IFNDEF VER130}
      {$UNDEF VER130_up}
      {$IFNDEF VER125}
        {$UNDEF VER125_up}
        {$IFNDEF VER120}
          {$UNDEF VER120_up}
          {$IFNDEF VER110}
            {$UNDEF VER110_up}
            {$IFNDEF VER100}
              {$UNDEF VER100_up}
              {$IFNDEF VER93}
                {$UNDEF VER93_up}
                {$IFNDEF VER90}
                  {$UNDEF VER90_up}
                {$ENDIF}
              {$ENDIF}
            {$ENDIF}
          {$ENDIF}
        {$ENDIF}
      {$ENDIF}
    {$ENDIF}
  {$ENDIF}
{$ENDIF}


{$IFDEF VER93}  { Borland C++Builder 1.0 }
  {$DEFINE CBUILDER}
{$ENDIF}

{$IFDEF VER110} { Borland C++Builder 3.0 }
  {$DEFINE CBUILDER}
{$ENDIF}

{$IFDEF VER125} { Borland C++Builder 4.0 }
  {$DEFINE CBUILDER}
{$ENDIF}

{$IFDEF VER135} { Borland C++Builder 5.0 }
  {$DEFINE CBUILDER}
{$ENDIF}

{$IFDEF VER140} { Borland Kylix 1.0 }
  {$DEFINE Kylix}
{$ENDIF}

{$IFDEF CBUILDER}
  {$IFNDEF VER93}
  {$ObjExportAll On}
  {$ENDIF}
  {$DEFINE NoMath}      { No Math unit }
{$ENDIF}

{$IFDEF VER100_up}		{ Delphi 3 and above }
  {$DEFINE RR_PackageSupport}
  {$DEFINE RR_Interfaces}
  {$DEFINE RR_Resourcestring}
  {$DEFINE RR_EnhancedTBitmap}
  {$DEFINE RR_JPEG}
  {$IFDEF VER110_up}		{ C++Builder 3 and above }
    {$DEFINE RR_DefaultParams}
    {$IFDEF VER120_up}		{ Delphi 4 and above }
      {$DEFINE RR_Overloading}
      {$DEFINE RR_Int64}
      {$IFDEF VER140_up}
        {$DEFINE RR_CLXAvailable}
      {$ENDIF}
    {$ENDIF}
  {$ENDIF}
{$ENDIF}

{$DEFINE RR_PicEditBroken}    { PicEdit unit broken (missing LibConst) }

{$IFNDEF VER100 (not Delphi 3) }
  {$IFNDEF VER110 (not C++Builder 3) }
     {$IFNDEF VER120 (not Delphi 4) }
     {$UNDEF RR_PicEditBroken}
     {$ENDIF}
  {$ENDIF}
{$ENDIF}

{$IFDEF VER100}
  { Graphics.CopyBitmap bug: invalid parameters are passed to
    CreateDIBSection when HandleType = bmDIB and the value of the
    boolean expression (Width*Height=0) changes.
    This will cause an EOutOfResources exception. }
  {$DEFINE RR_CopyBitmapBug}
{$ENDIF}

{$IFDEF Win32}
//  {$DEFINE MSWindows}
{$ENDIF}


(***************************************************************************

     The contents of this file are subject to the Mozilla Public License
     Version 1.0 (the "License"); you may not use this file except in
     compliance with the License. You may obtain a copy of the License at
     http://www.mozilla.org/MPL/

     Software distributed under the License is distributed on an "AS IS"
     basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
     License for the specific language governing rights and limitations
     under the License.

     The Original Code is rrEllipse.pas.

     The Initial Developer of the Original Code is Robert Rossmair.

	mailto:Robert.Rossmair@t-online.de
	http://home.t-online.de/home/Robert.Rossmair/

     Portions created by Robert Rossmair are Copyright © 1999, 2000
     Robert Rossmair. All Rights Reserved.

     Contributor(s): ______________________________________.

     Compatibility: Delphi 4(?), Delphi 5, Kylix 1

     Last Modified: March 24, 2001

 ***************************************************************************)

{.$DEFINE Debug}
{$IFDEF Win32}
  {$DEFINE MSWindows}
{$ENDIF}

unit rrEllipses;

interface

uses
{$IFDEF MSWindows}
  Windows, Graphics,
{$ELSE}
  Types, Qt, QGraphics,
{$ENDIF}
  Math,
  SysUtils;

type
  T2DVector = packed record
    X, Y: Double;
  end;
  T2DPoint = T2DVector;

  T2DPointArray = array of T2DPoint;

  TCubicBezierCurve = array[0..3] of T2DPoint;
  PCubicBezierCurve = ^TCubicBezierCurve;

{$IFDEF MSWindows}
procedure CircularArc(DC: hDC; X, Y, Radius, BegAngle, EndAngle: Double);
{$ENDIF}
procedure DrawCircularArc(Canvas: TCanvas; X, Y, Radius, BegAngle, EndAngle: Double);

{ Draw elliptical arc rotated by angle Angle;

  X, Y: Center coordinates
  A, B: Half axis lengths

  BegAngle, EndAngle are relative to Angle }

{$IFDEF MSWindows}
procedure EllipticalArc(DC: hDC; X, Y, A, B, Angle, BegAngle, EndAngle: Double);
{$ENDIF}
procedure DrawEllipticalArc(Canvas: TCanvas; X, Y, A, B, Angle, BegAngle, EndAngle: Double);

{ Ellipse approximation by 8 cubic Bezier curves.

  Relative approximation error < 4.25E-6
  X, Y: Center coordinates
  A, B: Half axis lengths
  Angle: Angle in radians }

{$IFDEF MSWindows}
procedure EllipseEx(DC: hDC; X, Y, A, B, Angle: Double);
  {$IFDEF RR_Overloading} overload; {$ENDIF}
{$ENDIF}
procedure DrawEllipse(Canvas: TCanvas; X, Y, A, B, Angle: Double);
  {$IFDEF RR_Overloading} overload; {$ENDIF}

{ Ellipse approximation by N cubic Bezier curves, N >= 2

  Relative approximation error <= (1-cos(Pi/N))і/(54*(1+cos(Pi/N)))

  X, Y: Center coordinates
  A, B: Half axis lengths
  Angle: Angle in radians }

{$IFDEF RR_Overloading}
  {$IFDEF MSWindows}
  procedure EllipseEx(DC: hDC; X, Y, A, B, Angle: Double; N: Integer); overload;
  {$ENDIF}
procedure DrawEllipse(Canvas: TCanvas; X, Y, A, B, Angle: Double; N: Integer); overload;
{$ENDIF RR_Overloading}

{ Helper functions }

function FPoint(X, Y: Double): T2DPoint;
function NormAngle(const Angle: Double): Double; // fold angle to [-Pi, Pi[
procedure Rotate(var P: array of T2DVector; Angle: Double);
procedure Scale(var P: array of T2DVector;
  ScaleFactorX, ScaleFactorY: Double);
procedure Translate(var P: array of T2DVector; dX, dY: Double);

procedure BezierArcPoints(
  var Points: T2DPointArray;
  X, Y,
  Radius,
  BegAngle,
  EndAngle: Double);

procedure BezierElpArcPoints(
  var Points: T2DPointArray;
  X, Y,
  aLen,bLen,
  angle,
  BegAngle,
  EndAngle: Double);

procedure GetCircleThruPoints(
  const P1, P2, P3: T2DPoint;
  var Center: T2DPoint;
  var Radius: Double);
{$IFDEF RR_Overloading} overload;

procedure GetCircleThruPoints(
  const X1, Y1, X2, Y2, X3, Y3: Double;
  var CenterX, CenterY: Double;
  var Radius: Double); overload;

procedure GetCircleThruPoints(
  const P1, P2, P3: TPoint;
  var Center: T2DPoint;
  var Radius: Double); overload;

{$ENDIF RR_Overloading}
var
  DxfMode: Boolean = False;
  DxfMode16: Boolean = False;
  DxfMode32: Boolean = False;

implementation
//uses DrawObjects;
{$IFDEF RR_Resourcestring}
resourcestring
{$ELSE}
const
{$ENDIF}
  SPointsOnStraightLine = 'GetCircleThruPoints: Points lie on straight line';

const
  Pi2 = 2*Pi;
  Inv2Pi = 0.5/Pi;
  Epsilon = 1E-12;

//var
//  DxfMode: Boolean = False;

{$IFDEF RR_Overloading}
{$IFDEF MSWindows}
procedure DrawEllipse(Canvas: TCanvas; X, Y, A, B, Angle: Double; N: Integer);
begin
  EllipseEx(Canvas.Handle, X, Y, A, B, Angle, N);
end;

procedure EllipseEx(DC: hDC; X, Y, A, B, Angle: Double; N: Integer);
{$ELSE (no MSWindows)}
procedure DrawEllipse(Canvas: TCanvas; X, Y, A, B, Angle: Double; N: Integer);
{$ENDIF MSWindows}
var
  P: array of T2DPoint;
  Q: array of TPoint;
  Alpha, Beta: Double;
  SinA, CosA: Double;
  Temp: Double;
  I, J, K: Integer;
begin
  if N < 2 then N := 2;
  Alpha := Pi/N;
  Beta := 4 * (1-Cos(Alpha)) / (3*Sin(Alpha));
  SetLength(P, N*3+1);
  for I := 0 to N-1 do
  begin
    Temp := I * (2*Alpha);
    CosA := cos(Temp);
    SinA := sin(Temp);
    K := I*3;
    if K = 0
      then J := N*3-1
      else J := K-1;
    P[K].X := A * CosA;
    P[K].Y := B * SinA;
    SinA := Beta * A * SinA;
    CosA := Beta * B * CosA;
    P[J].X := P[K].X + SinA;
    P[J].Y := P[K].Y - CosA;
    P[K+1].X := P[K].X - SinA;
    P[K+1].Y := P[K].Y + CosA;
  end;
  P[N*3] := P[0];
  CosA := cos(Angle);
  SinA := sin(Angle);
  SetLength(Q, N*3+1);
  for I := Low(P) to High(P) do
  // Rotate and translate
  begin
    Q[I].X := Round(P[I].X*CosA - P[I].Y*SinA +X);
    Q[I].Y := Round(P[I].X*SinA + P[I].Y*CosA +Y);
  end;
  {$IFDEF MSWindows}
  // Draw ellipse and fill it using the current pen and brush
  BeginPath(DC);
  PolyBezier(DC, Q, High(Q)+1);
  EndPath(DC);
  StrokeAndFillPath(DC);
  {$ELSE}
  for I := 0 to N-1 do
    Canvas.PolyBezier(Q, I * 3);
  {$ENDIF MSWindows}
end;
{$ENDIF RR_Overloading}

{$IFDEF MSWindows}
procedure DrawEllipse(Canvas: TCanvas; X, Y, A, B, Angle: Double);
begin
  EllipseEx(Canvas.Handle, X, Y, A, B, Angle);
end;

procedure EllipseEx(DC: hDC; X, Y, A, B, Angle: Double);
{$ELSE (no MSWindows)}
procedure DrawEllipse(Canvas: TCanvas; X, Y, A, B, Angle: Double);
{$ENDIF MSWindows}
const
  N = 8; // N Bezier curves

  P: array[0..N*3] of T2DPoint = (  // precomputed unit circle data
  (X:  1;	      Y:  0),
  (X:  1; 	      Y:  0.26521648984),
  (X:  0.89464315963; Y:  0.51957040274),
  (X:  0.70710678119; Y:  0.70710678119),

  (X:  0.51957040274; Y:  0.89464315963),
  (X:  0.26521648984; Y:  1),
  (X:  0; 	      Y:  1),

  (X: -0.26521648984; Y:  1),
  (X: -0.51957040274; Y:  0.89464315963),
  (X: -0.70710678119; Y:  0.70710678119),

  (X: -0.89464315963; Y:  0.51957040274),
  (X: -1;	      Y:  0.26521648984),
  (X: -1;	      Y:  0),

  (X: -1;	      Y: -0.26521648984),
  (X: -0.89464315963; Y: -0.51957040274),
  (X: -0.70710678119; Y: -0.70710678119),

  (X: -0.51957040274; Y: -0.89464315963),
  (X: -0.26521648984; Y: -1),
  (X:  0;	      Y: -1),

  (X:  0.26521648984; Y: -1),
  (X:  0.51957040274; Y: -0.89464315963),
  (X:  0.70710678119; Y: -0.70710678119),

  (X:  0.89464315963; Y: -0.51957040274),
  (X:  1;	      Y: -0.26521648984),
  (X:  1;	      Y: 0));
var
  Q: array[0..N*3] of TPoint;
  SinA, CosA: Double;
  I: Integer;
begin
  CosA := cos(Angle);
  SinA := sin(Angle);
  for i := Low(P) to High(P) do
  // Scale, rotate and translate
  begin
    Q[I].X := Round(P[I].X*A*CosA - P[I].Y*B*SinA +X);
    Q[I].Y := Round(P[I].X*A*SinA + P[I].Y*B*CosA +Y);
  end;
  {$IFDEF MSWindows}
  // Draw ellipse and fill it using the current pen and brush
  BeginPath(DC);
  PolyBezier(DC, Q, High(Q)+1);
  EndPath(DC);
  StrokeAndFillPath(DC);
  {$ELSE}
  for I := 0 to N-1 do
    Canvas.PolyBezier(Q, I * 3);
  {$ENDIF MSWindows}
end;

function FPoint(X, Y: Double): T2DPoint;
begin
  Result.X := X;
  Result.Y := Y;
end;

function NormAngle(const Angle: Double): Double;
begin
  Result := Frac(Angle * Inv2Pi);
  if Result < -0.5 then Result := Result + 1 else
  if Result >= 0.5 then Result := Result - 1;
  Result := Result*Pi2;
end;

procedure Rotate(var P: array of T2DVector; Angle: Double);
var
  I: Integer;
  X, Y: Double;
  SinA: Double;
  CosA: Double;
begin
  SinA := sin(Angle);
  CosA := cos(Angle);
  for I := Low(P) to High(P) do
  begin
    X := P[I].X;
    Y := P[I].Y;
    P[I].X := X * CosA - Y * SinA;
    P[I].Y := X * SinA + Y * CosA;;
  end;
end;

procedure Scale(var P: array of T2DVector; ScaleFactorX, ScaleFactorY: Double);
var
  I: Integer;
begin
  for I := Low(P) to High(P) do
  begin
    P[I].X := P[I].X * ScaleFactorX;
    P[I].Y := P[I].Y * ScaleFactorY;
  end;
end;

procedure Translate(var P: array of T2DVector; dX, dY: Double);
var
  I: Integer;
begin
  for I := Low(P) to High(P) do
  begin
    P[I].X := P[I].X + dX;
    P[I].Y := P[I].Y + dY;
  end;
end;

procedure FitBezierToUnitArc(var Points: T2DPointArray; BegAngle, EndAngle: Double);
var
  I, N: Integer;
var
  CosA, SinA: Double;
  Alpha, Beta: Double;
begin
  BegAngle := NormAngle(BegAngle);
  EndAngle := NormAngle(EndAngle);
  //if Abs(BegAngle - EndAngle) < 0.001 then //07.10.2011 if BegAngle = EndAngle then
  // IGOR 2017-05-03 так оно правильнее, а то бывают арки с ооочень близкими углами 
  if Abs(BegAngle - EndAngle) < 0.00000001 then
  begin
    BegAngle := EndAngle;
    EndAngle := EndAngle + 2 * Pi;
  end;

  Alpha := 0.5 * (EndAngle - BegAngle);
  if Alpha < 0 then
    Alpha := Alpha + Pi;
  if DxfMode then
    N := Ceil(Alpha * (128 / Pi))
  else if DxfMode32 then
    N := Ceil(Alpha * (32 / Pi))
  else if DxfMode16 then
    N := Ceil(Alpha * (16 / Pi))
  else
    N := Ceil(Alpha * (4 / Pi));
  if N > 1 then
    Alpha := Alpha/N;

  if Alpha < Epsilon then Exit;
  SetLength(Points, N * 3 + 1);

  CosA := cos(Alpha);
  SinA := sin(Alpha);
  Beta := 4 * (1 - CosA) / (3 * SinA);

  for I := 0 to N - 1 do
  begin
    Points[I*3].X := CosA;
    Points[I*3].Y := -SinA;
    Points[I*3+3].X := CosA;
    Points[I*3+3].Y := SinA;
    Points[I*3+1].X := Points[I*3+0].X + Beta * SinA;
    Points[I*3+1].Y := Points[I*3+0].Y + Beta * CosA;
    Points[I*3+2].X := Points[I*3+3].X + Beta * SinA;
    Points[I*3+2].Y := Points[I*3+3].Y - Beta * CosA;
    Rotate(Slice(PCubicBezierCurve(@Points[I*3])^, 4),
           BegAngle+(I*2+1)*Alpha);
  end;

end;

{$IFDEF MSWindows}
procedure CircularArc(
  DC: hDC;
  X, Y,
  Radius,
  BegAngle,
  EndAngle: Double);
begin
  EllipticalArc(DC, X, Y, Radius, Radius, 0, BegAngle, EndAngle);
end;
{$ENDIF}

procedure DrawCircularArc(
  Canvas: TCanvas;
  X, Y,
  Radius,
  BegAngle,
  EndAngle: Double);
begin
  DrawEllipticalArc(Canvas, X, Y, Radius, Radius, 0, BegAngle, EndAngle);
end;

procedure DrawEllipticalArc(
  Canvas: TCanvas;
  X, Y,
  A, B,
  Angle,
  BegAngle,
  EndAngle: Double);
{$IFDEF MSWindows}
begin
  EllipticalArc(Canvas.Handle, X, Y, A, B, Angle, BegAngle, EndAngle);
end;

procedure EllipticalArc(
  DC: hDC;
  X, Y,
  A, B,
  Angle,
  BegAngle,
  EndAngle: Double);
{$ENDIF MSWindows}

{$IFDEF Debug}
  procedure CrossAt(DC: hDC; const P: TPoint);
  begin
    MoveToEx(DC, P.X-3, P.Y-3, nil);
    LineTo(DC, P.X+4, P.Y+4);
    MoveToEx(DC, P.X-3, P.Y+3, nil);
    LineTo(DC, P.X+4, P.Y-4);
  end;
{$ENDIF Debug}

  procedure ScaleAngle(var Angle: Double);
  var
    SinA, CosA: Extended;
  begin
    SinCos(Angle, SinA, CosA);
    Angle := ArcTan2(A * SinA, B * CosA);
  end;

var
  I: Integer;
  Points: T2DPointArray;
  P: array of TPoint;
begin
  if A <> B then
  begin
    ScaleAngle(BegAngle);
    ScaleAngle(EndAngle);
  end;
  FitBezierToUnitArc(Points,
        BegAngle,
        EndAngle);
  Scale(Points, A, B);
  Rotate(Points, Angle);
  Translate(Points, X, Y);
  SetLength(P, Length(Points));
  for I := 0 to Length(Points)-1 do
  begin
    P[I].X := Round(Points[I].X);
    P[I].Y := Round(Points[I].Y);
{$IFDEF Debug}
    CrossAt(DC, P[I]);
{$ENDIF Debug}
  end;
  {$IFDEF MSWindows}
  PolyBezier(DC, P[0], Length(P));
  {$ELSE}
  for I := 0 to Length(P) div 3-1 do
    Canvas.PolyBezier(P, I * 3);
  {$ENDIF}
end;

procedure BezierElpArcPoints(var Points: T2DPointArray; X, Y, aLen, bLen, Angle, BegAngle, EndAngle: Double);

  procedure ScaleAngle(var Angle: Double);
  var
    SinA, CosA: Extended;
  begin
    SinCos(Angle, SinA, CosA);
    Angle := ArcTan2(alen * SinA, blen * CosA);
  end;
  
begin
  if aLen <> bLen then
  begin
    ScaleAngle(BegAngle);
    ScaleAngle(EndAngle);
  end;
  FitBezierToUnitArc(Points, BegAngle, EndAngle);
  Scale(Points, aLen, bLen);
  Rotate(Points, Angle);
  Translate(Points, X, Y);
end;

procedure BezierArcPoints(var Points: T2DPointArray; X, Y, Radius, BegAngle, EndAngle: Double);
begin
  FitBezierToUnitArc(Points, BegAngle, EndAngle);
  Scale(Points, Radius, Radius);
  Translate(Points, X, Y);
end;


procedure GetCircleThruPoints(
  const P1, P2, P3: T2DPoint;
  var Center: T2DPoint;
  var Radius: Double);

  procedure GetMiddleOrthogonal(
    P1, P2: T2DPoint;
    var P: T2DPoint;
    var N: T2DVector);
  begin
    P.X := 0.5*(P1.X+P2.X);
    P.Y := 0.5*(P1.Y+P2.Y);
    N.X := P1.Y-P2.Y;
    N.Y := P2.X-P1.X;
  end;

var
  P12, P23: T2DPoint;
  N12, N23: T2DVector;
var
  a12, b: Double;
begin
  GetMiddleOrthogonal(P1, P2, P12, N12);
  GetMiddleOrthogonal(P2, P3, P23, N23);

  b := (N12.Y*N23.X-N12.X*N23.Y);
  if Abs(b) < Epsilon then raise EZeroDivide.Create(SPointsOnStraightLine);
  a12 := ((P23.Y-P12.Y) * N23.X + (P12.X-P23.X) * N23.Y)/b;

  Center.X := P12.X + a12 * N12.X;
  Center.Y := P12.Y + a12 * N12.Y;

  Radius := Sqrt(Sqr(Center.X-P1.X)+Sqr(Center.Y-P1.Y));
end;

{$IFDEF RR_Overloading}
procedure GetCircleThruPoints(
  const X1, Y1, X2, Y2, X3, Y3: Double;
  var CenterX, CenterY: Double;
  var Radius: Double);
var
  C: T2DPoint;
begin
  GetCircleThruPoints(
  	FPoint(X1, Y1),
        FPoint(X2, Y2),
        FPoint(X3, Y3),
        C, Radius);
  CenterX := C.X;
  CenterY := C.Y;
end;

procedure GetCircleThruPoints(
  const P1, P2, P3: TPoint;
  var Center: T2DPoint;
  var Radius: Double);
begin
  GetCircleThruPoints(
  	FPoint(P1.X, P1.Y),
        FPoint(P2.X, P2.Y),
        FPoint(P3.X, P3.Y),
        Center, Radius);
end;
{$ENDIF RR_Overloading}

end.
