/**************************************************************************
DIFF1.CPP - DirectInput simple force feedback sample 1
Demonstrates an application which plays a force on the joystick
in response to mouse clicks.
**************************************************************************/
/**************************************************************************
(C) Copyright 1997 Microsoft Corp. All rights reserved.
You have a royalty-free right to use, modify, reproduce and
distribute the Sample Files (and/or any modified version) in
any way you find useful, provided that you agree that
Microsoft has no warranty obligations or liability for any
Sample Application Files which are modified.
**************************************************************************/
#include "diff1.h"
#include <windowsx.h>
#include <dinput.h>
#include <math.h>
/****************************************************************************
*
* Global variables
*
****************************************************************************/
char c_szClassName[] = "DIFF1";
HINSTANCE g_hinst; /* My instance handle */
BOOL g_fPaused = TRUE; /* Should I be paused? */
int g_xForce; /* Coordinates of the active force */
int g_yForce;
int g_cxClient; /* Client window size */
int g_cyClient;
/****************************************************************************
*
* DirectInput globals
*
****************************************************************************/
LPDIRECTINPUT g_pdi;
LPDIRECTINPUTDEVICE2 g_pJoystick;
LPDIRECTINPUTEFFECT g_pEffect;
/****************************************************************************
*
* Complain
*
* Whine and moan.
*
****************************************************************************/
void
Complain(
HWND hwndOwner,
HRESULT hr,
LPCSTR pszMessage
)
{
MessageBox(hwndOwner, pszMessage, "DirectInput Sample", MB_OK);
}
/****************************************************************************
*
* EnumFFJoysticksCallback
*
* Called once for each enumerated force feedback joystick.
*
* If we find one, create a device interface on it so we can
* play with it.
*
* Parameters:
*
* pinst
*
* Pointer to DIDEVICEINSTANCE structure which describes
* the device.
*
* lpvContext
*
* The pointer we passed to EnumDevices which we don't
* use for anything.
*
****************************************************************************/
BOOL CALLBACK
EnumFFJoysticksCallback(LPCDIDEVICEINSTANCE pinst, LPVOID lpvContext)
{
HRESULT hr;
LPDIRECTINPUTDEVICE pdev;
LPDIRECTINPUTDEVICE2 pdev2;
/*
* Obtain an interface to the enumerated force feedback joystick.
*
* Parameters:
*
* pinst->guidInstance
*
* The instance GUID for the device we wish to access.
*
* &pdev
*
* Receives pointer to the IDirectInputDevice interface
* that was created.
*
* NULL
*
* We do not use OLE aggregation, so this parameter
* must be NULL.
*
*/
hr = g_pdi->CreateDevice(pinst->guidInstance, &pdev, NULL);
/*
* If it failed, then we can't use this joystick for some
* bizarre reason. (Maybe the user unplugged it while we
* were in the middle of enumerating it.)
*
* Continue enumerating; maybe we'll have better luck with the
* next one.
*/
if (FAILED(hr)) {
return DIENUM_CONTINUE;
}
/*
* We really want to use IDirectInputDevice2, so move there
* once and for all.
*
* Parameters:
*
* IID_IDirectInputDevice2
*
* The interface we are requesting.
*
* &pdev2
*
* Receives a pinter to the new interface.
*/
hr = pdev->QueryInterface(IID_IDirectInputDevice2,
(LPVOID *)&pdev2);
/*
* Whether or not the QueryInterface worked, we are finished
* with the old interface.
*/
pdev->Release();
/*
* If the QueryInterface failed, then something weird happened.
* Maybe the currently-installed version of DirectInput doesn't
* support force feedback.
*
* Continue enumerating; maybe we'll have better luck with the
* next one.
*/
if (FAILED(hr)) {
return DIENUM_CONTINUE;
}
/*
* We successfully created an IDirectInputDevice2.
*
* No point in looking for another one.
*/
g_pJoystick = pdev2;
return DIENUM_STOP;
}
/****************************************************************************
*
* DIInit
*
* Initialize the DirectInput variables.
*
* This entails the following four functions:
*
* DirectInputCreate
* IDirectInput::EnumDevices (to find a joystick)
* IDirectInputDevice2::SetDataFormat
* IDirectInputDevice2::SetCooperativeLevel
* IDirectInputDevice2::SetProperty (to disable auto-center)
* IDirectInputDevice2::CreateEffect
*
****************************************************************************/
BOOL
DIInit(HWND hwnd)
{
HRESULT hr;
/*
* Register with the DirectInput subsystem and get a pointer
* to a IDirectInput interface we can use.
*
* Parameters:
*
* g_hinst
*
* Instance handle to our application or DLL.
*
* DIRECTINPUT_VERSION
*
* The version of DirectInput we were designed for.
* We take the value from the <dinput.h> header file.
*
* &g_pdi
*
* Receives pointer to the IDirectInput interface
* that was created.
*
* NULL
*
* We do not use OLE aggregation, so this parameter
* must be NULL.
*
*/
hr = DirectInputCreate(g_hinst, DIRECTINPUT_VERSION, &g_pdi, NULL);
if (FAILED(hr)) {
Complain(hwnd, hr, "DirectInputCreate");
return FALSE;
}
/*
* Look for a force feedback joystick we can use for this
* sample program.
*
* Parameters:
*
* DIDEVTYPE_JOYSTICK
*
* Enumerate only joystick devices.
*
* EnumFFJoysticksCallback
*
* Callback function that is called once for
* each force feedback joystick found.
*
* NULL
*
* Context which is passed to the callback function.
*
* DIEDFL_ATTACHEDONLY | DIEDFL_FORCEFEEDBACK
*
* Flags that further restrict the enumeration.
*
* We are interested only in attached joysticks
* which support force feedback.
*/
hr = g_pdi->EnumDevices(DIDEVTYPE_JOYSTICK,
EnumFFJoysticksCallback,
NULL,
DIEDFL_ATTACHEDONLY | DIEDFL_FORCEFEEDBACK);
if (g_pJoystick == NULL) {
Complain(hwnd, hr, "Couldn't find any force feedback joysticks");
return FALSE;
}
/*
* Set the data format to "simple joystick 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 IDirectInputDevice2::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.
*
* Parameters:
*
* c_dfDIJoystick
*
* Predefined data format which describes
* a DIJOYSTATE structure.
*/
hr = g_pJoystick->SetDataFormat(&c_dfDIJoystick);
if (FAILED(hr)) {
Complain(hwnd, hr, "SetDataFormat");
return FALSE;
}
/*
* Set the cooperativity level to let DirectInput know how
* this device should interact with the system and with other
* DirectInput applications.
*
* Parameters:
*
* DISCL_EXCLUSIVE
*
* Exclusive access is required in order to perform
* force feedback.
*
* DISCL_FOREGROUND
*
* If the user switches away from our application,
* automatically release the joystick back to the system.
*
*/
hr = g_pJoystick->SetCooperativeLevel(hwnd,
DISCL_EXCLUSIVE | DISCL_FOREGROUND);
if (FAILED(hr)) {
Complain(hwnd, hr, "SetCooperativeLevel");
return FALSE;
}
/*
* Since we will be playing force feedback effects,
* we should disable the auto-centering spring.
*
* DIPROPDWORD::diph.dwSize
*
* Must be sizeof(DIPROPDWORD)
*
* DIPROPDWORD::diph.dwHeaderSize
*
* Must be sizeof(DIPROPHEADER)
*
* DIPROPDWORD::diph.dwObj
*
* Must be zero for device properties.
*
* DIPROPDWORD::diph.dwHow
*
* DIPH_DEVICE for device properties.
*
* DIPROPDWORD::dwData
*
* FALSE to disable auto-centering.
*/
DIPROPDWORD dipdw;
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = FALSE;
hr = g_pJoystick->SetProperty(DIPROP_AUTOCENTER, &dipdw.diph);
if (FAILED(hr)) {
Complain(hwnd, hr, "SetProperty(Autocenter)");
return FALSE;
}
/*
* This application needs only one effect: Applying raw forces.
*
* DIEFFECT::dwSize
*
* Must be sizeof(DIEFFECT).
*
* DIEFFECT::dwFlags
*
* DIEFF_CARTESIAN because we will be applying X and Y
* forces, not angles.
*
* DIEFF_OBJECTOFFSETS because we will be using the
* DIJOFS_* macros to specify the axes.
*
* DIEFFECT::dwDuration
*
* INFINITE because we want the force to continue playing
* indefinitely until we explicitly change it.
*
* DIEFFECT::dwSamplePeriod
*
* 0 means "use default". Using a custom sample period
* is a special effect which we don't particularly care
* about.
*
* DIEFFECT::dwGain
*
* DI_FFNOMINALMAX to play all values at exactly the
* strength we specify.
*
* DIEFFECT::dwTriggerButton
*
* DIEB_NOTRIGGER because we don't want this effect
* to be associated with a trigger.
*
* DIEFFECT::dwTriggerRepeatInterval
*
* 0 because there no trigger.
*
* DIEFFECT::cAxes
*
* 2 because we have two axes, X and Y.
*
* DIEFFECT::rgdwAxes
*
* Points to an array which identifies the two axes
* we want to talk to, namely X and Y.
*
* DIEFFECT::rglDirection
*
* Points to an array which gives the direction in
* which the force should be applied.
* Nothing yet.
*
* DIEFFECT::lpEnvelope
*
* NULL because we don't want to apply an envelope
* to the effect.
*
* DIEFFECT::cbTypeSpecificParameters
* DIEFFECT::lpvTypeSpecificParameters
*
* Size of and pointer to type-specific parameters.
* For a constant force effect, we need a
* DICONSTANTFORCE structure.
*/
DIEFFECT eff;
DWORD rgdwAxes[2] = { DIJOFS_X, DIJOFS_Y };
LONG rglDirection[2] = { 0, 0 };
DICONSTANTFORCE cf = { 0 };
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 = 2;
eff.rgdwAxes = rgdwAxes;
eff.rglDirection = rglDirection;
eff.lpEnvelope = 0;
eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
eff.lpvTypeSpecificParams = &cf;
/*
* Now create this effect we prepared.
*
* Parameters:
*
* GUID_ConstantForce
*
* We are playing a raw force, plain and simple.
*
* &eff
*
* The DIEFFECT structure that describes it.
*
* &g_pEffect
*
* Receives pointer to the IDirectInputEffect interface
* that was created.
*
* NULL
*
* We do not use OLE aggregation, so this parameter
* must be NULL.
*
*/
hr = g_pJoystick->CreateEffect(GUID_ConstantForce,
&eff,
&g_pEffect,
NULL);
if (FAILED(hr)) {
Complain(hwnd, hr, "CreateEffect");
return FALSE;
}
return TRUE;
}
/****************************************************************************
*
* DITerm
*
* Terminate our usage of DirectInput.
*
****************************************************************************/
void
DITerm(void)
{
/*
* Destroy any lingering IDirectInputEffect object.
*/
if (g_pEffect) {
g_pEffect->Release();
g_pEffect = NULL;
}
/*
* Destroy any lingering IDirectInputDevice object.
*/
if (g_pJoystick) {
/*
* Cleanliness is next to godliness. Unacquire the device
* one last time just in case we got really confused and tried
* to exit while the device is still acquired.
*/
g_pJoystick->Unacquire();
g_pJoystick->Release();
g_pJoystick = NULL;
}
/*
* Destroy any lingering IDirectInput object.
*/
if (g_pdi) {
g_pdi->Release();
g_pdi = NULL;
}
}
/****************************************************************************
*
* joySetForcesXY
*
* Apply the X and Y forces to the effect we prepared.
*
* Parameters:
*
* peff
*
* Pointer to a constant-force LPDIRECTINPUEFFECT
* which we will push in the appropriate direction
* with the appropriate amount of force.
*
* x, y
*
* X and Y forces to apply, respectively, in the
* range -DI_FFNOMINALMAX to +DI_FFNOMINALMAX.
*
****************************************************************************/
HRESULT
joySetForcesXY(LPDIRECTINPUTEFFECT peff, int x, int y)
{
HRESULT hres;
/*
* Modifying an effect is basically the same as creating
* a new one, except you need only specify the parameters
* you are modifying; the rest are left alone.
*
* DIEFFECT::dwSize
*
* Must be sizeof(DIEFFECT).
*
* DIEFFECT::dwFlags
*
* DIEFF_CARTESIAN because we will be applying X and Y
* forces, not angles.
*
* DIEFF_OBJECTOFFSETS because we used the
* DIJOFS_* macros to specify the axes.
*
* DIEFFECT::cAxes
*
* 2 because we have two axes, X and Y.
*
* DIEFFECT::rglDirection
*
* Points to an array which gives the direction in
* which the force should be applied.
*
* We use the x and y values directly to specify
* the direction. Note that DirectInput currently
* uses these values to specify direction only, not
* magnitude. Magnitude needs to be specified
* elsewhere. (See below.)
*
* DIEFFECT::cbTypeSpecificParameters
* DIEFFECT::lpvTypeSpecificParameters
*
* Size of and pointer to type-specific parameters.
* For a constant force effect, we need a
* DICONSTANTFORCE structure.
*
* DICONSTANTFORCE::lMagnitude
*
* The total magnitude of the force, which is
* the hypotenuse of x and y.
*/
LONG rglDirection[2] = { x, y };
DICONSTANTFORCE cf;
cf.lMagnitude = (DWORD)sqrt((double)x * (double)x +
(double)y * (double)y);
DIEFFECT eff;
eff.dwSize = sizeof(DIEFFECT);
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
eff.cAxes = 2;
eff.rglDirection = rglDirection;
eff.lpEnvelope = 0;
eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
eff.lpvTypeSpecificParams = &cf;
/*
* Now set the new parameters and start the effect immediately.
*
* Parameters:
*
* &eff
*
* The DIEFFECT structure that describes the new parameters.
*
* DIEP_DIRECTION
* DIEP_TYPESPECIFICPARAMS
*
* We want to change the direction and the DICONSTANTFORCE.
*
* DIEP_START
*
* Start the effect as soon as the parameters are downloaded.
*/
hres = peff->SetParameters(&eff, DIEP_DIRECTION |
DIEP_TYPESPECIFICPARAMS |
DIEP_START);
return hres;
}
/****************************************************************************
*
* Ex_SyncAcquire
*
* Acquire or unacquire the joystick, depending on the the g_fPaused
* flag. This synchronizes the device with our internal view of
* the world.
*
****************************************************************************/
void
Ex_SyncAcquire(HWND hwnd)
{
if (g_fPaused) {
if (g_pJoystick) {
g_pJoystick->Unacquire();
}
} else {
if (g_pJoystick) g_pJoystick->Acquire();
if (g_pEffect) {
g_pEffect->Start(1, 0);
}
}
}
/****************************************************************************
*
* Ex_OnSize
*
* The window client size changed. Remember the new size.
*
****************************************************************************/
void
Ex_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
g_cxClient = cx;
g_cyClient = cy;
}
/****************************************************************************
*
* Ex_OnPaint
*
* Paint our little target area thingie.
*
* We draw a small crosshairs at the center (so the user knows
* where the center is)
*
****************************************************************************/
void
Ex_OnPaint(HWND hwnd)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
if (hdc) {
HPEN hpenOld, hpenBlack;
HBRUSH hbrOld, hbrBlack;
int x, y;
/*
* Everything is scaled to the size of the window.
*/
hpenBlack = GetStockPen(BLACK_PEN);
hpenOld = SelectPen(hdc, hpenBlack);
x = g_cxClient / 2;
y = g_cyClient / 2;
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(g_cxClient, g_xForce + DI_FFNOMINALMAX, 2 * DI_FFNOMINALMAX);
y = MulDiv(g_cyClient, g_yForce + DI_FFNOMINALMAX, 2 * DI_FFNOMINALMAX);
Ellipse(hdc, x-5, y-5, x+6, y+6);
SelectBrush(hdc, hbrOld);
SelectPen(hdc, hpenOld);
EndPaint(hwnd, &ps);
}
}
/****************************************************************************
*
* Ex_CoordToForce
*
* Convert a coordinate 0 <= x <= cx to a force value
* in the range -DI_FFNOMINALMAX to +DI_FFNOMINALMAX.
*
****************************************************************************/
int
Ex_CoordToForce(int x, int cx)
{
x = MulDiv(x, 2 * DI_FFNOMINALMAX, cx) - DI_FFNOMINALMAX;
if (x < -DI_FFNOMINALMAX) {
x = -DI_FFNOMINALMAX;
}
if (x > +DI_FFNOMINALMAX) {
x = +DI_FFNOMINALMAX;
}
return x;
}
/****************************************************************************
*
* Ex_OnMouseMove
*
* If the mouse button is down, then change the direction of
* the force to match the new location.
*
****************************************************************************/
void
Ex_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
{
if (keyFlags & MK_LBUTTON) {
g_xForce = Ex_CoordToForce(x, g_cxClient);
g_yForce = Ex_CoordToForce(y, g_cyClient);
InvalidateRect(hwnd, 0, TRUE);
UpdateWindow(hwnd);
joySetForcesXY(g_pEffect, g_xForce, g_yForce);
}
}
/****************************************************************************
*
* Ex_OnLButtonDown
*
* Capture the mouse so we can follow it, and start updating the
* force information.
*
****************************************************************************/
void
Ex_OnLButtonDown(HWND hwnd, BOOL fDblClk, int x, int y, UINT keyFlags)
{
SetCapture(hwnd);
Ex_OnMouseMove(hwnd, x, y, MK_LBUTTON);
}
/****************************************************************************
*
* Ex_OnLButtonUp
*
* Stop capturing the mouse when the button goes up.
*
****************************************************************************/
void
Ex_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags)
{
ReleaseCapture();
}
/****************************************************************************
*
* Ex_WndProc
*
* Window procedure for simple force feedback sample.
*
****************************************************************************/
LRESULT CALLBACK
Ex_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
HANDLE_MSG(hwnd, WM_PAINT, Ex_OnPaint);
HANDLE_MSG(hwnd, WM_MOUSEMOVE, Ex_OnMouseMove);
HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Ex_OnLButtonDown);
HANDLE_MSG(hwnd, WM_LBUTTONUP, Ex_OnLButtonUp);
HANDLE_MSG(hwnd, WM_SIZE, Ex_OnSize);
/*
* WM_ACTIVATE
*
* Windows sends this message when the window becomes
* the active window or stops being the active window.
*
* wParam = WA_INACTIVE if window is no longer active
*
* wParam = WA_ACTIVE or WA_CLICKACTIVE if window is now active
*
* If we are losing activation, then pause.
*
* If we are gaining activation, then unpause.
*
* After deciding whether we are paused or unpaused,
* tell DirectInput that we don't (paused) or do (unpaused)
* want access to the joystick.
*
*/
case WM_ACTIVATE:
g_fPaused = wParam == WA_INACTIVE;
Ex_SyncAcquire(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
/****************************************************************************
*
* AppInit
*
* Set up everything the application needs to get started.
*
****************************************************************************/
HWND
AppInit(
HINSTANCE hinst,
int nCmdShow
)
{
/*
* Save instance handle for future reference.
*/
g_hinst = hinst;
/*
* Set up the window class.
*/
WNDCLASS wc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_APPLICATION));
wc.lpszMenuName = NULL;
wc.lpszClassName = c_szClassName;
wc.hbrBackground = GetStockBrush(WHITE_BRUSH);
wc.hInstance = hinst;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = Ex_WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
if (!RegisterClass(&wc)) {
return NULL;
}
HWND hwnd = CreateWindow(
c_szClassName, // Class name
"DIFF1 - Click in target to change force value", // Caption
WS_OVERLAPPEDWINDOW, // Style
CW_USEDEFAULT, CW_USEDEFAULT, // Position
CW_USEDEFAULT, CW_USEDEFAULT, // Size
NULL, // No parent
NULL, // No menu
g_hinst, // inst handle
0 // no params
);
if (!DIInit(hwnd)) {
DestroyWindow(hwnd);
return NULL;
}
ShowWindow(hwnd, nCmdShow);
return hwnd;
}
/****************************************************************************
*
* WinMain
*
* Application entry point.
*
****************************************************************************/
int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
MSG msg;
msg.wParam = 0; /* In case something goes horribly wrong */
HWND hwnd = AppInit(hinst, nCmdShow);
if (hwnd) {
/*
* Standard message loop.
*/
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
DITerm();
return msg.wParam;
}