2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Component for handling joystick messages
8
17/03/07 - DaStr - Dropped Kylix support in favor of FPC (BugTracekrID=1681585)
9
29/01/02 - Egg - Added NoCaptureErrors
10
18/12/00 - Egg - Fix for supporting 2 joysticks simultaneously
11
14/04/00 - Egg - Various minor to major fixes, the component should
12
now work properly for the 4 first buttons and XY axis
13
20/03/00 - Egg - Creation from GLScene's TGLJoystick
21
{$IFDEF UNIX}{$Message Error 'Unit not supported'}{$ENDIF}
30
TJoystickButton = (jbButton1, jbButton2, jbButton3, jbButton4);
31
TJoystickButtons = set of TJoystickButton;
33
TJoystickID = (jidNoJoystick, jidJoystick1, jidJoystick2);
34
TJoystickDesignMode = (jdmInactive, jdmActive);
35
TJoyPos = (jpMin, jpCenter, jpMax);
36
TJoyAxis = (jaX, jaY, jaZ, jaR, jaU, jaV);
38
TJoystickEvent = procedure(Sender: TObject; JoyID: TJoystickID; Buttons: TJoystickButtons;
39
XDeflection, YDeflection: Integer) of Object;
43
{ A component interfacing the Joystick via the (regular) windows API. }
44
TGLJoystick = class (TComponent)
48
FNumButtons, FLastX, FLastY, FLastZ : Integer;
49
FThreshold, FInterval : Cardinal;
50
FCapture, FNoCaptureErrors : Boolean;
51
FJoystickID : TJoystickID;
52
FMinMaxInfo : array[TJoyAxis, TJoyPos] of Integer;
53
FXPosInfo, FYPosInfo : array[0..4] of Integer;
54
FOnJoystickButtonChange, FOnJoystickMove : TJoystickEvent;
55
FXPosition, FYPosition : Integer;
56
FJoyButtons : TJoystickButtons;
58
procedure SetCapture(AValue: Boolean);
59
procedure SetInterval(AValue: Cardinal);
60
procedure SetJoystickID(AValue: TJoystickID);
61
procedure SetThreshold(AValue: Cardinal);
65
function MakeJoyButtons(Param: UINT): TJoystickButtons;
66
procedure DoJoystickCapture(AHandle: HWND; AJoystick: TJoystickID);
67
procedure DoJoystickRelease(AJoystick: TJoystickID);
68
procedure DoXYMove(Buttons: Word; XPos, YPos: Integer);
69
procedure DoZMove(Buttons: Word; ZPos: Integer);
70
procedure ReapplyCapture(AJoystick: TJoystickID);
71
procedure WndProc(var Msg: TMessage);
72
procedure Loaded; override;
76
constructor Create(AOwner : TComponent); override;
77
destructor Destroy; override;
79
procedure Assign(Source: TPersistent); override;
81
property JoyButtons : TJoystickButtons read FJoyButtons;
82
property XPosition : Integer read FXPosition;
83
property YPosition : Integer read FYPosition;
87
{ When set to True, the component attempts to capture the joystick.
88
If capture is successfull, retrieving joystick status is possible,
89
if not, an error message is triggered. }
90
property Capture : Boolean read FCapture write SetCapture default False;
91
{ If true joystick capture errors do not result in exceptions. }
92
property NoCaptureErrors : Boolean read FNoCaptureErrors write FNoCaptureErrors default True;
93
{ Polling frequency (milliseconds) }
94
property Interval : Cardinal read FInterval write SetInterval default 100;
95
property JoystickID: TJoystickID read FJoystickID write SetJoystickID default jidNoJoystick;
96
property Threshold: Cardinal read FThreshold write SetThreshold default 1000;
97
property OnJoystickButtonChange: TJoystickEvent read FOnJoystickButtonChange write FOnJoystickButtonChange;
98
property OnJoystickMove: TJoystickEvent read FOnJoystickMove write FOnJoystickMove;
102
// ---------------------------------------------------------------------
103
// ---------------------------------------------------------------------
104
// ---------------------------------------------------------------------
106
// ---------------------------------------------------------------------
107
// ---------------------------------------------------------------------
108
// ---------------------------------------------------------------------
110
uses SysUtils, MMSystem;
113
cJoystickIDToNative : array [jidNoJoystick..jidJoystick2] of Byte =
114
(9, JOYSTICKID1, JOYSTICKID2);
117
glsNoJoystickDriver = 'There''s no joystick driver present';
118
glsConnectJoystick = 'Joystick is not connected to your system';
119
glsJoystickError = 'Your system reports a joystick error, can''t do anything about it';
122
// ------------------ TJoystick ------------------
127
constructor TGLJoystick.Create(AOwner: TComponent);
130
FWindowHandle := AllocateHWnd(WndProc);
133
FJoystickID := jidNoJoystick;
137
FNoCaptureErrors := True;
142
destructor TGLJoystick.Destroy;
144
DeallocateHWnd(FWindowHandle);
150
procedure TGLJoystick.WndProc(var Msg: TMessage);
154
jidJoystick1 : // check only 1st stick
157
DoXYMove(wParam, lParamLo, lParamHi);
159
DoZMove(wParam, lParamLo);
161
if Assigned(FOnJoystickButtonChange) then
162
FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
165
if Assigned(FOnJoystickButtonChange) then
166
FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
169
jidJoystick2 : // check only 2nd stick
172
DoXYMove(wParam, lParamLo, lParamHi);
174
DoZMove(wParam, lParamLo);
176
if Assigned(FOnJoystickButtonChange) then
177
FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
180
if Assigned(FOnJoystickButtonChange) then
181
FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
184
jidNoJoystick : ; // ignore
194
procedure TGLJoystick.Loaded;
197
ReapplyCapture(FJoystickID);
202
procedure TGLJoystick.Assign(Source: TPersistent);
204
if Source is TGLJoystick then begin
205
FInterval := TGLJoystick(Source).FInterval;
206
FThreshold := TGLJoystick(Source).FThreshold;
207
FCapture := TGLJoystick(Source).FCapture;
208
FJoystickID := TGLJoystick(Source).FJoystickID;
210
ReapplyCapture(FJoystickID);
212
FJoystickID := jidNoJoystick;
216
end else inherited Assign(Source);
221
function TGLJoystick.MakeJoyButtons(Param: UINT): TJoystickButtons;
224
if (Param and JOY_BUTTON1) > 0 then Include(Result, jbButton1);
225
if (Param and JOY_BUTTON2) > 0 then Include(Result, jbButton2);
226
if (Param and JOY_BUTTON3) > 0 then Include(Result, jbButton3);
227
if (Param and JOY_BUTTON4) > 0 then Include(Result, jbButton4);
233
function DoScale(aValue : Integer) : Integer;
235
Result:=Round(AValue/1);
240
procedure TGLJoystick.ReapplyCapture(AJoystick: TJoystickID);
244
DoJoystickRelease(AJoystick);
245
if FCapture and (not (csDesigning in ComponentState)) then with JC do begin
246
joyGetDevCaps(cJoystickIDToNative[FJoystickID], @JC, SizeOf(JC));
247
FNumButtons := wNumButtons;
248
FMinMaxInfo[jaX, jpMin] := DoScale(wXMin);
249
FMinMaxInfo[jaX, jpCenter] := DoScale((wXMin + wXMax) div 2); FMinMaxInfo[jaX, jpMax] := DoScale(wXMax);
250
FMinMaxInfo[jaY, jpMin] := DoScale(wYMin); FMinMaxInfo[jaY, jpCenter] := DoScale((wYMin + wYMax) div 2); FMinMaxInfo[jaY, jpMax] := DoScale(wYMax);
251
FMinMaxInfo[jaZ, jpMin] := DoScale(wZMin); FMinMaxInfo[jaZ, jpCenter] := DoScale((wZMin + wZMax) div 2); FMinMaxInfo[jaZ, jpMax] := DoScale(wZMax);
252
FMinMaxInfo[jaR, jpMin] := DoScale(wRMin); FMinMaxInfo[jaR, jpCenter] := DoScale((wRMin + wRMax) div 2); FMinMaxInfo[jaR, jpMax] := DoScale(wRMax);
253
FMinMaxInfo[jaU, jpMin] := DoScale(wUMin); FMinMaxInfo[jaU, jpCenter] := DoScale((wUMin + wUMax) div 2); FMinMaxInfo[jaU, jpMax] := DoScale(wUMax);
254
FMinMaxInfo[jaV, jpMin] := DoScale(wVMin); FMinMaxInfo[jaV, jpCenter] := DoScale((wVMin + wVMax) div 2); FMinMaxInfo[jaV, jpMax] := DoScale(wVMax);
255
DoJoystickCapture(FWindowHandle, AJoystick)
261
procedure TGLJoystick.DoJoystickCapture(AHandle: HWND; AJoystick: TJoystickID);
265
res:=joySetCapture(AHandle, cJoystickIDToNative[AJoystick], FInterval, True);
266
if res<>JOYERR_NOERROR then begin
268
if not NoCaptureErrors then begin
270
MMSYSERR_NODRIVER : raise Exception.Create(glsNoJoystickDriver);
271
JOYERR_UNPLUGGED : raise Exception.Create(glsConnectJoystick);
272
JOYERR_NOCANDO : raise Exception.Create(glsJoystickError);
274
raise Exception.Create(glsJoystickError);
277
end else joySetThreshold(cJoystickIDToNative[AJoystick], FThreshold);
282
procedure TGLJoystick.DoJoystickRelease(AJoystick: TJoystickID);
284
if AJoystick <> jidNoJoystick then
285
joyReleaseCapture(cJoystickIDToNative[AJoystick]);
290
procedure TGLJoystick.SetCapture(AValue: Boolean);
292
if FCapture <> AValue then begin
294
if not (csReading in ComponentState) then begin
296
ReapplyCapture(FJoystickID);
307
procedure TGLJoystick.SetInterval(AValue: Cardinal);
309
if FInterval <> AValue then begin
311
if not (csReading in ComponentState) then
312
ReapplyCapture(FJoystickID);
318
procedure TGLJoystick.SetJoystickID(AValue: TJoystickID);
320
if FJoystickID <> AValue then begin
322
if not (csReading in ComponentState) then
323
ReapplyCapture(AValue);
324
FJoystickID := AValue;
326
on E: Exception do begin
327
ReapplyCapture(FJoystickID);
328
Application.ShowException(E);
334
//------------------------------------------------------------------------------
336
procedure TGLJoystick.SetThreshold(AValue: Cardinal);
339
if FThreshold <> AValue then
341
FThreshold := AValue;
342
if not (csReading in ComponentState) then ReapplyCapture(FJoystickID);
346
//------------------------------------------------------------------------------
348
function Approximation(Data: array of Integer): Integer;
350
// calculate a better estimation of the last value in the given data, depending
351
// on the other values (used to approximate a smoother joystick movement)
353
// based on Gauss' principle of smallest squares in Maximum-Likelihood and
354
// linear normal equations
357
SumX, SumY, SumXX, SumYX: Double;
366
Comps := High(Data) + 1;
367
for I := 0 to High(Data) do
370
SumY := SumY + Data[I];
371
SumXX := SumXX + I * I;
372
SumYX := SumYX + I * Data[I];
374
a0 := (SumY * SumXX - SumX * SumYX) / (Comps * SumXX - SumX * SumX);
375
a1 := (Comps * SumYX - SumY * SumX) / (Comps * SumXX - SumX * SumX);
376
Result := Round(a0 + a1 * High(Data));
381
procedure TGLJoystick.DoXYMove(Buttons: Word; XPos, YPos: Integer);
386
XPos := DoScale(XPos);
387
YPos := DoScale(YPos);
388
if (FLastX = -1) or (FLastY = -1) then begin
391
for I:=0 to 4 do begin
396
Move(FXPosInfo[1], FXPosInfo[0], 16);
397
FXPosInfo[4] := XPos;
398
XPos := Approximation(FXPosInfo);
399
Move(FYPosInfo[1], FYPosInfo[0], 16);
400
FYPosInfo[4] := YPos;
401
YPos := Approximation(FYPosInfo);
402
MakeJoyButtons(Buttons);
403
dX := Round((XPos-FMinMaxInfo[jaX, jpCenter]) * 100 / FMinMaxInfo[jaX, jpCenter]);
404
dY := Round((YPos-FMinMaxInfo[jaY, jpCenter]) * 100 / FMinMaxInfo[jaY, jpCenter]);
405
if Assigned(FOnJoystickMove) then
406
FOnJoystickMove(Self, FJoystickID, FJoyButtons, dX, dY);
416
procedure TGLJoystick.DoZMove(Buttons: Word; ZPos: Integer);
419
FLastZ := Round(ZPos * 100 / 65536);
420
MakeJoyButtons(Buttons);
425
RegisterClasses([TGLJoystick]);