unit Controller;

interface

uses Windows,OpenGL, U3d;

type
  TControlMode=(cmSceneControl,cmVirtualReality{,cmTrackBall в версии 3});
  // =====================================================================
  PControlRec=^TControlRec;
  TControlRec=record
    Key : word;
    Desc : string;
    Pressed : Boolean;
  end;
  // =====================================================================
  PControlSet=^TControlSet;
  TControlSet=object
  public
  // Углы
    Up,Down,Left,Right,RollLeft,RollRight,
  // Движение
    Accelerate,Reverse,SlideLeft,SlideRight,SlideUp,SlideDown:TControlRec;
    // Установки клавиш по умолчанию
    constructor Create;
    destructor Destroy;
    // Опрос клавиатуры и обновление управляющих записей
    procedure AskKeyboard;
  end;
  // =====================================================================
  PController   = ^TController;
  TController   = class(TObject)
  private
    Controls    : TControlSet;
    Mode        : TControlMode;
    // Параметры движения
    Accel       : vector_t;
    AngleAccel  : vector_t;
    Speed       : vector_t;
    AngleSpeed  : vector_t;
    MaxSpeed    : vector_t;
    MaxAngleSpeed:vector_t;
    // Параметры системы координат
    RealUp      : vector_t;
    RealDir     : vector_t;
    Angles      : vector_t;
    // Параметры локальной системы координат
    Location    : vector_t;
    Right       : vector_t;
    Up          : vector_t;
    Dir         : vector_t;
    // Матрица
    Matrix      : hmatrix_t;
    ConcatRot   : matrix_t;
    function    GetMode:TControlMode;
    procedure   ChangeMode(NewMode : TControlMode);
  public
    constructor Create;
    destructor  Destroy;override;
    function    GetTransform:u3dval_p;
    function    KeyboardControl(dt:u3dval):Boolean;
    procedure   CalcAccel;
    procedure   ResetPosition;
    property    ControlMode:TControlMode read GetMode write ChangeMode;
  end;

implementation

uses Setup;

constructor TControlSet.Create;
begin
  // Клавиши:
  // UpArrow    (38) - Наклон вверх
  // DownArrow  (40) - Наклон вниз
  // LeftArrow  (37) - Наклон влево
  // RightArrow (39) - Наклон вправо
  // Delete     (46) - Поворот влево
  // PgDn       (34) - Поворот вправо
  // R          (82) - Вперед
  // F          (70) - Назад
  // D          (68) - Влево
  // G          (71) - Вправо
  // A          (65) - Вверх
  // Z          (90) - Вниз
  Up.Key:=38;
  Up.Desc:='Наклон вверх';
  Down.Key:=40;
  Down.Desc:='Наклон вниз';
  Left.Key:=37;
  Left.Desc:='Наклон влево';
  Right.Key:=39;
  Right.Desc:='Наклон вправо';
  RollLeft.Key:=46;
  RollLeft.Desc:='Поворот влево';
  RollRight.Key:=34;
  RollRight.Desc:='Поворот вправо';
  Accelerate.Key:=82;
  Accelerate.Desc:='Движение вперед';
  Reverse.Key:=70;
  Reverse.Desc:='Движение назад';
  SlideLeft.Key:=68;
  SlideLeft.Desc:='Сдвиг влево';
  SlideRight.Key:=71;
  SlideRight.Desc:='Сдвиг вправо';
  SlideUp.Key:=90;
  SlideUp.Desc:='Сдвиг вверх';
  SlideDown.Key:=65;
  SlideDown.Desc:='Сдвиг вниз';
end;

destructor TControlSet.Destroy;
begin
end;

procedure TControlSet.AskKeyboard;
begin
  // =====================================================================
  Accelerate.Pressed:=0<>($8000 and GetAsyncKeyState(Accelerate.key));
  Reverse.Pressed:=0<>($8000 and GetAsyncKeyState(Reverse.key));
  SlideLeft.Pressed:=0<>($8000 and GetAsyncKeyState(SlideLeft.key));
  SlideRight.Pressed:=0<>($8000 and GetAsyncKeyState(SlideRight.key));
  SlideUp.Pressed:=0<>($8000 and GetAsyncKeyState(SlideUp.key));
  SlideDown.Pressed:=0<>($8000 and GetAsyncKeyState(SlideDown.key));
  // =====================================================================
  Up.Pressed:=0<>($8000 and GetAsyncKeyState(Up.key));
  Down.Pressed:=0<>($8000 and GetAsyncKeyState(Down.key));
  Left.Pressed:=0<>($8000 and GetAsyncKeyState(Left.key));
  Right.Pressed:=0<>($8000 and GetAsyncKeyState(Right.key));
  RollLeft.Pressed:=0<>($8000 and GetAsyncKeyState(RollLeft.key));
  RollRight.Pressed:=0<>($8000 and GetAsyncKeyState(RollRight.key));
end;

constructor TController.Create;
begin
  Controls.Create;
  Mode:=cmSceneControl;
  RealUp.x:=0;
  RealUp.y:=1;
  RealUp.z:=0;
  RealDir.x:=0;
  RealDir.y:=0;
  RealDir.z:=-1;
  vtCopy(Dir,RealDir);
  vtCopy(Up,RealUp);
  vtCross(Right,Dir,Up);
  CalcAccel;
  // Скорость - единицы в миллисекунду
  vtSet(MaxSpeed,0.007,0.007,0.007);
  vtZero(Speed);
  // Скорость - РАДИАНЫ в миллисекунду
  vtSet(MaxAngleSpeed,0.006,0.006,0.006);
  vtZero(AngleSpeed);
  ResetPosition;
end;

procedure TController.CalcAccel;
var
  ac : u3dval;
const
  MinAccel = 2e-6;
  MinAngleAccel = 1e-6;
begin
  // Линейное ускорение: единицы на миллисекунду в квадрате
  // Угловое ускорение - РАДИАНЫ на миллисекунду в квадрате
  if(AppSetup.GLFlags.Mass=0) then
  begin
    vtSet(Accel,1,1,1);
    vtSet(AngleAccel,1,1,1);
    exit;
  end;
  ac:=MinAccel*OptionsForm.TBMass.Max/
    AppSetup.GLFlags.Mass;
  vtSet(Accel,ac,ac,ac);
  ac:=MinAngleAccel*OptionsForm.TBMass.Max/
    AppSetup.GLFlags.Mass;
  vtSet(AngleAccel,ac,ac,ac);
end;

destructor TController.Destroy;
begin
  Controls.Destroy;
  Inherited Destroy;
end;

function TController.GetTransform;
begin
  Result:=u3dval_p(@Matrix);
end;

function TController.GetMode;
begin
  Result:=Mode;
end;

procedure TController.ChangeMode;
begin
  Mode:=NewMode;
  ResetPosition;
end;

procedure TController.ResetPosition;
begin
  vtZero(Angles);
  if(Mode=cmSceneControl) then
    vtSet(Location,0,-2,-6)
  else
    vtSet(Location,0,-2,6);
  vtCopy(Dir,RealDir);
  vtCopy(Up,RealUp);
  vtCross(Right,Dir,Up);
  vtZero(Speed);
  vtZero(AngleSpeed);
  mthIdentity(Matrix);
  mtIdentity(ConcatRot);
end;

procedure TruncateAngle(Angle:u3dval);
begin
  while(angle>M_2PI) do angle:=angle-M_2PI;
  while(angle<0) do angle:=angle+M_2PI;
end;

procedure TruncateAngles(Angles:vector_t);
begin
  TruncateAngle(Angles.pitch);
  TruncateAngle(Angles.yaw);
  TruncateAngle(Angles.roll);
end;

procedure CreateRotationMatrix(var Dst:matrix_t;var Angles:vector_t);
var
  pm,ym,rm,temp : matrix_t;
begin
  mtYRotation(ym,angles.yaw);
  mtZRotation(rm,angles.roll);
  mtXRotation(pm,angles.pitch);
  mtMul(temp,ym,pm);
  mtMul(Dst,temp,rm);
end;

procedure HCreateRotationMatrix(var Dst:hmatrix_t;var Angles:vector_t);
var
  temp : matrix_t;
begin
  CreateRotationMatrix(temp,Angles);
  mtSetLU(Dst,temp);
end;

procedure TruncateVector(var V,Bound:vector_t);
var
  i : integer;
begin
  for i:=0 to 2 do
  begin
    if(V.Elements[i]>Bound.Elements[i]) then
      V.Elements[i]:=Bound.Elements[i]
    else
      if(V.Elements[i]<(-Bound.Elements[i])) then
        V.Elements[i]:=-Bound.Elements[i];
  end;
end;

procedure FallSpeed(var Speed,Accel,dt:u3dval);
var
  Module,Sign : u3dval;
begin
  Module:=abs(Speed);
  if(Module<1e-7) then
  begin
    Speed:=0;
    exit;
  end;
  Sign:=Speed/Module;
  Module:=Module-abs(Accel*dt);
  if(Module<1e-7) then
  begin
    Speed:=0;
    exit;
  end;
  Speed:=Sign*Module;
end;

function TController.KeyboardControl;
var
  dpos,tv : vector_t;
  temp,temp1 : matrix_t;
  i : integer;
  DirF,
  AngleF : array[0..3] of integer;
begin
  Result:=False;
  if(dt<=0) then exit;
  // 10 кадров в секунду - предельно низкая скорость. Дальше нужно
  // замедлять время, иначе упрявлять будет невозможно. Такой подход
  // использован в Quake; в результате игра остается проходимой даже
  // при частоте 5 - 7 кадров в секунду и ниже.
  if(dt>100) then dt:=100;
  Result:=True;
  // Опрашиваем клавиатуру
  Controls.AskKeyboard;
  // Меняем скорости
  DirF[2]:=0;
  if(Controls.Accelerate.Pressed and (not Controls.Reverse.Pressed)) then
    DirF[2]:=1;
  if((not Controls.Accelerate.Pressed) and Controls.Reverse.Pressed) then
    DirF[2]:=-1;
  DirF[0]:=0;
  if(Controls.SlideLeft.Pressed and (not Controls.SlideRight.Pressed)) then
    DirF[0]:=1;
  if((not Controls.SlideLeft.Pressed) and Controls.SlideRight.Pressed) then
    DirF[0]:=-1;
  DirF[1]:=0;
  if(Controls.SlideUp.Pressed and (not Controls.SlideDown.Pressed)) then
    DirF[1]:=1;
  if((not Controls.SlideUp.Pressed) and Controls.SlideDown.Pressed) then
    DirF[1]:=-1;
  for i:=0 to 2 do
  begin
    case DirF[i] of
      0: FallSpeed(Speed.Elements[i],Accel.Elements[i],dt);
      1: Speed.Elements[i]:=Speed.Elements[i]+Accel.Elements[i]*dt;
      -1: Speed.Elements[i]:=Speed.Elements[i]-Accel.Elements[i]*dt;
    end;
  end;
  AngleF[0]:=0;
  if(Controls.Up.Pressed and (not Controls.Down.Pressed)) then
    AngleF[0]:=1;
  if((not Controls.Up.Pressed) and Controls.Down.Pressed) then
    AngleF[0]:=-1;
  AngleF[1]:=0;
  if(Controls.Left.Pressed and (not Controls.Right.Pressed)) then
    AngleF[1]:=1;
  if((not Controls.Left.Pressed) and Controls.Right.Pressed) then
    AngleF[1]:=-1;
  AngleF[2]:=0;
  if(Controls.RollLeft.Pressed and (not Controls.RollRight.Pressed)) then
    AngleF[2]:=1;
  if((not Controls.RollLeft.Pressed) and Controls.RollRight.Pressed) then
    AngleF[2]:=-1;
  for i:=0 to 2 do
  begin
    case AngleF[i] of
      0: FallSpeed(AngleSpeed.Elements[i],AngleAccel.Elements[i],dt);
      1: AngleSpeed.Elements[i]:=AngleSpeed.Elements[i]+AngleAccel.Elements[i]*dt;
      -1: AngleSpeed.Elements[i]:=AngleSpeed.Elements[i]-AngleAccel.Elements[i]*dt;
    end;
  end;
  TruncateVector(Speed,MaxSpeed);
  TruncateVector(AngleSpeed,MaxAngleSpeed);
  // В соответствии со значениями клавиш модифицирум позицию
  if(Mode=cmSceneControl) then
  begin
    vtMul(tv,Speed,dt);
    vtAdd(Location,Location,tv);
    vtMul(tv,AngleSpeed,dt);
    vtAdd(Angles,Angles,tv);
    Angles.Roll:=0;

    TruncateAngles(Angles);
    CreateRotationMatrix(ConcatRot,Angles);
    mtSetLU(Matrix,ConcatRot);

    mtTranspose(temp,ConcatRot);
    mtVecMul(Dir,temp,RealDir);
    mtVecMul(Up,temp,RealUp);
    vtCross(Right,Dir,Up);

    Matrix[3][0]:=Location.x;
    Matrix[3][1]:=Location.y;
    Matrix[3][2]:=Location.z;
  end
  else // cmVirtualReality
  begin
    // 1.05.99 Я СДЕЛАЛ ЭТО !!!
    vtMul(dpos,Speed,dt);
    vtMul(Angles,AngleSpeed,dt * -1.0);

    vtMul(tv,Dir,dpos.z);
    vtAdd(Location,Location,tv);
    vtMul(tv,Up,dpos.y);
    vtAdd(Location,Location,tv);
    vtMul(tv,Right,dpos.x);
    vtAdd(Location,Location,tv);

    CreateRotationMatrix(temp,Angles);
    mtMul(temp1,ConcatRot,temp);
    mtCopy(ConcatRot,temp1);

    mtVecMul(Dir,ConcatRot,RealDir);
    mtVecMul(Up,ConcatRot,RealUp);
    vtCross(Right,Dir,Up);

    mthIdentity(Matrix);
    Matrix[0][0]:=Right.x;
    Matrix[1][0]:=Right.y;
    Matrix[2][0]:=Right.z;
    Matrix[0][1]:=Up.x;
    Matrix[1][1]:=Up.y;
    Matrix[2][1]:=Up.z;
    Matrix[0][2]:=Dir.x;
    Matrix[1][2]:=Dir.y;
    Matrix[2][2]:=Dir.z;

    mtGetLU(temp,Matrix);
    mtSelfTranspose(temp);
    mtVecMul(tv,temp,Location);

    Matrix[3][0]:=tv.x;
    Matrix[3][1]:=tv.y;
    Matrix[3][2]:=tv.z;

    // Соответствует glScalef(1,1,-1)
    Matrix[2][0]:=-Matrix[2][0];
    Matrix[2][1]:=-Matrix[2][1];
    Matrix[2][2]:=-Matrix[2][2];
    Matrix[2][3]:=-Matrix[2][3];

  end;

end;

end.
