//----------------------------------------------------------------------------- // File: FFConst.cpp // // Desc: Demonstrates an application which sets a force feedback constant force // determined by the user. // // Copyright (c) 1998-2001 Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #define STRICT #include #include #include #include #include #include #include #include "resource.h" //----------------------------------------------------------------------------- // Function prototypes //----------------------------------------------------------------------------- INT_PTR CALLBACK MainDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam ); BOOL CALLBACK EnumFFDevicesCallback( const DIDEVICEINSTANCE* pInst, VOID* pContext ); BOOL CALLBACK EnumAxesCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext ); HRESULT InitDirectInput( HWND hDlg ); VOID FreeDirectInput(); VOID OnPaint( HWND hDlg ); HRESULT OnMouseMove( HWND hDlg, INT x, INT y, UINT keyFlags ); VOID OnLeftButtonDown( HWND hDlg, INT x, INT y, UINT keyFlags ); VOID OnLeftButtonUp( HWND hDlg, INT x, INT y, UINT keyFlags ); INT CoordToForce( INT x ); HRESULT SetDeviceForcesXY(); //----------------------------------------------------------------------------- // Defines, constants, and global variables //----------------------------------------------------------------------------- #define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } #define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } #define FEEDBACK_WINDOW_X 20 #define FEEDBACK_WINDOW_Y 60 #define FEEDBACK_WINDOW_WIDTH 200 LPDIRECTINPUT8 g_pDI = NULL; LPDIRECTINPUTDEVICE8 g_pDevice = NULL; LPDIRECTINPUTEFFECT g_pEffect = NULL; BOOL g_bActive = TRUE; DWORD g_dwNumForceFeedbackAxis = 0; INT g_nXForce; INT g_nYForce; DWORD g_dwLastEffectSet; // Time of the previous force feedback effect set //----------------------------------------------------------------------------- // Name: WinMain() // Desc: Entry point for the application. Since we use a simple dialog for // user interaction we don't need to pump messages. //----------------------------------------------------------------------------- INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT ) { // Display the main dialog box. DialogBox( hInst, MAKEINTRESOURCE(IDD_FORCE_FEEDBACK), NULL, MainDlgProc ); return TRUE; } //----------------------------------------------------------------------------- // Name: MainDlgProc // Desc: Handles dialog messages //----------------------------------------------------------------------------- INT_PTR CALLBACK MainDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam ) { switch( msg ) { case WM_INITDIALOG: if( FAILED( InitDirectInput( hDlg ) ) ) { MessageBox( NULL, _T("Error Initializing DirectInput ") _T("The sample will now exit."), _T("FFConst"), MB_ICONERROR | MB_OK ); EndDialog( hDlg, 0 ); } // Init the time of the last force feedback effect g_dwLastEffectSet = timeGetTime(); break; case WM_MOUSEMOVE: if( FAILED( OnMouseMove( hDlg, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (UINT)wParam ) ) ) { MessageBox( NULL, _T("Error setting effect parameters. ") _T("The sample will now exit."), _T("FFConst"), MB_ICONERROR | MB_OK ); EndDialog( hDlg, 0 ); } break; case WM_LBUTTONDOWN: OnLeftButtonDown( hDlg, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (UINT)wParam ); break; case WM_LBUTTONUP: OnLeftButtonUp( hDlg, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (UINT)wParam ); break; case WM_PAINT: OnPaint( hDlg ); break; case WM_ACTIVATE: if( WA_INACTIVE != wParam && g_pDevice ) { // Make sure the device is acquired, if we are gaining focus. g_pDevice->Acquire(); if( g_pEffect ) g_pEffect->Start( 1, 0 ); // Start the effect } break; case WM_COMMAND: switch( LOWORD(wParam) ) { case IDCANCEL: EndDialog( hDlg, 0 ); break; default: return FALSE; // Message not handled } break; case WM_DESTROY: // Cleanup everything KillTimer( hDlg, 0 ); FreeDirectInput(); break; default: return FALSE; // Message not handled } return TRUE; // Message handled } //----------------------------------------------------------------------------- // Name: InitDirectInput() // Desc: Initialize the DirectInput variables. //----------------------------------------------------------------------------- HRESULT InitDirectInput( HWND hDlg ) { DIPROPDWORD dipdw; HRESULT hr; // Register with the DirectInput subsystem and get a pointer // to a IDirectInput interface we can use. if( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&g_pDI, NULL ) ) ) { return hr; } // Look for a force feedback device we can use if( FAILED( hr = g_pDI->EnumDevices( DI8DEVCLASS_GAMECTRL, EnumFFDevicesCallback, NULL, DIEDFL_ATTACHEDONLY | DIEDFL_FORCEFEEDBACK ) ) ) { return hr; } if( NULL == g_pDevice ) { MessageBox( NULL, _T("Force feedback device not found. ") _T("The sample will now exit."), _T("FFConst"), MB_ICONERROR | MB_OK ); EndDialog( hDlg, 0 ); return S_OK; } // Set the data format to "simple joystick" - a predefined data format. A // data format specifies which controls on a device we are interested in, // and how they should be reported. // // This tells DirectInput that we will be passing a DIJOYSTATE structure to // IDirectInputDevice8::GetDeviceState(). Even though we won't actually do // it in this sample. But setting the data format is important so that the // DIJOFS_* values work properly. if( FAILED( hr = g_pDevice->SetDataFormat( &c_dfDIJoystick ) ) ) return hr; // Set the cooperative level to let DInput know how this device should // interact with the system and with other DInput applications. // Exclusive access is required in order to perform force feedback. if( FAILED( hr = g_pDevice->SetCooperativeLevel( hDlg, DISCL_EXCLUSIVE | DISCL_FOREGROUND ) ) ) { return hr; } // Since we will be playing force feedback effects, we should disable the // auto-centering spring. dipdw.diph.dwSize = sizeof(DIPROPDWORD); dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); dipdw.diph.dwObj = 0; dipdw.diph.dwHow = DIPH_DEVICE; dipdw.dwData = FALSE; if( FAILED( hr = g_pDevice->SetProperty( DIPROP_AUTOCENTER, &dipdw.diph ) ) ) return hr; // Enumerate and count the axes of the joystick if ( FAILED( hr = g_pDevice->EnumObjects( EnumAxesCallback, (VOID*)&g_dwNumForceFeedbackAxis, DIDFT_AXIS ) ) ) return hr; // This simple sample only supports one or two axis joysticks if( g_dwNumForceFeedbackAxis > 2 ) g_dwNumForceFeedbackAxis = 2; // This application needs only one effect: Applying raw forces. DWORD rgdwAxes[2] = { DIJOFS_X, DIJOFS_Y }; LONG rglDirection[2] = { 0, 0 }; DICONSTANTFORCE cf = { 0 }; DIEFFECT eff; ZeroMemory( &eff, sizeof(eff) ); eff.dwSize = sizeof(DIEFFECT); eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; eff.dwDuration = INFINITE; eff.dwSamplePeriod = 0; eff.dwGain = DI_FFNOMINALMAX; eff.dwTriggerButton = DIEB_NOTRIGGER; eff.dwTriggerRepeatInterval = 0; eff.cAxes = g_dwNumForceFeedbackAxis; eff.rgdwAxes = rgdwAxes; eff.rglDirection = rglDirection; eff.lpEnvelope = 0; eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); eff.lpvTypeSpecificParams = &cf; eff.dwStartDelay = 0; // Create the prepared effect if( FAILED( hr = g_pDevice->CreateEffect( GUID_ConstantForce, &eff, &g_pEffect, NULL ) ) ) { return hr; } if( NULL == g_pEffect ) return E_FAIL; return S_OK; } //----------------------------------------------------------------------------- // Name: EnumAxesCallback() // Desc: Callback function for enumerating the axes on a joystick and counting // each force feedback enabled axis //----------------------------------------------------------------------------- BOOL CALLBACK EnumAxesCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext ) { DWORD* pdwNumForceFeedbackAxis = (DWORD*) pContext; if( (pdidoi->dwFlags & DIDOI_FFACTUATOR) != 0 ) (*pdwNumForceFeedbackAxis)++; return DIENUM_CONTINUE; } //----------------------------------------------------------------------------- // Name: EnumFFDevicesCallback() // Desc: Called once for each enumerated force feedback device. If we find // one, create a device interface on it so we can play with it. //----------------------------------------------------------------------------- BOOL CALLBACK EnumFFDevicesCallback( const DIDEVICEINSTANCE* pInst, VOID* pContext ) { LPDIRECTINPUTDEVICE8 pDevice; HRESULT hr; // Obtain an interface to the enumerated force feedback device. hr = g_pDI->CreateDevice( pInst->guidInstance, &pDevice, NULL ); // If it failed, then we can't use this device for some // bizarre reason. (Maybe the user unplugged it while we // were in the middle of enumerating it.) So continue enumerating if( FAILED(hr) ) return DIENUM_CONTINUE; // We successfully created an IDirectInputDevice8. So stop looking // for another one. g_pDevice = pDevice; return DIENUM_STOP; } //----------------------------------------------------------------------------- // Name: FreeDirectInput() // Desc: Initialize the DirectInput variables. //----------------------------------------------------------------------------- VOID FreeDirectInput() { // Unacquire the device one last time just in case // the app tried to exit while the device is still acquired. if( g_pDevice ) g_pDevice->Unacquire(); // Release any DirectInput objects. SAFE_RELEASE( g_pEffect ); SAFE_RELEASE( g_pDevice ); SAFE_RELEASE( g_pDI ); } //----------------------------------------------------------------------------- // Name: OnPaint() // Desc: Handles the WM_PAINT window message //----------------------------------------------------------------------------- VOID OnPaint( HWND hDlg ) { PAINTSTRUCT ps; HDC hDC; HPEN hpenOld; HPEN hpenBlack; HBRUSH hbrOld; HBRUSH hbrBlack; INT x; INT y; hDC = BeginPaint( hDlg, &ps ); if( NULL == hDC ) return; // Everything is scaled to the size of the window. hpenBlack = GetStockPen( BLACK_PEN ); hpenOld = SelectPen( hDC, hpenBlack ); // Draw force feedback bounding rect MoveToEx( hDC, FEEDBACK_WINDOW_X, FEEDBACK_WINDOW_Y, NULL ); LineTo( hDC, FEEDBACK_WINDOW_X, FEEDBACK_WINDOW_Y + FEEDBACK_WINDOW_WIDTH ); LineTo( hDC, FEEDBACK_WINDOW_X + FEEDBACK_WINDOW_WIDTH, FEEDBACK_WINDOW_Y + FEEDBACK_WINDOW_WIDTH ); LineTo( hDC, FEEDBACK_WINDOW_X + FEEDBACK_WINDOW_WIDTH, FEEDBACK_WINDOW_Y ); LineTo( hDC, FEEDBACK_WINDOW_X, FEEDBACK_WINDOW_Y ); // Calculate center of feedback window for center marker x = FEEDBACK_WINDOW_X + FEEDBACK_WINDOW_WIDTH / 2; y = FEEDBACK_WINDOW_Y + FEEDBACK_WINDOW_WIDTH / 2; // Draw center marker MoveToEx( hDC, x, y - 10, NULL ); LineTo( hDC, x, y + 10 + 1 ); MoveToEx( hDC, x - 10, y, NULL ); LineTo( hDC, x + 10 + 1, y ); hbrBlack = GetStockBrush( BLACK_BRUSH ); hbrOld = SelectBrush( hDC, hbrBlack ); x = MulDiv( FEEDBACK_WINDOW_WIDTH, g_nXForce + DI_FFNOMINALMAX, 2 * DI_FFNOMINALMAX ); y = MulDiv( FEEDBACK_WINDOW_WIDTH, g_nYForce + DI_FFNOMINALMAX, 2 * DI_FFNOMINALMAX ); x += FEEDBACK_WINDOW_X; y += FEEDBACK_WINDOW_Y; Ellipse( hDC, x-5, y-5, x+6, y+6 ); SelectBrush( hDC, hbrOld ); SelectPen( hDC, hpenOld ); EndPaint( hDlg, &ps ); } //----------------------------------------------------------------------------- // Name: OnMouseMove() // Desc: If the mouse button is down, then change the direction of // the force to match the new location. //----------------------------------------------------------------------------- HRESULT OnMouseMove( HWND hDlg, INT x, INT y, UINT keyFlags ) { HRESULT hr; DWORD dwCurrentTime; if( NULL == g_pEffect ) return S_OK; if( keyFlags & MK_LBUTTON ) { dwCurrentTime = timeGetTime(); if( dwCurrentTime - g_dwLastEffectSet < 100 ) { // Don't allow setting effect more often than // 100ms since every time an effect is set, the // device will jerk. // // Note: This is not neccessary, and is specific to this sample return S_OK; } g_dwLastEffectSet = dwCurrentTime; x -= FEEDBACK_WINDOW_X; y -= FEEDBACK_WINDOW_Y; g_nXForce = CoordToForce( x ); g_nYForce = CoordToForce( y ); InvalidateRect( hDlg, 0, TRUE ); UpdateWindow( hDlg ); if( FAILED( hr = SetDeviceForcesXY() ) ) return hr; } return S_OK; } //----------------------------------------------------------------------------- // Name: OnLeftButtonDown() // Desc: Capture the mouse so we can follow it, and start updating the // force information. //----------------------------------------------------------------------------- VOID OnLeftButtonDown( HWND hDlg, INT x, INT y, UINT keyFlags ) { SetCapture( hDlg ); OnMouseMove( hDlg, x, y, MK_LBUTTON ); } //----------------------------------------------------------------------------- // Name: OnLeftButtonUp() // Desc: Stop capturing the mouse when the button goes up. //----------------------------------------------------------------------------- VOID OnLeftButtonUp( HWND hDlg, INT x, INT y, UINT keyFlags ) { ReleaseCapture(); } //----------------------------------------------------------------------------- // Name: CoordToForce() // Desc: Convert a coordinate 0 <= nCoord <= FEEDBACK_WINDOW_WIDTH // to a force value in the range -DI_FFNOMINALMAX to +DI_FFNOMINALMAX. //----------------------------------------------------------------------------- INT CoordToForce( INT nCoord ) { INT nForce = MulDiv( nCoord, 2 * DI_FFNOMINALMAX, FEEDBACK_WINDOW_WIDTH ) - DI_FFNOMINALMAX; // Keep force within bounds if( nForce < -DI_FFNOMINALMAX ) nForce = -DI_FFNOMINALMAX; if( nForce > +DI_FFNOMINALMAX ) nForce = +DI_FFNOMINALMAX; return nForce; } //----------------------------------------------------------------------------- // Name: SetDeviceForcesXY() // Desc: Apply the X and Y forces to the effect we prepared. //----------------------------------------------------------------------------- HRESULT SetDeviceForcesXY() { // Modifying an effect is basically the same as creating a new one, except // you need only specify the parameters you are modifying LONG rglDirection[2] = { 0, 0 }; DICONSTANTFORCE cf; if( g_dwNumForceFeedbackAxis == 1 ) { // If only one force feedback axis, then apply only one direction and // keep the direction at zero cf.lMagnitude = g_nXForce; rglDirection[0] = 0; } else { // If two force feedback axis, then apply magnitude from both directions rglDirection[0] = g_nXForce; rglDirection[1] = g_nYForce; cf.lMagnitude = (DWORD)sqrt( (double)g_nXForce * (double)g_nXForce + (double)g_nYForce * (double)g_nYForce ); } DIEFFECT eff; ZeroMemory( &eff, sizeof(eff) ); eff.dwSize = sizeof(DIEFFECT); eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS; eff.cAxes = g_dwNumForceFeedbackAxis; eff.rglDirection = rglDirection; eff.lpEnvelope = 0; eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE); eff.lpvTypeSpecificParams = &cf; eff.dwStartDelay = 0; // Now set the new parameters and start the effect immediately. return g_pEffect->SetParameters( &eff, DIEP_DIRECTION | DIEP_TYPESPECIFICPARAMS | DIEP_START ); }