(******************************************************************************
 *                                                                            *
 *  Придумал и написал Кода Виктор, Март 2002                                 *
 *                                                                            *
 *  Файл:       main.pas                                                      *
 *  Содержание: Пример использования калибровки игровых устройств             *
 *                                                                            *
 ******************************************************************************)
unit main;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, StdCtrls,
  ExtCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    gb1: TGroupBox;
    gb2: TGroupBox;
    gb3: TGroupBox;
    lbInfo: TLabel;
    lb1: TLabel;
    lb2: TLabel;
    lbXAxis: TLabel;
    lbYAxis: TLabel;
    lbIndex: TLabel;
    lbButtons: TLabel;
    btnClose: TButton;
    imView: TImage;
    Bevel1: TBevel;
    gb4: TGroupBox;
    Label1: TLabel;
    Label2: TLabel;
    tbDeadZone: TTrackBar;
    tbSaturation: TTrackBar;
    procedure btnCloseClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure tbDeadZoneChange(Sender: TObject);
    procedure tbSaturationChange(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure Idle( Sender: TObject; var Done: Boolean );
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses
  DirectInput8;




//------------------------------------------------------------------------------
// Константы и глобальные переменные
//------------------------------------------------------------------------------
var
  lpDI8:          IDirectInput8       = nil;
  lpDIGameDevice: IDirectInputDevice8 = nil;




//------------------------------------------------------------------------------
// Имя:      EnumJoysticksCallback()
// Описание: Перечисляет установленные в системе игровые контроллеры
//------------------------------------------------------------------------------
function EnumJoysticksCallback( var lpddi: TDIDeviceInstanceA; pvRef: Pointer ):
   Integer; stdcall;
var
  didc: TDIDEVCAPS;
begin
  Result := DIENUM_CONTINUE;

  // На основе полученного GUID устройства создаём объект для работы с ним
  if FAILED( lpDI8.CreateDevice( lpddi.guidInstance, lpDIGameDevice, nil ) ) then
     Exit;
  lpDIGameDevice._AddRef;

  // Получаем тип устройства (для вывода на метку)
  case GET_DIDEVICE_TYPE( lpddi.dwDevType ) of
    DI8DEVTYPE_JOYSTICK: Form1.lbInfo.Caption := 'Джойстик ';
    DI8DEVTYPE_GAMEPAD:  Form1.lbInfo.Caption := 'Геймпад ';
    DI8DEVTYPE_DRIVING:  Form1.lbInfo.Caption := 'Руль ';
    DI8DEVTYPE_FLIGHT:   Form1.lbInfo.Caption := 'Штурвал ';
  end;

  // Подготавливаем структуру TDIDEVCAPS, она поможет получить сведения о
  // количестве осей и кнопок устройства
  ZeroMemory( @didc, SizeOf( TDIDEVCAPS ) );
  didc.dwSize := SizeOf( TDIDEVCAPS );

  // Получаем информацию методом устройства
  if FAILED( lpDIGameDevice.GetCapabilities( didc ) ) then
     Exit;

  // Выводим собранные сведения
  Form1.lbInfo.Caption := Form1.lbInfo.Caption + lpddi.tszProductName + ' (' +
                          IntToStr( didc.dwAxes ) + ' оси, '+
                          IntToStr( didc.dwButtons ) + ' кнопок).';
  Result := DIENUM_STOP;
end;




//------------------------------------------------------------------------------
// Имя:      CalibrateGameDevice()
// Описание: Калибрует оси контроллера 
//------------------------------------------------------------------------------
function CalibrateGameDevice( dwDeadZone, dwSaturation: DWORD ): Boolean;
var
  diprgn: TDIPROPRANGE;
  diprdw: TDIPROPDWORD;
begin
  Result := FALSE;

  // Подготавливаем структуру TDIPROPRANGE
  ZeroMemory( @diprgn, SizeOf( TDIPROPRANGE ) );
  diprgn.diph.dwSize       := SizeOf( TDIPROPRANGE );
  diprgn.diph.dwHeaderSize := SizeOf( TDIPROPHEADER );
  diprgn.diph.dwHow        := DIPH_BYOFFSET;

  // Настраиваем диапазон координат по оси X
  diprgn.diph.dwObj := DIJOFS_X;
  diprgn.lMin       := -1000;
  diprgn.lMax       := +1000;
  // Устанавливаем диапазон
  if FAILED( lpDIGameDevice.SetProperty( DIPROP_RANGE, diprgn.diph ) ) then
     Exit;

  // Настраиваем диапазон координат по оси Y
  diprgn.diph.dwObj := DIJOFS_Y;
  diprgn.lMin       := -1000;
  diprgn.lMax       := +1000;
  // Устанавливаем диапазон
  if FAILED( lpDIGameDevice.SetProperty( DIPROP_RANGE, diprgn.diph ) ) then
     Exit;

  //----------------------------------------------------------------------------
  // Подготавливаем структуру TDIPROPDWORD
  ZeroMemory( @diprdw, SizeOf( TDIPROPDWORD ) );
  diprdw.diph.dwSize       := SizeOf( TDIPROPDWORD );
  diprdw.diph.dwHeaderSize := SizeOf( TDIPROPHEADER );
  diprdw.diph.dwHow        := DIPH_BYOFFSET;
  diprdw.dwData            := dwDeadZone; // Область покоя

  // Устанавливаем область покоя для оси X
  diprdw.diph.dwObj := DIJOFS_X;
  if FAILED( lpDIGameDevice.SetProperty( DIPROP_DEADZONE, diprdw.diph ) ) then
     Exit;

  // Устанавливаем область покоя для оси Y
  diprdw.diph.dwObj := DIJOFS_Y;
  if FAILED( lpDIGameDevice.SetProperty( DIPROP_DEADZONE, diprdw.diph ) ) then
     Exit;

  //----------------------------------------------------------------------------
  diprdw.dwData := dwSaturation; // Область насыщения

  // Устанавливаем область покоя для оси X
  diprdw.diph.dwObj := DIJOFS_X;
  if FAILED( lpDIGameDevice.SetProperty( DIPROP_SATURATION, diprdw.diph ) ) then
     Exit;

  // Устанавливаем область покоя для оси Y
  diprdw.diph.dwObj := DIJOFS_Y;
  if FAILED( lpDIGameDevice.SetProperty( DIPROP_SATURATION, diprdw.diph ) ) then
     Exit;

  Result := TRUE;
end;




//------------------------------------------------------------------------------
// Имя:      InitDirectInput()
// Описание: Производит инициализацию объектов DirectInput в программе
//------------------------------------------------------------------------------
function InitDirectInput( hWnd: HWND ): Boolean;
begin
  Result := FALSE;

  // Создаём объект DirectInput
  if FAILED( DirectInput8Create( GetModuleHandle( 0 ), DIRECTINPUT_VERSION,
                                 IID_IDirectInput8, lpDI8, nil ) ) then
     Exit;
  lpDI8._AddRef();

  // Перечисляем методом созданного объекта установленные игровые устройства
  if FAILED( lpDI8.EnumDevices( DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, nil,
                                DIEDFL_ATTACHEDONLY ) ) then
     Exit;

  // Убеждаемся, что игровое устройство действительно обнаружено
  if lpDIGameDevice = nil then
     Exit;

  // Установим формат данных для "простого устройства" - предопределённый формат
  // Формат данных определяет, какие элементы управления в устройстве нас инте-
  // ресуют и как о них будет сообщено. Эта строка говорит DInput, что мы будем
  // использовать структуру DIJOYSTATE2 в методе GetDeviceState()
  if FAILED( lpDIGameDevice.SetDataFormat( @c_dfDIJoystick ) ) then
     Exit;

  // Устанавливаем уровень кооперации - можно фоновый и монопольный сразу
  if FAILED( lpDIGameDevice.SetCooperativeLevel( hWnd, DISCL_BACKGROUND or
                                                       DISCL_EXCLUSIVE ) ) then
     Exit;

  if not CalibrateGameDevice( 0, 6000 ) then
     Exit;

  // Захватываем игровое устройство
  lpDIGameDevice.Acquire();

  Result := TRUE;
end;




//------------------------------------------------------------------------------
// Имя:      ReleaseDirectInput()
// Описание: Производит удаление объектов DirectInput
//------------------------------------------------------------------------------
procedure ReleaseDirectInput();
begin
  // Удаляем объект для работы с игровым контроллером
  if lpDIGameDevice <> nil then
  begin
    lpDIGameDevice.Unacquire(); // Освобождаем устройство
    lpDIGameDevice._Release();
    lpDIGameDevice := nil;
  end;

  // Удаляем главный объект DirectInput (всегда последним)
  if lpDI8 <> nil then
  begin
    lpDI8._Release();
    lpDI8 := nil;
  end;
end;




//------------------------------------------------------------------------------
// Имя:      UpdateGameDeviceState()
// Описание: Производит опрос контроллера и выводит данные в окно
//------------------------------------------------------------------------------
function UpdateGameDeviceState(): Boolean;
var
  js2: TDIJOYSTATE;
  i,
  nXPos,
  nYPos:  Integer;

  hr:    HRESULT;
begin
  Result := FALSE;

  // Опрашиваем устройство ввода
  if FAILED( lpDIGameDevice.Poll() ) then
  begin
    // Если метод потерпел неудачу
    hr := lpDIGameDevice.Acquire();
    // Продолжаем, пока не захватим (спорный момент - можем зависнуть)
    while hr = DIERR_INPUTLOST do hr := lpDIGameDevice.Acquire();

    Result := TRUE;
    Exit;
  end;

  // Производим опрос состояния джойстика, данные записаны в структуре js2
  if FAILED( lpDIGameDevice.GetDeviceState( SizeOf( TDIJOYSTATE), @js2 ) ) then
     Exit;

  // Выводим данные в окно
  with Form1 do
  begin
    lbXAxis.Caption := IntToStr( js2.lX );
    lbYAxis.Caption := IntToStr( js2.lY );

    lbButtons.Caption := '';

    // Теперь выводим список номеров нажатых кнопок
    for i := 0 to 127 do // DirectInput8 теоретически поддерживает 128 кнопок
    begin
      if js2.rgbButtons[ i ] = $80 then // Признак нажатой кнопки
      if i <= 9 then lbButtons.Caption := lbButtons.Caption + Format( '0%d ', [ i + 1 ] )
                else lbButtons.Caption := lbButtons.Caption + Format( '%d ', [ i + 1 ] );
    end;

    // Вычисляем координаты курсора в окошке
    nXPos := Round( js2.lX / 22 ) + 50;
    nYPos := Round( js2.lY / 22 ) + 50;

    // Рисуем курсор
    with imView.Canvas do
    begin
      // Очищаем поверхность рисования
      Brush.Color := clWhite;
      FillRect( Canvas.ClipRect );

      // Рисуем курсор
      MoveTo( nXPos - 4, nYPos );
      LineTo( nXPos + 5, nYPos );

      MoveTo( nXPos, nYPos + 4 );
      LineTo( nXPos, nYPos - 5 );
    end;
  end;

  Result := TRUE;
end;




//------------------------------------------------------------------------------
// Имя:      TForm1.Idle()
// Описание: Вызывает функцию опроса состояния джойстика
//------------------------------------------------------------------------------
procedure TForm1.Idle( Sender: TObject; var Done: Boolean );
begin
  // Если данные от джойстика не получены
  if not UpdateGameDeviceState() then
  begin
    MessageBox( Form1.Handle, 'Потеряно устройство управления!',
                'Ошибка!', MB_ICONHAND);
    Form1.Close();
  end;

  Done := FALSE;
end;




//------------------------------------------------------------------------------
// Имя:      TForm1.FormCreate()
// Описание: Производит инициализацию DirectInput при старте программы
//------------------------------------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
  if not InitDirectInput( Form1.Handle ) then
  begin
    MessageBox( Form1.Handle, 'Ошибка при инициализации DirectInput!' + #13 +
                'Возможно, ни одно из игровых устройств к компьютеру не присоединёно.',
                'Ошибка!', MB_ICONHAND);
    ReleaseDirectInput();
    Halt;
  end;

  // Назначаем обработчик Idle-события. Компонент TTimer не позволит раскрыть
  // всех преимуществ использования DirectInput.
  Application.OnIdle := Idle;
end;




//------------------------------------------------------------------------------
// Имя:      TForm1.tbDeadZoneChange()
// Описание: Устанавливает облась покоя для оси контроллера
//------------------------------------------------------------------------------
procedure TForm1.tbDeadZoneChange(Sender: TObject);
begin
  CalibrateGameDevice( tbDeadZone.Position * 2000, tbSaturation.Position * 2000 );
end;



//------------------------------------------------------------------------------
// Имя:      TForm1.tbSaturationChange()
// Описание: Устанавливает облась насыщения для оси контроллера
//------------------------------------------------------------------------------
procedure TForm1.tbSaturationChange(Sender: TObject);
begin
  CalibrateGameDevice( tbDeadZone.Position * 2000, tbSaturation.Position * 2000 );
end;




//------------------------------------------------------------------------------
// Имя:      TForm1.btnCloseClick()
// Описание: Закрывает программу
//------------------------------------------------------------------------------
procedure TForm1.btnCloseClick(Sender: TObject);
begin
  Form1.Close();
end;




//------------------------------------------------------------------------------
// Имя:      TForm1.FormDestroy()
// Описание: Вызывается при удалении программы из памяти
//------------------------------------------------------------------------------
procedure TForm1.FormDestroy(Sender: TObject);
begin
   ReleaseDirectInput();
end;

end.
