| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- //
- // This unit is part of the GLScene Engine, http://glscene.org
- //
- unit GLJoystick;
- (* Component for handling joystick messages *)
- interface
- {$I GLScene.inc}
- uses
- Winapi.Windows,
- Winapi.Messages,
- Winapi.MMSystem,
- System.SysUtils,
- System.Classes,
- VCL.Forms,
- VCL.Controls,
- GLS.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. }
- TGLJoystick = 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 TGLJoystick.Create(AOwner: TComponent);
- begin
- inherited;
- FWindowHandle := AllocateHWnd(WndProc);
- FInterval := 100;
- FThreshold := 1000;
- FJoystickID := jidNoJoystick;
- FLastX := -1;
- FLastY := -1;
- FLastZ := -1;
- FNoCaptureErrors := True;
- end;
- destructor TGLJoystick.Destroy;
- begin
- DeallocateHWnd(FWindowHandle);
- inherited;
- end;
- procedure TGLJoystick.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 TGLJoystick.Loaded;
- begin
- inherited;
- ReapplyCapture(FJoystickID);
- end;
- procedure TGLJoystick.Assign(Source: TPersistent);
- begin
- if Source is TGLJoystick then begin
- FInterval := TGLJoystick(Source).FInterval;
- FThreshold := TGLJoystick(Source).FThreshold;
- FCapture := TGLJoystick(Source).FCapture;
- FJoystickID := TGLJoystick(Source).FJoystickID;
- try
- ReapplyCapture(FJoystickID);
- except
- FJoystickID := jidNoJoystick;
- FCapture := False;
- raise;
- end;
- end else inherited Assign(Source);
- end;
- function TGLJoystick.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 TGLJoystick.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 TGLJoystick.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 TGLJoystick.DoJoystickRelease(AJoystick: TJoystickID);
- begin
- if AJoystick <> jidNoJoystick then
- joyReleaseCapture(cJoystickIDToNative[AJoystick]);
- end;
- procedure TGLJoystick.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 TGLJoystick.SetInterval(AValue: Cardinal);
- begin
- if FInterval <> AValue then begin
- FInterval := AValue;
- if not (csReading in ComponentState) then
- ReapplyCapture(FJoystickID);
- end;
- end;
- procedure TGLJoystick.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 TGLJoystick.SetThreshold(AValue: Cardinal);
- begin
- if FThreshold <> AValue then
- begin
- FThreshold := AValue;
- if not (csReading in ComponentState) then ReapplyCapture(FJoystickID);
- end;
- end;
- //------------------------------------------------------------------------------
- function Approximation(const 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 TGLJoystick.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 TGLJoystick.DoZMove(Buttons: Word; ZPos: Integer);
- begin
- if FLastZ = -1 then
- FLastZ := Round(ZPos * 100 / 65536);
- MakeJoyButtons(Buttons);
- end;
- initialization
- RegisterClasses([TGLJoystick]);
- end.
|