Delphi 3D – Camera with “Heads Up” Display

When allowing a user to control or move the camera in a 3D scene it is useful to have some form of “Heads Up” or “dashboard” display to provide feedback to the user about the orientation etc. of the camera. We wrap all this up in a single “AssignFlyingCameraToForm” procedure.

First we inherit from TDummy to create a TFlyingCamera Class. This gives a 3D control to which we can attach the camera and the control display and to which we can apply any control movements. Initially we attached the “Heads Up” display at Position [0,0,0] and the TCamera at {0,0,-3] but this gives a weird effect when panning the TFlyingCamera near close objects as the actual camera is effectively on a long stick and its position with relation to the objects change. Now the “Heads Up” display at Position [0,0,1.1] and the TCamera at {0,0,0].

I initially constructed the objects on the form to find out how to make it look right but then rolled it all into a single object class definition.

Type
  TFlyingCamera = Class(TDummy)
  private
    F3dForm: TForm3D;
    FForwardView: TCamera;
    FGunsight: TGunsight;
    FArtHorizon: TCube;
    FAHMaterial: TLightMaterialSource;
    FHeadsUp: TDummy;
    FLocText, FSpeedText, FAngleText: TText3D;
    FTextMaterial: TLightMaterialSource;
    FSpeedTimer: TTimer;
    FAngleInc: Real; // In degress
    FSpeed, // in units per second
    FSpeedInc: Real;
    FStepFormat:Boolean;
    FBankAngle:Single;
    Procedure SetBankAngle( Value:Single);
    Procedure MoveForward(ADistance: Real);
    procedure FmCamera3DKeyDown(Sender: TObject; var Key: Word;
      var KeyChar: Char; Shift: TShiftState); Virtual;
    Procedure SpeedTimer(Sender: TObject);
    Procedure SetText(ATxt: TText3D);
    Procedure ShowIt(AObj: TControl3D);
    procedure SetSpeed(const Value: Real);
    Procedure AlignCammeraToOrigin;
    Procedure Bank(ALeft:Boolean);
    Procedure CreateArtHorizon;
    procedure SetStepFormat(const Value: Boolean);
    Property  BankAngle:Single Read FBankAngle write SetBankAngle;
  Public
    Constructor Create(A3dForm: TComponent); override;
    destructor Destroy; override;
    Procedure UpdateHeadUpDisplay;
    Property Speed: Real read FSpeed write SetSpeed; // units per second
    Property StepFormat: Boolean read FStepFormat write SetStepFormat; // units per second
  End;

function AssignFlyingCameraToForm(A3dForm: TForm3D; AX:Single=0;AY:Single=0;AZ:Single=0): TFlyingCamera;

The Create Function puts all the bits together and allocates the forms OnKeyDown event to the component so the keys can control the movement.

constructor TFlyingCamera.Create(A3dForm: TComponent);

begin
  if not(A3dForm is TForm3D) then
    raise Exception.Create('Must have a 3D Form');
  F3dForm := A3dForm as TForm3D;
  Inherited Create(A3dForm);
  FAngleInc := 1.0;
  FSpeedInc := 0.01;
  FSpeed := 0;//0.1;

  Parent := A3dForm as TFmxObject;
  if CameraStartPos<>TPoint3D.Zero then
   Position.Point:= CameraStartPos
  Else
   Position.Point:=TPoint3D.Create(0,-20*Sin(DegToRad(20)),-20*Cos(DegToRad(20)));

  F3dForm.OnKeyDown := FmCamera3DKeyDown;

  FSpeedTimer := TTimer.Create(Self);
  FSpeedTimer.Interval := 100; // 1000 millisecs = 1 Second
  FSpeedTimer.OnTimer := SpeedTimer;

  FForwardView := TCamera.Create(Self);
  FForwardView.Parent := Self;
  FForwardView.Position.Z:=0;
  FHeadsUp :=TDummy.Create(self);
  FHeadsUp.Parent:=FForwardView;
  //Gimble the TCamera RotationAngle.Z Leave The Base Dummy RotationAngle.Z:=0
  FHeadsUp.Position.point:=TPoint3D.Create(0,0,1.2);
  FHeadsUp.Scale.Point:= TPoint3D.Create(0.4,0.4,0.4);
  FHeadsUp.Visible:=True;
  FGunsight := TGunsight.Create(FHeadsUp);

  FLocText := TText3D.Create(FHeadsUp);
  FLocText.Position.X := -1.2;
  FLocText.Position.Y := 1.1;
  SetText(FLocText);

  FSpeedText := TText3D.Create(FHeadsUp);
  FSpeedText.Position.X := 1.2;
  FSpeedText.Position.Y := 1.1;
  FSpeedText.Parent:=FHeadsUp;
  SetText(FSpeedText);

  FAngleText := TText3D.Create(FHeadsUp);
  FAngleText.Position.X := -1.2;
  FAngleText.Position.Y := 1.2;
  SetText(FAngleText);

  F3dForm.Camera := FForwardView;
  F3dForm.UsingDesignCamera := false;
  AlignCammeraToOrigin;
  Visible := true;
  Repaint;
end;

The requirement to move align the camera initially to the origin and later to move it forward in the viewed direction provided challenges which will be the subject of future submissions but the source code featured in my “Moving Camera Project” contains my solution to date.

While the TFlyingCamera class could be evolved into a toolbar component there seemed little point. Calling AssignFlyingCameraToForm achives all that is required.

Var
  CameraStartPos:TPoint3D;

function AssignFlyingCameraToForm(A3dForm: TForm3D; AX,AY,AZ:Single): TFlyingCamera;
begin
  if (AX<>0) or (AY<>0) or (AZ<>0) then
    CameraStartPos:=TPoint3D.Create(Ax,Ay,Az);
  Result := TFlyingCamera.Create(A3dForm);
end;

Leave a Reply

Your email address will not be published. Required fields are marked *