GXS.Joystick.pas 12 KB

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