unit Collision;

interface

uses
  Vec2f;

type
  T2DColPoint = record
    Point: TVec2f;
    Coll: Boolean;
  end;

  P2DColPoints = ^T2DColPoints;
  T2DColPoints = array[0..3] of T2DColPoint;

  T2DLine = record
    a, b: TVec2f;
  end;

  T2DRect = record
    X, Y, W, H: Single;
  end;

  T2DCircle = record
    C: TVec2f;
    R: Single;
  end;

  TCollision = class
  private
    function ArcTan2(dx, dy: Single): Single;
  public
    procedure InitColPoint(ColPoint: P2DColPoints);
    function PointRect(X, Y: Single; const ARect: T2DRect): Boolean; overload;
    function PointRect(Point: TVec2f; const ARect: T2DRect): Boolean; overload;

    function Line(const A, B: T2DLine; ColPoint: PVec2f): Boolean; overload;
    function Line(const A, B: T2DLine): Boolean; overload;
    function LineRect(const ALine: T2DLine; const ARect : T2DRect; ColPoints: P2DColPoints): Boolean; overload;
    function LineRect(const ALine: T2DLine; const ARect : T2DRect): Boolean; overload;
    function LineCircle(const ALine: T2DLine; const ACircle: T2DCircle): Boolean;
    function ToRect(X, Y, W, H: Single): T2DRect;
    function ToLine(A, B: TVec2f): T2DLine;
    function GetAngle(A, B: TVec2f): Single;

    function PointMinDisatance(Points: P2DColPoints; Point: TVec2f): Single;
    function PointMaxDisatance(Points: P2DColPoints; Point: TVec2f): Single;
  end;

var
  Coll: TCollision;

implementation

function TCollision.PointMinDisatance(Points: P2DColPoints; Point: TVec2f): Single;
var
  i: Integer;
  dis: Single;
begin
  Result := 5000;
  for i := 0 to 3 do
    if Points[i].Coll then
    begin
      dis := Point.Distance(Points[i].point);
      if dis < Result then
        Result := dis;
    end;
end;

function TCollision.PointMaxDisatance(Points: P2DColPoints; Point: TVec2f): Single;
var
  i: Integer;
  dis: Single;
begin
  Result := 0;
  for i := 0 to 3 do
    if Points[i].Coll then
    begin
      dis := Point.Distance(Points[i].point);
      if dis > Result then
        Result := dis;
    end;
end;

function TCollision.ArcTan2( dx, dy : Single ) : Single;
begin
  Result := abs( ArcTan( dy / dx ) * ( 180 / pi ) );
end;

function TCollision.PointRect(X, Y: Single; const ARect: T2DRect): Boolean;
begin
  Result := (X >= ARect.X) and (X < ARect.X + ARect.W) and (Y >= ARect.Y) and (Y < ARect.Y + ARect.H);
end;

function TCollision.PointRect(Point: TVec2f; const ARect: T2DRect): Boolean;
begin
  Result := PointRect(Point.X, Point.Y, ARect);
end;

function TCollision.ToRect(X, Y, W, H: Single): T2DRect;
begin
  Result.X := X;
  Result.Y := Y;
  Result.W := W;
  Result.H := H;
end;

function TCollision.ToLine(A, B: TVec2f): T2DLine;
begin
  Result.a := A;
  Result.b := B;
end;

function TCollision.Line(const A, B: T2DLine; ColPoint: PVec2f): Boolean;
  var
    s, t, tmp: Single;
    s1, s2: TVec2f;
    i: integer;
begin
  Result := False;
  s1 := A.b - A.a;
  s2 := B.b - B.a;
  s := (s2.X * (-s1.Y) - (-s1.X) * s2.Y);
  if s <> 0 Then
    tmp := 1 / (s2.X * (-s1.Y) - (-s1.X) * s2.Y)
  else
    exit;

  s := ((A.a.X - B.a.X) * (-s1.Y) - (-s1.X) * (A.a.Y - B.a.Y)) * tmp;
  t := (s2.X * (A.a.Y - B.a.Y) - (A.a.X - B.a.X) * s2.Y) * tmp;
  Result := (s >= 0) and (s <= 1) and (t >= 0) and (t <= 1);

  if Assigned(ColPoint) then
    ColPoint^ := TVec2f.Create(A.a.X + t * s1.X, A.a.Y + t * s1.Y);
end;

function TCollision.GetAngle(A, B: TVec2f): Single;
var
  dx, dy : Single;
begin
  dx := (B.X - A.X);
  dy := (B.Y - A.Y);

  if dx = 0 Then
    begin
      if dy > 0 Then
        Result := 90
      else
        Result := 270;
      exit;
    end;

  if dy = 0 Then
    begin
      if dx > 0 Then
        Result := 0
      else
        Result := 180;
      exit;
    end;

  if (dx < 0) and (dy > 0) Then
    Result := 180 - ArcTan2(dx, dy)
  else
    if (dx < 0) and (dy < 0) Then
      Result := 180 + ArcTan2(dx, dy)
    else
      if (dx > 0) and (dy < 0) Then
        Result := 360 - ArcTan2(dx, dy)
      else
        Result := ArcTan2(dx, dy);
end;

procedure TCollision.InitColPoint(ColPoint: P2DColPoints);
var
  i: Integer;
begin
  for i := 0 to 3 do
  begin
    ColPoint[i].point := TVec2f.Create(0, 0);
    ColPoint[i].Coll := False;
  end;
end;

function TCollision.Line(const A, B: T2DLine): Boolean;
begin
  Result := Line(A, B, nil);
end;

function TCollision.LineCircle(const ALine: T2DLine; const ACircle: T2DCircle): Boolean;
var
  p1, p2, d: TVec2f;
  a, b, c: Single;
begin
  p1 := ALine.a - ACircle.C;
  p2 := ALine.b - ACircle.C;

  d := TVec2f.Create(p2.X - p1.X, p2.Y - p1.Y);

  a := sqr(d.x) + sqr(d.y);
  b := (p1.X * d.x + p1.Y * d.y) * 2;
  c := sqr(p1.X) + sqr(p1.Y) - sqr(ACircle.R);

  if -b < 0 Then
    Result := c < 0
  else
    if -b < a * 2 Then
      Result := a * c * 4 - sqr(b) < 0
    else
      Result := a + b + c < 0;
end;

function TCollision.LineRect(const ALine: T2DLine; const ARect : T2DRect): Boolean;
var
  line0: T2DLine;
begin
  Result := LineRect(ALine, ARect, nil);
end;

function TCollision.LineRect(const ALine: T2DLine; const ARect : T2DRect; ColPoints: P2DColPoints): Boolean;
var
  line0: T2DLine;
begin
  if not Assigned(ColPoints) Then
  begin
    Result := PointRect(ALine.a.X, ALine.a.Y, ARect) or PointRect(ALine.b.X, ALine.b.Y, ARect);
    if not Result Then
    begin
      line0.a := TVec2f.Create(ARect.X, ARect.Y);
      line0.b := TVec2f.Create(ARect.X + ARect.W, ARect.Y + ARect.H);
      Result := Line(ALine, line0);
      if not Result Then
      begin
        line0.a := TVec2f.Create(ARect.X, ARect.Y + ARect.H);
        line0.b := TVec2f.Create(ARect.X + ARect.W, ARect.Y);
        Result := Line(ALine, line0);
      end;
    end;
  end
  else
  begin
    line0.a := TVec2f.Create(ARect.X, ARect.Y);
    line0.b := TVec2f.Create(ARect.X + ARect.W, ARect.Y);
    ColPoints[0].Coll := Line(ALine, line0, @ColPoints[0]);
    Result := ColPoints[0].Coll;

    line0.a := TVec2f.Create(ARect.X + ARect.W, ARect.Y);
    line0.b := TVec2f.Create(ARect.X + ARect.W, ARect.Y + ARect.H);
    ColPoints[1].Coll := Line(ALine, line0, @ColPoints[1]);
    Result := Result or ColPoints[1].Coll;

    line0.a := TVec2f.Create(ARect.X + ARect.W, ARect.Y + ARect.H);
    line0.b := TVec2f.Create(ARect.X, ARect.Y + ARect.H);
    ColPoints[2].Coll := Line(ALine, line0, @ColPoints[2]);
    Result := Result or ColPoints[2].Coll;

    line0.a := TVec2f.Create(ARect.X, ARect.Y);
    line0.b := TVec2f.Create(ARect.X, ARect.Y + ARect.H);
    ColPoints[3].Coll := Line(ALine, line0, @ColPoints[3]);
    Result := Result or ColPoints[3].Coll;
  end;
end;

initialization
  Coll := TCollision.Create;

finalization
  if Assigned(Coll) then
    Coll.Free;

end.
