GLS.Joystick.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.Joystick;
  5. (* Component for handling joystick messages *)
  6. interface
  7. uses
  8. Winapi.Windows,
  9. Winapi.Messages,
  10. Winapi.MMSystem,
  11. System.SysUtils,
  12. System.Classes;
  13. type
  14. TJoystickButton = (jbButton1, jbButton2, jbButton3, jbButton4);
  15. TJoystickButtons = set of TJoystickButton;
  16. TJoystickID = (jidNoJoystick, jidJoystick1, jidJoystick2);
  17. TJoystickDesignMode = (jdmInactive, jdmActive);
  18. TJoyPos = (jpMin, jpCenter, jpMax);
  19. TJoyAxis = (jaX, jaY, jaZ, jaR, jaU, jaV);
  20. TJoystickEvent = procedure(Sender: TObject; JoyID: TJoystickID;
  21. Buttons: TJoystickButtons; XDeflection, YDeflection: Integer) of Object;
  22. // A component interfacing the Joystick via the (regular) windows API.
  23. TGLJoystick = class(TComponent)
  24. private
  25. FWindowHandle: HWND;
  26. FNumButtons, FLastX, FLastY, FLastZ: Integer;
  27. FThreshold, FInterval: Cardinal;
  28. FCapture, FNoCaptureErrors: Boolean;
  29. FJoystickID: TJoystickID;
  30. FMinMaxInfo: array [TJoyAxis, TJoyPos] of Integer;
  31. FXPosInfo, FYPosInfo: array [0 .. 4] of Integer;
  32. FOnJoystickButtonChange, FOnJoystickMove: TJoystickEvent;
  33. FXPosition, FYPosition: Integer;
  34. FJoyButtons: TJoystickButtons;
  35. procedure SetCapture(AValue: Boolean);
  36. procedure SetInterval(AValue: Cardinal);
  37. procedure SetJoystickID(AValue: TJoystickID);
  38. procedure SetThreshold(AValue: Cardinal);
  39. protected
  40. function MakeJoyButtons(Param: UINT): TJoystickButtons;
  41. procedure DoJoystickCapture(AHandle: HWND; AJoystick: TJoystickID);
  42. procedure DoJoystickRelease(AJoystick: TJoystickID);
  43. procedure DoXYMove(Buttons: Word; XPos, YPos: Integer);
  44. procedure DoZMove(Buttons: Word; ZPos: Integer);
  45. procedure ReapplyCapture(AJoystick: TJoystickID);
  46. procedure WndProc(var Msg: TMessage);
  47. procedure Loaded; override;
  48. public
  49. constructor Create(AOwner: TComponent); override;
  50. destructor Destroy; override;
  51. procedure Assign(Source: TPersistent); override;
  52. property JoyButtons: TJoystickButtons read FJoyButtons;
  53. property XPosition: Integer read FXPosition;
  54. property YPosition: Integer read FYPosition;
  55. published
  56. (* When set to True, the component attempts to capture the joystick.
  57. If capture is successfull, retrieving joystick status is possible,
  58. if not, an error message is triggered. *)
  59. property Capture: Boolean read FCapture write SetCapture default False;
  60. // If true joystick capture errors do not result in exceptions.
  61. property NoCaptureErrors: Boolean read FNoCaptureErrors
  62. write FNoCaptureErrors default True;
  63. // Polling frequency (milliseconds)
  64. property Interval: Cardinal read FInterval write SetInterval default 100;
  65. property JoystickID: TJoystickID read FJoystickID write SetJoystickID
  66. default jidNoJoystick;
  67. property Threshold: Cardinal read FThreshold write SetThreshold
  68. default 1000;
  69. property OnJoystickButtonChange: TJoystickEvent read FOnJoystickButtonChange
  70. write FOnJoystickButtonChange;
  71. property OnJoystickMove: TJoystickEvent read FOnJoystickMove
  72. write FOnJoystickMove;
  73. end;
  74. // ---------------------------------------------------------------------
  75. implementation
  76. // ---------------------------------------------------------------------
  77. const
  78. cJoystickIDToNative: array [jidNoJoystick .. jidJoystick2] of Byte = (9,
  79. JOYSTICKID1, JOYSTICKID2);
  80. // ------------------
  81. // ------------------ TJoystick ------------------
  82. // ------------------
  83. constructor TGLJoystick.Create(AOwner: TComponent);
  84. begin
  85. inherited;
  86. FWindowHandle := AllocateHWnd(WndProc);
  87. FInterval := 100;
  88. FThreshold := 1000;
  89. FJoystickID := jidNoJoystick;
  90. FLastX := -1;
  91. FLastY := -1;
  92. FLastZ := -1;
  93. FNoCaptureErrors := True;
  94. end;
  95. destructor TGLJoystick.Destroy;
  96. begin
  97. DeallocateHWnd(FWindowHandle);
  98. inherited;
  99. end;
  100. procedure TGLJoystick.WndProc(var Msg: TMessage);
  101. begin
  102. with Msg do
  103. begin
  104. case FJoystickID of
  105. jidJoystick1: // check only 1st stick
  106. case Msg of
  107. MM_JOY1MOVE:
  108. DoXYMove(wParam, lParamLo, lParamHi);
  109. MM_JOY1ZMOVE:
  110. DoZMove(wParam, lParamLo);
  111. MM_JOY1BUTTONDOWN:
  112. if Assigned(FOnJoystickButtonChange) then
  113. FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
  114. FLastX, FLastY);
  115. MM_JOY1BUTTONUP:
  116. if Assigned(FOnJoystickButtonChange) then
  117. FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
  118. FLastX, FLastY);
  119. end;
  120. jidJoystick2: // check only 2nd stick
  121. case Msg of
  122. MM_JOY2MOVE:
  123. DoXYMove(wParam, lParamLo, lParamHi);
  124. MM_JOY2ZMOVE:
  125. DoZMove(wParam, lParamLo);
  126. MM_JOY2BUTTONDOWN:
  127. if Assigned(FOnJoystickButtonChange) then
  128. FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
  129. FLastX, FLastY);
  130. MM_JOY2BUTTONUP:
  131. if Assigned(FOnJoystickButtonChange) then
  132. FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
  133. FLastX, FLastY);
  134. end;
  135. jidNoJoystick:
  136. ; // ignore
  137. else
  138. Assert(False);
  139. end;
  140. Result := 0;
  141. end;
  142. end;
  143. procedure TGLJoystick.Loaded;
  144. begin
  145. inherited;
  146. ReapplyCapture(FJoystickID);
  147. end;
  148. procedure TGLJoystick.Assign(Source: TPersistent);
  149. begin
  150. if Source is TGLJoystick then
  151. begin
  152. FInterval := TGLJoystick(Source).FInterval;
  153. FThreshold := TGLJoystick(Source).FThreshold;
  154. FCapture := TGLJoystick(Source).FCapture;
  155. FJoystickID := TGLJoystick(Source).FJoystickID;
  156. try
  157. ReapplyCapture(FJoystickID);
  158. except
  159. FJoystickID := jidNoJoystick;
  160. FCapture := False;
  161. raise;
  162. end;
  163. end
  164. else
  165. inherited Assign(Source);
  166. end;
  167. function TGLJoystick.MakeJoyButtons(Param: UINT): TJoystickButtons;
  168. begin
  169. Result := [];
  170. if (Param and JOY_BUTTON1) > 0 then
  171. Include(Result, jbButton1);
  172. if (Param and JOY_BUTTON2) > 0 then
  173. Include(Result, jbButton2);
  174. if (Param and JOY_BUTTON3) > 0 then
  175. Include(Result, jbButton3);
  176. if (Param and JOY_BUTTON4) > 0 then
  177. Include(Result, jbButton4);
  178. FJoyButtons := Result;
  179. end;
  180. function DoScale(AValue: Integer): Integer;
  181. begin
  182. Result := Round(AValue / 1);
  183. end;
  184. procedure TGLJoystick.ReapplyCapture(AJoystick: TJoystickID);
  185. var
  186. jc: TJoyCaps;
  187. begin
  188. DoJoystickRelease(AJoystick);
  189. if FCapture and (not(csDesigning in ComponentState)) then
  190. with jc do
  191. begin
  192. joyGetDevCaps(cJoystickIDToNative[FJoystickID], @jc, SizeOf(jc));
  193. FNumButtons := wNumButtons;
  194. FMinMaxInfo[jaX, jpMin] := DoScale(wXMin);
  195. FMinMaxInfo[jaX, jpCenter] := DoScale((wXMin + wXMax) div 2);
  196. FMinMaxInfo[jaX, jpMax] := DoScale(wXMax);
  197. FMinMaxInfo[jaY, jpMin] := DoScale(wYMin);
  198. FMinMaxInfo[jaY, jpCenter] := DoScale((wYMin + wYMax) div 2);
  199. FMinMaxInfo[jaY, jpMax] := DoScale(wYMax);
  200. FMinMaxInfo[jaZ, jpMin] := DoScale(wZMin);
  201. FMinMaxInfo[jaZ, jpCenter] := DoScale((wZMin + wZMax) div 2);
  202. FMinMaxInfo[jaZ, jpMax] := DoScale(wZMax);
  203. FMinMaxInfo[jaR, jpMin] := DoScale(wRMin);
  204. FMinMaxInfo[jaR, jpCenter] := DoScale((wRMin + wRMax) div 2);
  205. FMinMaxInfo[jaR, jpMax] := DoScale(wRMax);
  206. FMinMaxInfo[jaU, jpMin] := DoScale(wUMin);
  207. FMinMaxInfo[jaU, jpCenter] := DoScale((wUMin + wUMax) div 2);
  208. FMinMaxInfo[jaU, jpMax] := DoScale(wUMax);
  209. FMinMaxInfo[jaV, jpMin] := DoScale(wVMin);
  210. FMinMaxInfo[jaV, jpCenter] := DoScale((wVMin + wVMax) div 2);
  211. FMinMaxInfo[jaV, jpMax] := DoScale(wVMax);
  212. DoJoystickCapture(FWindowHandle, AJoystick)
  213. end;
  214. end;
  215. procedure TGLJoystick.DoJoystickCapture(AHandle: HWND; AJoystick: TJoystickID);
  216. var
  217. res: Cardinal;
  218. begin
  219. res := joySetCapture(AHandle, cJoystickIDToNative[AJoystick],
  220. FInterval, True);
  221. if res <> JOYERR_NOERROR then
  222. begin
  223. FCapture := False;
  224. if not NoCaptureErrors then
  225. begin
  226. case res of
  227. MMSYSERR_NODRIVER:
  228. raise Exception.Create('No Joystick Driver');
  229. JOYERR_UNPLUGGED:
  230. raise Exception.Create('Connect Joystick');
  231. JOYERR_NOCANDO:
  232. raise Exception.Create('Joystick Error');
  233. else
  234. raise Exception.Create('Joystick Error');
  235. end;
  236. end;
  237. end
  238. else
  239. joySetThreshold(cJoystickIDToNative[AJoystick], FThreshold);
  240. end;
  241. procedure TGLJoystick.DoJoystickRelease(AJoystick: TJoystickID);
  242. begin
  243. if AJoystick <> jidNoJoystick then
  244. joyReleaseCapture(cJoystickIDToNative[AJoystick]);
  245. end;
  246. procedure TGLJoystick.SetCapture(AValue: Boolean);
  247. begin
  248. if FCapture <> AValue then
  249. begin
  250. FCapture := AValue;
  251. if not(csReading in ComponentState) then
  252. begin
  253. try
  254. ReapplyCapture(FJoystickID);
  255. except
  256. FCapture := False;
  257. raise;
  258. end;
  259. end;
  260. end;
  261. end;
  262. procedure TGLJoystick.SetInterval(AValue: Cardinal);
  263. begin
  264. if FInterval <> AValue then
  265. begin
  266. FInterval := AValue;
  267. if not(csReading in ComponentState) then
  268. ReapplyCapture(FJoystickID);
  269. end;
  270. end;
  271. procedure TGLJoystick.SetJoystickID(AValue: TJoystickID);
  272. begin
  273. if FJoystickID <> AValue then
  274. begin
  275. try
  276. if not(csReading in ComponentState) then
  277. ReapplyCapture(AValue);
  278. FJoystickID := AValue;
  279. except
  280. on E: Exception do
  281. begin
  282. ReapplyCapture(FJoystickID);
  283. ShowException(E, nil);
  284. end;
  285. end;
  286. end;
  287. end;
  288. // ------------------------------------------------------------------------------
  289. procedure TGLJoystick.SetThreshold(AValue: Cardinal);
  290. begin
  291. if FThreshold <> AValue then
  292. begin
  293. FThreshold := AValue;
  294. if not(csReading in ComponentState) then
  295. ReapplyCapture(FJoystickID);
  296. end;
  297. end;
  298. // ------------------------------------------------------------------------------
  299. function Approximation(const Data: array of Integer): Integer;
  300. // calculate a better estimation of the last value in the given data, depending
  301. // on the other values (used to approximate a smoother joystick movement)
  302. //
  303. // based on Gauss' principle of smallest squares in Maximum-Likelihood and
  304. // linear normal equations
  305. var
  306. SumX, SumY, SumXX, SumYX: Double;
  307. I, Comps: Integer;
  308. a0, a1: Double;
  309. begin
  310. SumX := 0;
  311. SumY := 0;
  312. SumXX := 0;
  313. SumYX := 0;
  314. Comps := High(Data) + 1;
  315. for I := 0 to High(Data) do
  316. begin
  317. SumX := SumX + I;
  318. SumY := SumY + Data[I];
  319. SumXX := SumXX + I * I;
  320. SumYX := SumYX + I * Data[I];
  321. end;
  322. a0 := (SumY * SumXX - SumX * SumYX) / (Comps * SumXX - SumX * SumX);
  323. a1 := (Comps * SumYX - SumY * SumX) / (Comps * SumXX - SumX * SumX);
  324. Result := Round(a0 + a1 * High(Data));
  325. end;
  326. procedure TGLJoystick.DoXYMove(Buttons: Word; XPos, YPos: Integer);
  327. var
  328. I: Integer;
  329. dX, dY: Integer;
  330. begin
  331. XPos := DoScale(XPos);
  332. YPos := DoScale(YPos);
  333. if (FLastX = -1) or (FLastY = -1) then
  334. begin
  335. FLastX := XPos;
  336. FLastY := YPos;
  337. for I := 0 to 4 do
  338. begin
  339. FXPosInfo[I] := XPos;
  340. FYPosInfo[I] := YPos;
  341. end;
  342. end
  343. else
  344. begin
  345. Move(FXPosInfo[1], FXPosInfo[0], 16);
  346. FXPosInfo[4] := XPos;
  347. XPos := Approximation(FXPosInfo);
  348. Move(FYPosInfo[1], FYPosInfo[0], 16);
  349. FYPosInfo[4] := YPos;
  350. YPos := Approximation(FYPosInfo);
  351. MakeJoyButtons(Buttons);
  352. dX := Round((XPos - FMinMaxInfo[jaX, jpCenter]) * 100 / FMinMaxInfo[jaX,
  353. jpCenter]);
  354. dY := Round((YPos - FMinMaxInfo[jaY, jpCenter]) * 100 / FMinMaxInfo[jaY,
  355. jpCenter]);
  356. if Assigned(FOnJoystickMove) then
  357. FOnJoystickMove(Self, FJoystickID, FJoyButtons, dX, dY);
  358. FXPosition := dX;
  359. FYPosition := dY;
  360. FLastX := XPos;
  361. FLastY := YPos;
  362. end;
  363. end;
  364. procedure TGLJoystick.DoZMove(Buttons: Word; ZPos: Integer);
  365. begin
  366. if FLastZ = -1 then
  367. FLastZ := Round(ZPos * 100 / 65536);
  368. MakeJoyButtons(Buttons);
  369. end;
  370. //------------------------------------
  371. initialization
  372. //------------------------------------
  373. RegisterClasses([TGLJoystick]);
  374. end.