Going Deeper with Force Feedback
Force feedback is really a massive topic. I had no idea how complex it was until I tried playing with it. Alas, I'm not going to go into it in any depth. A whole book could be written just about force feedback (and DirectMusic for that matter, but that's another story). However, I am going to give you an idea of what it is, and you'll set up a teeny-weeny force demo.
Force feedback describes the next generation of input devices, which have actuators, motors, and so forth that can exert forces on your hand, or your whole body for that matter. ("Cybersex" is going to take on a whole new meaning in a couple of years.) You've probably seen or may even own a force feedback device, such as the Microsoft Force Feedback joystick or some other similar device.
Programming these devices is very complex. Not only is a good understanding of force, spring, and motion needed, but the devices and the forces events, or effects, have a very close relationship to musical notes. That is, they can have an envelope that modulates the forces as they are applied to the various motors and actuators on the joystick. Thus, values like rate, frequency, timing, and so on all play a role in using and programming force feedback. In fact, creating effects to play or command the force feedback device is so complex that there are third-party tools you can use to create them, such as Microsoft's Force Factory. Luckily, you aren't going to need to use anything that fancy for this demo.
The Physics of Force Feedback
Force feedback devices let you set up two types of effects: motive forces and conditions. Motive forces are like active forces that are always in flux, whereas conditions are in response to an event. In either case, you control the amount of force N (in Newtons) and the properties of the force, such as its direction, duration, and so forth.
Setting Up Force Feedback
The first step in creating a force feedback device is to find one and get its GUID. If you recall how you scanned for standard joystick GUIDs, you're going to do the same thing for force feedback devices. However, when you do the device enumeration, you're going to call it like this:
GUID fjoystickGUID; // used to hold GUID for force joystick
// enumerate attached joystick devices only with
// DInput_Enum_Joysticks() as the callback function
if (FAILED(lpdi->EnumDevices(
DIDEVTYPE_JOYSTICK, // joysticks only
DInput_Enum_Joysticks, // enumeration function
&fjoystickGUID, // send guid back in this var
DIEDFL_ATTACHEDONLY | DIEDFL_FORCEFEEDBACK)))
{ /* error */ }
Once you have the GUID, you create the device as usual. However, you must make sure that the cooperation level is set for DISCL_EXCLUSIVE mode (no one else can use force feedback while you're using it). Here's all the code:
// assume DirectInput has already been created
// version 8 interface pointer
LPDIRECTINPUTDEVICE8 lpdijoy;
// create the joystick with GUID
if (FAILED(lpdi->CreateDevice(joystickGUID, &lpdijoy,
NULL)))
{ /* error */ }
if (FAILED(lpdijoy->SetCooperativeLevel(
main_window_handle,
DISCL_BACKGROUND | DISCL_EXCLUSIVE)))
{ /* error */ }
// set data format
if (FAILED(lpdijoy->SetDataFormat(&c_dfDIJoystick2)))
{ /* error */ }
Okay, now you have a force feedback device set up and ready to go. So what should you do with it?
A Force Feedback Demo
If you like, you can just use the force feedback device like a normal joystick. However, the data packet sent back is now DIJOYSTATE2 rather than DIJOYSTATE. The explanation of the code would take too long, so you're going to have to figure it out based on the comments and the demo program.
However, the code basically sets up an effect that is composed of an envelope and a periodic description. Moreover, the effect is connected to the joystick fire trigger, so it starts when the trigger is held. Here's the code that will set up the force feedback effect, assuming you have the GUID of the device and have set up the force feedback joystick as shown previously:
// force feedback setup
DWORD dwAxes[2] = { DIJOFS_X, DIJOFS_Y } ;
LONG lDirection[2] = { 0, 0 } ;
DIPERIODIC diPeriodic; // type-specific parameters
DIENVELOPE diEnvelope; // envelope
DIEFFECT diEffect; // general parameters
// setup the periodic structure
diPeriodic.dwMagnitude = DI_FFNOMINALMAX;
diPeriodic.lOffset = 0;
diPeriodic.dwPhase = 0;
diPeriodic.dwPeriod = (DWORD) (0.05 * DI_SECONDS);
// set the modulation envelope
diEnvelope.dwSize = sizeof(DIENVELOPE);
diEnvelope.dwAttackLevel = 0;
diEnvelope.dwAttackTime = (DWORD) (0.01 * DI_SECONDS);
diEnvelope.dwFadeLevel = 0;
diEnvelope.dwFadeTime = (DWORD) (3.0 * DI_SECONDS);
// set up the effect structure itself
diEffect.dwSize = sizeof(DIEFFECT);
diEffect.dwFlags = DIEFF_POLAR | DIEFF_OBJECTOFFSETS;
diEffect.dwDuration = (DWORD) INFINITE; // (1 * DI_SECONDS);
// set up details of effect
diEffect.dwSamplePeriod = 0; // = default
diEffect.dwGain = DI_FFNOMINALMAX; // no scaling
diEffect.dwTriggerButton = DIJOFS_BUTTON0; // connect effect
// to trigger button
diEffect.dwTriggerRepeatInterval = 0;
diEffect.cAxes = 2;
diEffect.rgdwAxes = dwAxes;
diEffect.rglDirection = &lDirection[0];
diEffect.lpEnvelope = &diEnvelope;
diEffect.cbTypeSpecificParams = sizeof(diPeriodic);
diEffect.lpvTypeSpecificParams = &diPeriodic;
// create the effect and get the interface to it
lpdijoy->CreateEffect(GUID_Square, // standard GUID
&diEffect, // where the data is
&lpdieffect, // where to put interface pointer
NULL); // no aggregation
For a demo of this in action, check out DEMO9_4.CPP (DEMO9_4_16B.CPP|EXE 16-bit version). It basically takes your little centipede demo and adds a machine gun to it! Of course, you need a force feedback joystick for the demo to work.
NOTE
The force feedback code shown previously is based on the example in the DirectX SDK, so you can refer there for a much more in-depth explanation.
|