unit Collision;

interface

uses
  Vec2f;

type
  PVec2f = ^TVec2f;
  P2DColPoint = ^T2DColPoint;
  T2DColPoint = record
    Points: array[0..99] of TVec2f;
    Count: Integer;
  end;

  T2DLine = record
    a, b: TVec2f;
  end;

  T2DRect = record
    Position, Size: TVec2f;
    constructor Create(APosition, ASize: TVec2f); overload;
    constructor Create(X, Y, W, H: Single); overload;
  end;

  T2DCircle = record
    Position: TVec2f;
    Radius: Single;
    constructor Create(APosition: TVec2f; ARadius: Single); overload;
    constructor Create(X, Y, ARadius: Single); overload;
  end;

  TCollision = class
    class procedure InitColPoint(ColPoint: P2DColPoint);
    class function PointRect(APoint: TVec2f; const ARect: T2DRect): Boolean;
    class function PointCircle(APoint: TVec2f; const ACircle: T2DCircle): Boolean;
    class function Line(const A, B: T2DLine; ColPoint: PVec2f): Boolean; overload;
    class function Line(const A, B: T2DLine): Boolean; overload;
    class function LineRect(const ALine: T2DLine; const ARect : T2DRect; ColPoint: P2DColPoint): Boolean; overload;
    class function LineRect(const ALine: T2DLine; const ARect : T2DRect): Boolean; overload;
    class function LineCircle(const ALine: T2DLine; const ACircle: T2DCircle): Boolean;
  end;

implementation

constructor T2DRect.Create(APosition, ASize: TVec2f);
begin
  Position := APosition;
  Size := ASize;
end;

constructor T2DRect.Create(X, Y, W, H: Single);
begin
  Position := TVec2f.Create(X, Y);
  Size := TVec2f.Create(W, H);
end;

constructor T2DCircle.Create(APosition: TVec2f; ARadius: Single);
begin
  Position := APosition;
  Radius := ARadius;
end;

constructor T2DCircle.Create(X, Y, ARadius: Single);
begin
  Position := TVec2f.Create(X, Y);
  Radius := ARadius;
end;

class function TCollision.PointRect(APoint: TVec2f; const ARect: T2DRect): Boolean;
begin
  Result := (APoint.X >= ARect.Position.X) and (APoint.X < ARect.Position.X + ARect.Size.X) and (APoint.Y >= ARect.Position.Y) and (APoint.Y < ARect.Position.Y + ARect.Size.Y);
end;

class function TCollision.PointCircle(APoint: TVec2f; const ACircle: T2DCircle): Boolean;
begin
  Result := sqr(ACircle.Position.X - APoint.X) + sqr(ACircle.Position.Y - APoint.Y) <= sqr(ACircle.Radius);
end;

class function TCollision.Line(const A, B: T2DLine; ColPoint: PVec2f): Boolean;
var
  s, t, tmp: Single;
  s1, s2: TVec2f;
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;

class procedure TCollision.InitColPoint(ColPoint: P2DColPoint);
var
  i: Integer;
begin
  ColPoint.Count := 0;
  for i := 0 to 3 do
    ColPoint.Points[i] := TVec2f.Create(0, 0);
end;

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

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

  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.Radius);

  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;

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

class function TCollision.LineRect(const ALine: T2DLine; const ARect : T2DRect; ColPoint: P2DColPoint): Boolean;
var
  line0: T2DLine;
  Point: TVec2f;
begin
  if not Assigned(ColPoint) Then
  begin
    Result := PointRect(ALine.a, ARect) or PointRect(ALine.b, ARect);
    if not Result Then
    begin
      line0.a := TVec2f.Create(ARect.Position.X, ARect.Position.Y);
      line0.b := TVec2f.Create(ARect.Position.X + ARect.Size.X, ARect.Position.Y + ARect.Size.Y);
      Result := Line(ALine, line0);
      if not Result Then
      begin
        line0.a := TVec2f.Create(ARect.Position.X, ARect.Position.Y + ARect.Size.Y);
        line0.b := TVec2f.Create(ARect.Position.X + ARect.Size.X, ARect.Position.Y);
        Result := Line(ALine, line0);
      end;
    end;
  end
  else
  begin
    line0.a := TVec2f.Create(ARect.Position.X, ARect.Position.Y);
    line0.b := TVec2f.Create(ARect.Position.X + ARect.Size.X, ARect.Position.Y);
    if Line(ALine, line0, @Point) then
    begin
      ColPoint.Points[ColPoint.Count] := Point;
      Inc(ColPoint.Count);
    end;

    line0.a := TVec2f.Create(ARect.Position.X + ARect.Size.X, ARect.Position.Y);
    line0.b := TVec2f.Create(ARect.Position.X + ARect.Size.X, ARect.Position.Y + ARect.Size.Y);
    if Line(ALine, line0, @Point) then
    begin
      ColPoint.Points[ColPoint.Count] := Point;
      Inc(ColPoint.Count);
    end;

    line0.a := TVec2f.Create(ARect.Position.X + ARect.Size.X, ARect.Position.Y + ARect.Size.Y);
    line0.b := TVec2f.Create(ARect.Position.X, ARect.Position.Y + ARect.Size.Y);
    if Line(ALine, line0, @Point) then
    begin
      ColPoint.Points[ColPoint.Count] := Point;
      Inc(ColPoint.Count);
    end;

    line0.a := TVec2f.Create(ARect.Position.X, ARect.Position.Y);
    line0.b := TVec2f.Create(ARect.Position.X, ARect.Position.Y + ARect.Size.Y);
    if Line(ALine, line0, @Point) then
    begin
      ColPoint.Points[ColPoint.Count] := Point;
      Inc(ColPoint.Count);
    end;
    Result := ColPoint.Count > 0;
  end;
end;

end.
