unit DoublePoint;

interface

uses
  SysUtils, Types, Classes, D2D1, asnum;

type
  TPointD = record
    X, Y: Double;
    class operator Implicit(const APoint: TPoint): TPointD;
    class operator Implicit(const APoint: TPointF): TPointD;
    class operator Implicit(const APoint: TPointD): TD2D1Point2F;
    class operator Implicit(const APoint: TD2D1Point2F): TPointD;
    class operator Add(const L, R: TPointD): TPointD; inline;
    class operator Subtract(const L, R: TPointD): TPointD; inline;
    class operator Multiply(const s: Double; const u: TPointD): TPointD; inline;
    class operator Negative(const u: TPointD): TPointD; inline;
    class operator Divide(const u: TPointD; const s: Double): TPointD; inline;
    class operator Equal(const Left, Right: TPointD): Boolean; inline;
    class operator NotEqual(const Left, Right: TPointD): Boolean; inline;
    class function Distance(const u, v: TPointD): Double; static;
    function Norm: Double; inline;
    function Normalized: TPointD; inline;
    constructor Create(const X, Y: Double); overload;
    constructor Create(const AVector: TRealVector); overload;
    function TryToInt(out APoint: TPoint): Boolean;
    procedure Offset(const X, Y: Double);
  end;

  TVectorD = TPointD;

  TSizeD = record
    Width, Height: Double;
    class operator Implicit(const ASize: TSizeF): TSizeD;
    class operator Implicit(const ASize: TSize ): TSizeD;
    class operator Equal(const Left, Right: TSizeD): Boolean;
    class operator NotEqual(const Left, Right: TSizeD): Boolean;
    constructor Create(const AWidth, AHeight: Double);
    function Area: Double; inline;
  end;

  TRectD = record
  strict private
    function GetWidth: Double; inline;
      procedure SetWidth(const Value: Double);
    function GetHeight: Double; inline;
    procedure SetHeight(const Value: Double);
    function GetSize: TSizeD;
    procedure SetSize(const Value: TSizeD);
  public
    class operator Implicit(const ARect: TRectD): TD2DRectF;
    class operator Equal(const Left, Right: TRectD): Boolean; inline;
    class operator NotEqual(const Left, Right: TRectD): Boolean; inline;
    constructor Create(const ALeft, ATop, ARight, ABottom: Double); overload;
    constructor Create(const ATopLeft, ABottomRight: TPointD); overload;
    function Area: Double; inline;
    property Width: Double read GetWidth write SetWidth;
    property Height: Double read GetHeight write SetHeight;
    function TryToInt(out ARect: TRect): Boolean;
    property Size: TSizeD read GetSize write SetSize;
    function MidPoint: TPointD;
    procedure Offset(const X, Y: Double);
  public
    case Boolean of
      False:
        (
          Left, Top, Right, Bottom: Double
        );
      True:
        (
          TopLeft: TPointD;
          BottomRight: TPointD;
        )
  end;

implementation

uses
  Math;

{ TPointD }

class operator TPointD.Add(const L, R: TPointD): TPointD;
begin
  Result.X := L.X + R.X;
  Result.Y := L.Y + R.Y;
end;

constructor TPointD.Create(const X, Y: Double);
begin
  Self.X := X;
  Self.Y := Y;
end;

constructor TPointD.Create(const AVector: TRealVector);
begin
  if AVector.Dimension = 2 then
  begin
    X := AVector[0];
    Y := AVector[1];
  end
  else
    raise Exception.Create('Vector must be two-dimensional.');
end;

class function TPointD.Distance(const u, v: TPointD): Double;
begin
  Result := (u - v).Norm;
end;

class operator TPointD.Divide(const u: TPointD; const s: Double): TPointD;
begin
  Result := (1/s) * u;
end;

class operator TPointD.Equal(const Left, Right: TPointD): Boolean;
begin
  Result := (Left.X = Right.X) and (Left.Y = Right.Y);
end;

class operator TPointD.Implicit(const APoint: TPointD): TD2D1Point2F;
begin
  Result.x := APoint.X;
  Result.y := APoint.Y;
end;

class operator TPointD.Implicit(const APoint: TPointF): TPointD;
begin
  Result.X := APoint.X;
  Result.Y := APoint.Y;
end;

class operator TPointD.Multiply(const s: Double; const u: TPointD): TPointD;
begin
  Result.X := s * u.X;
  Result.Y := s * u.Y;
end;

class operator TPointD.Negative(const u: TPointD): TPointD;
begin
  Result.X := -u.X;
  Result.Y := -u.Y;
end;

function TPointD.Norm: Double;
begin
  Result := Hypot(X, Y);
end;

function TPointD.Normalized: TPointD;
begin
  Result := Self / Self.Norm;
end;

class operator TPointD.NotEqual(const Left, Right: TPointD): Boolean;
begin
  Result := not (Left = Right);
end;

procedure TPointD.Offset(const X, Y: Double);
begin
  Self.X := Self.X + X;
  Self.Y := Self.Y + Y;
end;

class operator TPointD.Subtract(const L, R: TPointD): TPointD;
begin
  Result.X := L.X - R.X;
  Result.Y := L.Y - R.Y;
end;

function TPointD.TryToInt(out APoint: TPoint): Boolean;
begin
  Result := InRange(X, Integer.MinValue, Integer.MaxValue) and
    InRange(Y, Integer.MinValue, Integer.MaxValue);
  if Result then
    APoint := Point(Round(X), Round(Y));
end;

class operator TPointD.Implicit(const APoint: TPoint): TPointD;
begin
  Result.X := APoint.X;
  Result.Y := APoint.Y;
end;

class operator TPointD.Implicit(const APoint: TD2D1Point2F): TPointD;
begin
  Result.X := APoint.x;
  Result.Y := APoint.y;
end;

{ TRectD }

function TRectD.Area: Double;
begin
  if (Left < Right) and (Top < Bottom) then
    Result := (Right - Left) * (Bottom - Top)
  else
    Result := 0.0;
end;

constructor TRectD.Create(const ATopLeft, ABottomRight: TPointD);
begin
  TopLeft := ATopLeft;
  BottomRight := ABottomRight;
end;

class operator TRectD.Equal(const Left, Right: TRectD): Boolean;
begin
  Result := (Left.TopLeft = Right.TopLeft) and (Left.BottomRight = Right.BottomRight);
end;

constructor TRectD.Create(const ALeft, ATop, ARight, ABottom: Double);
begin
  Left := ALeft;
  Top := ATop;
  Right := ARight;
  Bottom := ABottom;
end;

function TRectD.GetHeight: Double;
begin
  if Top < Bottom then
    Result := Bottom - Top
  else
    Result := 0.0;
end;

class operator TRectD.Implicit(const ARect: TRectD): TD2DRectF;
begin
  Result.left := ARect.Left;
  Result.top := ARect.Top;
  Result.right := ARect.Right;
  Result.bottom := ARect.Bottom;
end;

function TRectD.MidPoint: TPointD;
begin
  Result.X := (Self.Left + Self.Right) / 2;
  Result.Y := (Self.Top + Self.Bottom) / 2;
end;

class operator TRectD.NotEqual(const Left, Right: TRectD): Boolean;
begin
  Result := not (Left = Right);
end;

procedure TRectD.Offset(const X, Y: Double);
begin
  TopLeft.Offset(X, Y);
  BottomRight.Offset(X, Y);
end;

procedure TRectD.SetHeight(const Value: Double);
begin
  Bottom := Top + Value;
end;

procedure TRectD.SetSize(const Value: TSizeD);
begin
  Right := Left + Value.Width;
  Bottom := Top + Value.Height;
end;

procedure TRectD.SetWidth(const Value: Double);
begin
  Right := Left + Value;
end;

function TRectD.GetSize: TSizeD;
begin
  Result.Width := Self.Width;
  Result.Height := Self.Height;
end;

function TRectD.TryToInt(out ARect: TRect): Boolean;
begin
  Result :=
    TopLeft.TryToInt(ARect.TopLeft)
      and
    BottomRight.TryToInt(ARect.BottomRight);
end;

function TRectD.GetWidth: Double;
begin
  if Left < Right then
    Result := Right - Left
  else
    Result := 0.0;
end;


{ TSizeD }

function TSizeD.Area: Double;
begin
  Result := Width * Height;
end;

constructor TSizeD.Create(const AWidth, AHeight: Double);
begin
  Self.Width := AWidth;
  Self.Height := AHeight;
end;

class operator TSizeD.Equal(const Left, Right: TSizeD): Boolean;
begin
  Result := (Left.Width = Right.Width) and (Left.Height = Right.Height);
end;

class operator TSizeD.Implicit(const ASize: TSize): TSizeD;
begin
  Result.Width := ASize.Width;
  Result.Height := ASize.Height;
end;

class operator TSizeD.NotEqual(const Left, Right: TSizeD): Boolean;
begin
  Result := not (Left = Right);
end;

class operator TSizeD.Implicit(const ASize: TSizeF): TSizeD;
begin
  Result.Width := ASize.Width;
  Result.Height := ASize.Height;
end;

end.
