GLJoystick.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. //
  2. // This unit is part of the GLScene Engine, http://glscene.org
  3. //
  4. unit GLJoystick;
  5. (* Component for handling joystick messages *)
  6. interface
  7. {$I GLScene.inc}
  8. uses
  9. Winapi.Windows,
  10. Winapi.Messages,
  11. Winapi.MMSystem,
  12. System.SysUtils,
  13. System.Classes,
  14. VCL.Forms,
  15. VCL.Controls,
  16. GLS.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; Buttons: TJoystickButtons;
  25. XDeflection, YDeflection: Integer) of Object;
  26. {A component interfacing the Joystick via the (regular) windows API. }
  27. TGLJoystick = 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 write FNoCaptureErrors default True;
  66. {Polling frequency (milliseconds) }
  67. property Interval : Cardinal read FInterval write SetInterval default 100;
  68. property JoystickID: TJoystickID read FJoystickID write SetJoystickID default jidNoJoystick;
  69. property Threshold: Cardinal read FThreshold write SetThreshold default 1000;
  70. property OnJoystickButtonChange: TJoystickEvent read FOnJoystickButtonChange write FOnJoystickButtonChange;
  71. property OnJoystickMove: TJoystickEvent read FOnJoystickMove write FOnJoystickMove;
  72. end;
  73. // ---------------------------------------------------------------------
  74. implementation
  75. // ---------------------------------------------------------------------
  76. const
  77. cJoystickIDToNative : array [jidNoJoystick..jidJoystick2] of Byte =
  78. (9, JOYSTICKID1, JOYSTICKID2);
  79. // ------------------
  80. // ------------------ TJoystick ------------------
  81. // ------------------
  82. constructor TGLJoystick.Create(AOwner: TComponent);
  83. begin
  84. inherited;
  85. FWindowHandle := AllocateHWnd(WndProc);
  86. FInterval := 100;
  87. FThreshold := 1000;
  88. FJoystickID := jidNoJoystick;
  89. FLastX := -1;
  90. FLastY := -1;
  91. FLastZ := -1;
  92. FNoCaptureErrors := True;
  93. end;
  94. destructor TGLJoystick.Destroy;
  95. begin
  96. DeallocateHWnd(FWindowHandle);
  97. inherited;
  98. end;
  99. procedure TGLJoystick.WndProc(var Msg: TMessage);
  100. begin
  101. with Msg do begin
  102. case FJoystickID of
  103. jidJoystick1 : // check only 1st stick
  104. case Msg of
  105. MM_JOY1MOVE :
  106. DoXYMove(wParam, lParamLo, lParamHi);
  107. MM_JOY1ZMOVE :
  108. DoZMove(wParam, lParamLo);
  109. MM_JOY1BUTTONDOWN :
  110. if Assigned(FOnJoystickButtonChange) then
  111. FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
  112. FLastX, FLastY);
  113. MM_JOY1BUTTONUP :
  114. if Assigned(FOnJoystickButtonChange) then
  115. FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
  116. FLastX, FLastY);
  117. end;
  118. jidJoystick2 : // check only 2nd stick
  119. case Msg of
  120. MM_JOY2MOVE :
  121. DoXYMove(wParam, lParamLo, lParamHi);
  122. MM_JOY2ZMOVE :
  123. DoZMove(wParam, lParamLo);
  124. MM_JOY2BUTTONDOWN :
  125. if Assigned(FOnJoystickButtonChange) then
  126. FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
  127. FLastX, FLastY);
  128. MM_JOY2BUTTONUP :
  129. if Assigned(FOnJoystickButtonChange) then
  130. FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
  131. FLastX, FLastY);
  132. end;
  133. jidNoJoystick : ; // ignore
  134. else
  135. Assert(False);
  136. end;
  137. Result:=0;
  138. end;
  139. end;
  140. procedure TGLJoystick.Loaded;
  141. begin
  142. inherited;
  143. ReapplyCapture(FJoystickID);
  144. end;
  145. procedure TGLJoystick.Assign(Source: TPersistent);
  146. begin
  147. if Source is TGLJoystick then begin
  148. FInterval := TGLJoystick(Source).FInterval;
  149. FThreshold := TGLJoystick(Source).FThreshold;
  150. FCapture := TGLJoystick(Source).FCapture;
  151. FJoystickID := TGLJoystick(Source).FJoystickID;
  152. try
  153. ReapplyCapture(FJoystickID);
  154. except
  155. FJoystickID := jidNoJoystick;
  156. FCapture := False;
  157. raise;
  158. end;
  159. end else inherited Assign(Source);
  160. end;
  161. function TGLJoystick.MakeJoyButtons(Param: UINT): TJoystickButtons;
  162. begin
  163. Result := [];
  164. if (Param and JOY_BUTTON1) > 0 then Include(Result, jbButton1);
  165. if (Param and JOY_BUTTON2) > 0 then Include(Result, jbButton2);
  166. if (Param and JOY_BUTTON3) > 0 then Include(Result, jbButton3);
  167. if (Param and JOY_BUTTON4) > 0 then Include(Result, jbButton4);
  168. FJoyButtons:=Result;
  169. end;
  170. function DoScale(aValue : Integer) : Integer;
  171. begin
  172. Result:=Round(AValue/1);
  173. end;
  174. procedure TGLJoystick.ReapplyCapture(AJoystick: TJoystickID);
  175. var
  176. jc : TJoyCaps;
  177. begin
  178. DoJoystickRelease(AJoystick);
  179. if FCapture and (not (csDesigning in ComponentState)) then with JC do begin
  180. joyGetDevCaps(cJoystickIDToNative[FJoystickID], @JC, SizeOf(JC));
  181. FNumButtons := wNumButtons;
  182. FMinMaxInfo[jaX, jpMin] := DoScale(wXMin);
  183. FMinMaxInfo[jaX, jpCenter] := DoScale((wXMin + wXMax) div 2); FMinMaxInfo[jaX, jpMax] := DoScale(wXMax);
  184. FMinMaxInfo[jaY, jpMin] := DoScale(wYMin); FMinMaxInfo[jaY, jpCenter] := DoScale((wYMin + wYMax) div 2); FMinMaxInfo[jaY, jpMax] := DoScale(wYMax);
  185. FMinMaxInfo[jaZ, jpMin] := DoScale(wZMin); FMinMaxInfo[jaZ, jpCenter] := DoScale((wZMin + wZMax) div 2); FMinMaxInfo[jaZ, jpMax] := DoScale(wZMax);
  186. FMinMaxInfo[jaR, jpMin] := DoScale(wRMin); FMinMaxInfo[jaR, jpCenter] := DoScale((wRMin + wRMax) div 2); FMinMaxInfo[jaR, jpMax] := DoScale(wRMax);
  187. FMinMaxInfo[jaU, jpMin] := DoScale(wUMin); FMinMaxInfo[jaU, jpCenter] := DoScale((wUMin + wUMax) div 2); FMinMaxInfo[jaU, jpMax] := DoScale(wUMax);
  188. FMinMaxInfo[jaV, jpMin] := DoScale(wVMin); FMinMaxInfo[jaV, jpCenter] := DoScale((wVMin + wVMax) div 2); FMinMaxInfo[jaV, jpMax] := DoScale(wVMax);
  189. DoJoystickCapture(FWindowHandle, AJoystick)
  190. end;
  191. end;
  192. procedure TGLJoystick.DoJoystickCapture(AHandle: HWND; AJoystick: TJoystickID);
  193. var
  194. res : Cardinal;
  195. begin
  196. res:=joySetCapture(AHandle, cJoystickIDToNative[AJoystick], FInterval, True);
  197. if res<>JOYERR_NOERROR then begin
  198. FCapture:=False;
  199. if not NoCaptureErrors then begin
  200. case res of
  201. MMSYSERR_NODRIVER : raise Exception.Create(strNoJoystickDriver);
  202. JOYERR_UNPLUGGED : raise Exception.Create(strConnectJoystick);
  203. JOYERR_NOCANDO : raise Exception.Create(strJoystickError);
  204. else
  205. raise Exception.Create(strJoystickError);
  206. end;
  207. end;
  208. end else joySetThreshold(cJoystickIDToNative[AJoystick], FThreshold);
  209. end;
  210. procedure TGLJoystick.DoJoystickRelease(AJoystick: TJoystickID);
  211. begin
  212. if AJoystick <> jidNoJoystick then
  213. joyReleaseCapture(cJoystickIDToNative[AJoystick]);
  214. end;
  215. procedure TGLJoystick.SetCapture(AValue: Boolean);
  216. begin
  217. if FCapture <> AValue then begin
  218. FCapture := AValue;
  219. if not (csReading in ComponentState) then begin
  220. try
  221. ReapplyCapture(FJoystickID);
  222. except
  223. FCapture := False;
  224. raise;
  225. end;
  226. end;
  227. end;
  228. end;
  229. procedure TGLJoystick.SetInterval(AValue: Cardinal);
  230. begin
  231. if FInterval <> AValue then begin
  232. FInterval := AValue;
  233. if not (csReading in ComponentState) then
  234. ReapplyCapture(FJoystickID);
  235. end;
  236. end;
  237. procedure TGLJoystick.SetJoystickID(AValue: TJoystickID);
  238. begin
  239. if FJoystickID <> AValue then begin
  240. try
  241. if not (csReading in ComponentState) then
  242. ReapplyCapture(AValue);
  243. FJoystickID := AValue;
  244. except
  245. on E: Exception do begin
  246. ReapplyCapture(FJoystickID);
  247. Application.ShowException(E);
  248. end;
  249. end;
  250. end;
  251. end;
  252. //------------------------------------------------------------------------------
  253. procedure TGLJoystick.SetThreshold(AValue: Cardinal);
  254. begin
  255. if FThreshold <> AValue then
  256. begin
  257. FThreshold := AValue;
  258. if not (csReading in ComponentState) then ReapplyCapture(FJoystickID);
  259. end;
  260. end;
  261. //------------------------------------------------------------------------------
  262. function Approximation(const Data: array of Integer): Integer;
  263. // calculate a better estimation of the last value in the given data, depending
  264. // on the other values (used to approximate a smoother joystick movement)
  265. //
  266. // based on Gauss' principle of smallest squares in Maximum-Likelihood and
  267. // linear normal equations
  268. var
  269. SumX, SumY, SumXX, SumYX: Double;
  270. I, Comps: Integer;
  271. a0, a1: Double;
  272. begin
  273. SumX := 0;
  274. SumY := 0;
  275. SumXX := 0;
  276. SumYX := 0;
  277. Comps := High(Data) + 1;
  278. for I := 0 to High(Data) do
  279. begin
  280. SumX := SumX + I;
  281. SumY := SumY + Data[I];
  282. SumXX := SumXX + I * I;
  283. SumYX := SumYX + I * Data[I];
  284. end;
  285. a0 := (SumY * SumXX - SumX * SumYX) / (Comps * SumXX - SumX * SumX);
  286. a1 := (Comps * SumYX - SumY * SumX) / (Comps * SumXX - SumX * SumX);
  287. Result := Round(a0 + a1 * High(Data));
  288. end;
  289. procedure TGLJoystick.DoXYMove(Buttons: Word; XPos, YPos: Integer);
  290. var
  291. I: Integer;
  292. dX, dY: Integer;
  293. begin
  294. XPos := DoScale(XPos);
  295. YPos := DoScale(YPos);
  296. if (FLastX = -1) or (FLastY = -1) then begin
  297. FLastX:=XPos;
  298. FLastY:=YPos;
  299. for I:=0 to 4 do begin
  300. FXPosInfo[I]:=XPos;
  301. FYPosInfo[I]:=YPos;
  302. end;
  303. end else begin
  304. Move(FXPosInfo[1], FXPosInfo[0], 16);
  305. FXPosInfo[4] := XPos;
  306. XPos := Approximation(FXPosInfo);
  307. Move(FYPosInfo[1], FYPosInfo[0], 16);
  308. FYPosInfo[4] := YPos;
  309. YPos := Approximation(FYPosInfo);
  310. MakeJoyButtons(Buttons);
  311. dX := Round((XPos-FMinMaxInfo[jaX, jpCenter]) * 100 / FMinMaxInfo[jaX, jpCenter]);
  312. dY := Round((YPos-FMinMaxInfo[jaY, jpCenter]) * 100 / FMinMaxInfo[jaY, jpCenter]);
  313. if Assigned(FOnJoystickMove) then
  314. FOnJoystickMove(Self, FJoystickID, FJoyButtons, dX, dY);
  315. FXPosition:=dX;
  316. FYPosition:=dY;
  317. FLastX:=XPos;
  318. FLastY:=YPos;
  319. end;
  320. end;
  321. procedure TGLJoystick.DoZMove(Buttons: Word; ZPos: Integer);
  322. begin
  323. if FLastZ = -1 then
  324. FLastZ := Round(ZPos * 100 / 65536);
  325. MakeJoyButtons(Buttons);
  326. end;
  327. initialization
  328. RegisterClasses([TGLJoystick]);
  329. end.