unit FirstPersonCamera;

interface

uses Windows, SysUtils, Math3D, Camera;

type
  TFirstPersonCamera = class(TCamera)
  protected
    FForwardVector      : TVector3;
    FRightVector        : TVector3;
    FUpVector           : TVector3;
    FBendAngle          : single;
    FLeanAngle          : single;
    FMaxBendAngle       : single;
    FMaxLeanAngle       : single;
    procedure SetUpVector(up: TVector3); virtual;
    procedure SetForwardVector(fw: TVector3); virtual;
  public
    constructor Create; override;
    procedure MoveForward(amount: single); override;
    procedure MoveBackward(amount: single); override;
    procedure MoveLeft(amount: single); override;
    procedure MoveRight(amount: single); override;
    procedure MoveUp(amount: single); override;
    procedure MoveDown(amount: single); override;
    procedure TurnLeft(degree: single); override;
    procedure TurnRight(degree: single); override;
    procedure BendDown(degree: single); override;
    procedure BendUp(degree: single); override;
    procedure LeanLeft(degree: single); override;
    procedure LeanRight(degree: single); override;
    procedure LeanBack(degree: single); virtual;
    property UpVector: TVector3 read FUpVector write FUpVector;
    property ForwardVector: TVector3 read FForwardVector write SetForwardVector;
    property BendAngle: single read FBendAngle;
    property LeanAngle: single read FLeanAngle;
    property MaxBendAngle: single read FMaxBendAngle write FMaxBendAngle;
    property MaxLeanAngle: single read FMaxLeanAngle write FMaxLeanAngle;
  end;

implementation

constructor TFirstPersonCamera.Create;
begin
  inherited Create;

  FUpVector         := Vector3(0, 1, 0);
  FForwardVector    := Vector3(0, 0,-1);
  FRightVector      := VecCross3(FForwardVector, FUpVector);

  FBendAngle        := 0;
  FLeanAngle        := 0;

  FMaxBendAngle     := 90;
  FMaxLeanAngle     := 30;
end;

procedure TFirstPersonCamera.SetUpVector;
begin
  FUpVector := up;
  FRightVector := VecCross3(FForwardVector, FUpVector);
end;

procedure TFirstPersonCamera.SetForwardVector;
begin
  FForwardVector := fw;
  FRightVector := VecCross3(FForwardVector, FUpVector);
end;

procedure TFirstPersonCamera.MoveForward;
begin
  Position := VecAdd3(Position, VecScalarMult3(FForwardVector, amount));
end;

procedure TFirstPersonCamera.MoveBackward;
begin
  Position := VecSubtract3(Position, VecScalarMult3(FForwardVector, amount));
end;

procedure TFirstPersonCamera.MoveLeft;
begin
  Position := VecSubtract3(Position, VecScalarMult3(FRightVector, amount));
end;

procedure TFirstPersonCamera.MoveRight;
begin
  Position := VecAdd3(Position, VecScalarMult3(FRightVector, amount));
end;

procedure TFirstPersonCamera.MoveUp;
begin
  Position := VecAdd3(Position, VecScalarMult3(FUpVector, amount));
end;

procedure TFirstPersonCamera.MoveDown;
begin
  Position := VecSubtract3(Position, VecScalarMult3(FUpVector, amount));
end;

procedure TFirstPersonCamera.TurnLeft;
var
  dir: TVector3;
  fw: TVector3;
begin
  dir := Direction;
  fw := ForwardVector;
  VecRotate3(dir, FUpVector, (degree*pi)/180);
  VecRotate3(FCameraRotation, FUpVector, (degree*pi)/180);
  VecRotate3(fw, FUpVector, (degree*pi)/180);
  Direction := dir;
  ForwardVector := fw;
end;

procedure TFirstPersonCamera.TurnRight;
var
  dir: TVector3;
  fw: TVector3;
begin
  dir := Direction;
  fw := ForwardVector;
  VecRotate3(dir, FUpVector, -(degree*pi)/180);
  VecRotate3(FCameraRotation, FUpVector, -(degree*pi)/180);
  VecRotate3(fw, FUpVector, -(degree*pi)/180);
  Direction := dir;
  ForwardVector := fw;
end;

procedure TFirstPersonCamera.BendDown;
var
  dir: TVector3;
begin
  FBendAngle := FBendAngle - degree;
  if FBendAngle < -FMaxBendAngle then
  begin
    degree := degree + FMaxBendAngle + FBendAngle;
    FBendAngle := -FMaxBendAngle;
  end;
  dir := Direction;
  VecRotate3(dir, FRightVector, -(degree*pi)/180);
  VecRotate3(FCameraRotation, FRightVector, -(degree*pi)/180);
  Direction := dir;
end;

procedure TFirstPersonCamera.BendUp;
var
  dir: TVector3;
begin
  FBendAngle := FBendAngle + degree;
  if FBendAngle > FMaxBendAngle then
  begin
    degree := degree + FMaxBendAngle - FBendAngle;
    FBendAngle := FMaxBendAngle;
  end;
  dir := Direction;
  VecRotate3(dir, FRightVector, (degree*pi)/180);
  VecRotate3(FCameraRotation, FRightVector, (degree*pi)/180);
  Direction := dir;
end;

procedure TFirstPersonCamera.LeanLeft;
var
  dir: TVector3;
begin
  FLeanAngle := FLeanAngle - degree;
  if FLeanAngle < -FMaxLeanAngle then
  begin
    degree := degree + FMaxLeanAngle + FLeanAngle;
    FLeanAngle := -FMaxLeanAngle;
  end;
  MoveLeft((degree*pi)/180);
  dir := Direction;
  VecRotate3(dir, FForwardVector, -(degree*pi)/180);
  VecRotate3(FCameraRotation, FForwardVector, -(degree*pi)/180);
  Direction := dir;
end;

procedure TFirstPersonCamera.LeanRight;
var
  dir: TVector3;
begin
  FLeanAngle := FLeanAngle + degree;
  if FLeanAngle > FMaxLeanAngle then
  begin
    degree := degree + FMaxLeanAngle - FLeanAngle;
    FLeanAngle := FMaxLeanAngle;
  end;
  MoveRight((degree*pi)/180);
  dir := Direction;
  VecRotate3(dir, FForwardVector, (degree*pi)/180);
  VecRotate3(FCameraRotation, FForwardVector, (degree*pi)/180);
  Direction := dir;
end;

procedure TFirstPersonCamera.LeanBack;
begin
  if FLeanAngle > 0 then LeanLeft(degree);
  if FLeanAngle < 0 then LeanRight(degree);
end;

end.
