123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415 |
- //
- // The graphics engine GLXEngine. The unit of GXScene for Delphi
- //
- unit GXS.Joystick;
- (* Component for handling joystick messages *)
- interface
- {$I Stage.Defines.inc}
- uses
- Winapi.Windows,
- Winapi.Messages,
- Winapi.MMSystem,
- System.Classes,
- System.SysUtils,
- FMX.Forms,
- FMX.Controls,
- Stage.Strings;
- type
- TJoystickButton = (jbButton1, jbButton2, jbButton3, jbButton4);
- TJoystickButtons = set of TJoystickButton;
- TJoystickID = (jidNoJoystick, jidJoystick1, jidJoystick2);
- TJoystickDesignMode = (jdmInactive, jdmActive);
- TJoyPos = (jpMin, jpCenter, jpMax);
- TJoyAxis = (jaX, jaY, jaZ, jaR, jaU, jaV);
- TJoystickEvent = procedure(Sender: TObject; JoyID: TJoystickID;
- Buttons: TJoystickButtons; XDeflection, YDeflection: Integer) of Object;
- // A component interfacing the Joystick via the (regular) windows API.
- TgxJoystick = class(TComponent)
- private
- FWindowHandle: HWND;
- FNumButtons, FLastX, FLastY, FLastZ: Integer;
- FThreshold, FInterval: Cardinal;
- FCapture, FNoCaptureErrors: Boolean;
- FJoystickID: TJoystickID;
- FMinMaxInfo: array [TJoyAxis, TJoyPos] of Integer;
- FXPosInfo, FYPosInfo: array [0 .. 4] of Integer;
- FOnJoystickButtonChange, FOnJoystickMove: TJoystickEvent;
- FXPosition, FYPosition: Integer;
- FJoyButtons: TJoystickButtons;
- procedure SetCapture(AValue: Boolean);
- procedure SetInterval(AValue: Cardinal);
- procedure SetJoystickID(AValue: TJoystickID);
- procedure SetThreshold(AValue: Cardinal);
- protected
- function MakeJoyButtons(Param: UINT): TJoystickButtons;
- procedure DoJoystickCapture(AHandle: HWND; AJoystick: TJoystickID);
- procedure DoJoystickRelease(AJoystick: TJoystickID);
- procedure DoXYMove(Buttons: Word; XPos, YPos: Integer);
- procedure DoZMove(Buttons: Word; ZPos: Integer);
- procedure ReapplyCapture(AJoystick: TJoystickID);
- procedure WndProc(var Msg: TMessage);
- procedure Loaded; override;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure Assign(Source: TPersistent); override;
- property JoyButtons: TJoystickButtons read FJoyButtons;
- property XPosition: Integer read FXPosition;
- property YPosition: Integer read FYPosition;
- published
- { When set to True, the component attempts to capture the joystick.
- If capture is successfull, retrieving joystick status is possible,
- if not, an error message is triggered. }
- property Capture: Boolean read FCapture write SetCapture default False;
- // If true joystick capture errors do not result in exceptions.
- property NoCaptureErrors: Boolean read FNoCaptureErrors
- write FNoCaptureErrors default True;
- // Polling frequency (milliseconds)
- property Interval: Cardinal read FInterval write SetInterval default 100;
- property JoystickID: TJoystickID read FJoystickID write SetJoystickID
- default jidNoJoystick;
- property Threshold: Cardinal read FThreshold write SetThreshold
- default 1000;
- property OnJoystickButtonChange: TJoystickEvent read FOnJoystickButtonChange
- write FOnJoystickButtonChange;
- property OnJoystickMove: TJoystickEvent read FOnJoystickMove
- write FOnJoystickMove;
- end;
- // ---------------------------------------------------------------------
- implementation
- // ---------------------------------------------------------------------
- const
- cJoystickIDToNative: array [jidNoJoystick .. jidJoystick2] of Byte = (9,
- JOYSTICKID1, JOYSTICKID2);
- // ------------------
- // ------------------ TJoystick ------------------
- // ------------------
- constructor TgxJoystick.Create(AOwner: TComponent);
- begin
- inherited;
- FWindowHandle := AllocateHWnd(WndProc);
- FInterval := 100;
- FThreshold := 1000;
- FJoystickID := jidNoJoystick;
- FLastX := -1;
- FLastY := -1;
- FLastZ := -1;
- FNoCaptureErrors := True;
- end;
- destructor TgxJoystick.Destroy;
- begin
- DeallocateHWnd(FWindowHandle);
- inherited;
- end;
- procedure TgxJoystick.WndProc(var Msg: TMessage);
- begin
- with Msg do
- begin
- case FJoystickID of
- jidJoystick1: // check only 1st stick
- case Msg of
- MM_JOY1MOVE:
- DoXYMove(wParam, lParamLo, lParamHi);
- MM_JOY1ZMOVE:
- DoZMove(wParam, lParamLo);
- MM_JOY1BUTTONDOWN:
- if Assigned(FOnJoystickButtonChange) then
- FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
- FLastX, FLastY);
- MM_JOY1BUTTONUP:
- if Assigned(FOnJoystickButtonChange) then
- FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
- FLastX, FLastY);
- end;
- jidJoystick2: // check only 2nd stick
- case Msg of
- MM_JOY2MOVE:
- DoXYMove(wParam, lParamLo, lParamHi);
- MM_JOY2ZMOVE:
- DoZMove(wParam, lParamLo);
- MM_JOY2BUTTONDOWN:
- if Assigned(FOnJoystickButtonChange) then
- FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
- FLastX, FLastY);
- MM_JOY2BUTTONUP:
- if Assigned(FOnJoystickButtonChange) then
- FOnJoystickButtonChange(Self, FJoystickID, MakeJoyButtons(wParam),
- FLastX, FLastY);
- end;
- jidNoJoystick:
- ; // ignore
- else
- Assert(False);
- end;
- Result := 0;
- end;
- end;
- procedure TgxJoystick.Loaded;
- begin
- inherited;
- ReapplyCapture(FJoystickID);
- end;
- procedure TgxJoystick.Assign(Source: TPersistent);
- begin
- if Source is TgxJoystick then
- begin
- FInterval := TgxJoystick(Source).FInterval;
- FThreshold := TgxJoystick(Source).FThreshold;
- FCapture := TgxJoystick(Source).FCapture;
- FJoystickID := TgxJoystick(Source).FJoystickID;
- try
- ReapplyCapture(FJoystickID);
- except
- FJoystickID := jidNoJoystick;
- FCapture := False;
- raise;
- end;
- end
- else
- inherited Assign(Source);
- end;
- function TgxJoystick.MakeJoyButtons(Param: UINT): TJoystickButtons;
- begin
- Result := [];
- if (Param and JOY_BUTTON1) > 0 then
- Include(Result, jbButton1);
- if (Param and JOY_BUTTON2) > 0 then
- Include(Result, jbButton2);
- if (Param and JOY_BUTTON3) > 0 then
- Include(Result, jbButton3);
- if (Param and JOY_BUTTON4) > 0 then
- Include(Result, jbButton4);
- FJoyButtons := Result;
- end;
- function DoScale(AValue: Integer): Integer;
- begin
- Result := Round(AValue / 1);
- end;
- procedure TgxJoystick.ReapplyCapture(AJoystick: TJoystickID);
- var
- jc: TJoyCaps;
- begin
- DoJoystickRelease(AJoystick);
- if FCapture and (not(csDesigning in ComponentState)) then
- with jc do
- begin
- joyGetDevCaps(cJoystickIDToNative[FJoystickID], @jc, SizeOf(jc));
- FNumButtons := wNumButtons;
- FMinMaxInfo[jaX, jpMin] := DoScale(wXMin);
- FMinMaxInfo[jaX, jpCenter] := DoScale((wXMin + wXMax) div 2);
- FMinMaxInfo[jaX, jpMax] := DoScale(wXMax);
- FMinMaxInfo[jaY, jpMin] := DoScale(wYMin);
- FMinMaxInfo[jaY, jpCenter] := DoScale((wYMin + wYMax) div 2);
- FMinMaxInfo[jaY, jpMax] := DoScale(wYMax);
- FMinMaxInfo[jaZ, jpMin] := DoScale(wZMin);
- FMinMaxInfo[jaZ, jpCenter] := DoScale((wZMin + wZMax) div 2);
- FMinMaxInfo[jaZ, jpMax] := DoScale(wZMax);
- FMinMaxInfo[jaR, jpMin] := DoScale(wRMin);
- FMinMaxInfo[jaR, jpCenter] := DoScale((wRMin + wRMax) div 2);
- FMinMaxInfo[jaR, jpMax] := DoScale(wRMax);
- FMinMaxInfo[jaU, jpMin] := DoScale(wUMin);
- FMinMaxInfo[jaU, jpCenter] := DoScale((wUMin + wUMax) div 2);
- FMinMaxInfo[jaU, jpMax] := DoScale(wUMax);
- FMinMaxInfo[jaV, jpMin] := DoScale(wVMin);
- FMinMaxInfo[jaV, jpCenter] := DoScale((wVMin + wVMax) div 2);
- FMinMaxInfo[jaV, jpMax] := DoScale(wVMax);
- DoJoystickCapture(FWindowHandle, AJoystick)
- end;
- end;
- procedure TgxJoystick.DoJoystickCapture(AHandle: HWND; AJoystick: TJoystickID);
- var
- res: Cardinal;
- begin
- res := joySetCapture(AHandle, cJoystickIDToNative[AJoystick],
- FInterval, True);
- if res <> JOYERR_NOERROR then
- begin
- FCapture := False;
- if not NoCaptureErrors then
- begin
- case res of
- MMSYSERR_NODRIVER:
- raise Exception.Create(strNoJoystickDriver);
- JOYERR_UNPLUGGED:
- raise Exception.Create(strConnectJoystick);
- JOYERR_NOCANDO:
- raise Exception.Create(strJoystickError);
- else
- raise Exception.Create(strJoystickError);
- end;
- end;
- end
- else
- joySetThreshold(cJoystickIDToNative[AJoystick], FThreshold);
- end;
- procedure TgxJoystick.DoJoystickRelease(AJoystick: TJoystickID);
- begin
- if AJoystick <> jidNoJoystick then
- joyReleaseCapture(cJoystickIDToNative[AJoystick]);
- end;
- procedure TgxJoystick.SetCapture(AValue: Boolean);
- begin
- if FCapture <> AValue then
- begin
- FCapture := AValue;
- if not(csReading in ComponentState) then
- begin
- try
- ReapplyCapture(FJoystickID);
- except
- FCapture := False;
- raise;
- end;
- end;
- end;
- end;
- procedure TgxJoystick.SetInterval(AValue: Cardinal);
- begin
- if FInterval <> AValue then
- begin
- FInterval := AValue;
- if not(csReading in ComponentState) then
- ReapplyCapture(FJoystickID);
- end;
- end;
- procedure TgxJoystick.SetJoystickID(AValue: TJoystickID);
- begin
- if FJoystickID <> AValue then
- begin
- try
- if not(csReading in ComponentState) then
- ReapplyCapture(AValue);
- FJoystickID := AValue;
- except
- on E: Exception do
- begin
- ReapplyCapture(FJoystickID);
- Application.ShowException(E);
- end;
- end;
- end;
- end;
- // ------------------------------------------------------------------------------
- procedure TgxJoystick.SetThreshold(AValue: Cardinal);
- begin
- if FThreshold <> AValue then
- begin
- FThreshold := AValue;
- if not(csReading in ComponentState) then
- ReapplyCapture(FJoystickID);
- end;
- end;
- // ------------------------------------------------------------------------------
- function Approximation(Data: array of Integer): Integer;
- // calculate a better estimation of the last value in the given data, depending
- // on the other values (used to approximate a smoother joystick movement)
- //
- // based on Gauss' principle of smallest squares in Maximum-Likelihood and
- // linear normal equations
- var
- SumX, SumY, SumXX, SumYX: Double;
- I, Comps: Integer;
- a0, a1: Double;
- begin
- SumX := 0;
- SumY := 0;
- SumXX := 0;
- SumYX := 0;
- Comps := High(Data) + 1;
- for I := 0 to High(Data) do
- begin
- SumX := SumX + I;
- SumY := SumY + Data[I];
- SumXX := SumXX + I * I;
- SumYX := SumYX + I * Data[I];
- end;
- a0 := (SumY * SumXX - SumX * SumYX) / (Comps * SumXX - SumX * SumX);
- a1 := (Comps * SumYX - SumY * SumX) / (Comps * SumXX - SumX * SumX);
- Result := Round(a0 + a1 * High(Data));
- end;
- procedure TgxJoystick.DoXYMove(Buttons: Word; XPos, YPos: Integer);
- var
- I: Integer;
- dX, dY: Integer;
- begin
- XPos := DoScale(XPos);
- YPos := DoScale(YPos);
- if (FLastX = -1) or (FLastY = -1) then
- begin
- FLastX := XPos;
- FLastY := YPos;
- for I := 0 to 4 do
- begin
- FXPosInfo[I] := XPos;
- FYPosInfo[I] := YPos;
- end;
- end
- else
- begin
- Move(FXPosInfo[1], FXPosInfo[0], 16);
- FXPosInfo[4] := XPos;
- XPos := Approximation(FXPosInfo);
- Move(FYPosInfo[1], FYPosInfo[0], 16);
- FYPosInfo[4] := YPos;
- YPos := Approximation(FYPosInfo);
- MakeJoyButtons(Buttons);
- dX := Round((XPos - FMinMaxInfo[jaX, jpCenter]) * 100 / FMinMaxInfo[jaX,
- jpCenter]);
- dY := Round((YPos - FMinMaxInfo[jaY, jpCenter]) * 100 / FMinMaxInfo[jaY,
- jpCenter]);
- if Assigned(FOnJoystickMove) then
- FOnJoystickMove(Self, FJoystickID, FJoyButtons, dX, dY);
- FXPosition := dX;
- FYPosition := dY;
- FLastX := XPos;
- FLastY := YPos;
- end;
- end;
- procedure TgxJoystick.DoZMove(Buttons: Word; ZPos: Integer);
- begin
- if FLastZ = -1 then
- FLastZ := Round(ZPos * 100 / 65536);
- MakeJoyButtons(Buttons);
- end;
- //------------------------------------------------------------------
- initialization
- //------------------------------------------------------------------
- RegisterClasses([TgxJoystick]);
- end.
|