Hi,
Let me first do a quick little introduction of myself on this forum: I'm Marijn, 20 years old, Game Technology student in Utrecht. I've written a tutorial on how to use FreePIE to combine their expensive, multipart simracing setups for a game that supports only one controller. Many people wanted their FFB support as well, that's why I'm working on it now, even though I don't even own an FFB device (testing is
really hard
).
I was thinking about the exact same implementations: one to directly forward FFB data from vJoy to a SlimDX device (usecase when controllers are simply combined and the user doesn't need to worry about FFB data), and the other to separately read-out/receive FFB data in FreePIE, do something with it and send FFB commands to a SlimDX device (this can also be used entirely separated).
I'll start off with the SlimDX part, my next post will contain the vJoy section.
Effect
So, the SlimDX implementation is fairly simple. There's an Effect object that combines a SlimDX device, GUID of the effect and it's EffectParameters, and exposes methods to control it (start, stop, loop etc). Calling the constructor will automatically tell the device to create a new effect, and eventually send the EffectParameters (these can be passed in later on).
Global Gain
This is a simple multiplier to control the overal strength of
every effect on the device, whereas 10.000 is the maximum and actually means no scaling (iow: multiplying the force from an effect by 1)
EffectParameters
These are global settings for an effect. They contain the following basic parameters:
Code: Select all
EffectParams[blockIndex] = new EffectParameters()
{
Duration = -1, //-1 is infinite, I haven't yet found on which scale this is (in C++ examples it's multiplied by DI_SECONDS)
Flags = EffectFlags.Cartesian | EffectFlags.ObjectIds, //Contains info about the effect: whether the direction is given in Polar, Cartesian or Spherical coordinates. ObjectIds is to tell what type will be used to determine the axis when
Gain = 10000, //multiplier over this single effect. Works the same as Gain explained above
SamplePeriod = 0, //default sample period
StartDelay = 0, //self explanatory
TriggerButton = -1, //a button can be specified to trigger an effect
TriggerRepeatInterval = 0, //probably how many times you can press the button to trigger the effect?
Envelope = null //to apply a Gain based on time (can use this to a ramping up or down force with a single effect). I can explain more on this if you wish
};
Axes
Obviously you have to define in what direction the force is. This has to be done by specifying the direction the effect
comes from. So if I specify a cartesian vector [1,0], which is a vector pointing to the right, the effect will come from the right side and push the stick/wheel to the left (in case of a wheel, going left on the X axis means turning counterclockwise).
The Polar variant is a little confusing as well. A Polar system is defined as a vector pointing to the right and rotating it counterclockwise (example: 90 degrees means that the vector pointing to the right will be rotated 90 degrees counterclockwise). However, for some weird reason Polar coordinates in FFB start with a vector pointing to the top which is rotated clockwise. So, an FFB effect of 90 degrees Polar means a source vector pointing to the right, thus again an effect that'll turn your wheel counterclockwise or move your stick to the left. The polar angle should be specified in the first element of the dirs array
DX wants to know the actuators that you are using for this effect, so we first have to get that data. In the above piece of code I specified DX will receive ObjectIds, so let's get these:
Code: Select all
List<int> ax = new List<int>();
foreach (DeviceObjectInstance deviceObject in joystick.GetObjects())
{
if ((deviceObject.ObjectType & ObjectDeviceType.ForceFeedbackActuator) != 0)
{
ax.Add((int)deviceObject.ObjectType); //change this to .Offset in case you've specified EffectFlags.Offsets instead
}
}
Axes = ax.ToArray();
(this should be executed when the device is initialized, values won't change over time)
Now, we can simply set the axes on our efffect:
Code: Select all
EffectParams[blockIndex].SetAxes(Axes, dirs); //dirs in this case either is the cartesian vector, or it contains the Polar angle in the first element (lengths of Axes and dirs should be the same)
TypeSpecificParameters
Parameters specific to a certain effect. Each effect has it's own type. For now, we'll only discuss the easiest one: ConstantForce.
Because we want to modify this effect on the fly is actually the only reason we cache EffectParams in an array. In the past I haven't been able to get the following piece of code to work, but I might need to revisit it and retry:
Code: Select all
EffectParameters ep = Effects[blockIndex].GetParameters(EffectParameterFlags.TypeSpecificParameters);
(I'll explain EffectParameterFlags in a minute)
Anyway, TypeSpecificParameters are 'stored' in the Parameter property from an EffectParameter, so in case we want to use a constant force, we simply do the following:
Code: Select all
EffectParams[blockIndex].Parameters = new ConstantForce();
Now, if we want to modify the Magnitude (which is actually the only parameter of a constant force), we can use the built-in casting methods (it's obviously also possible to do the cast yourself):
Code: Select all
EffectParams[blockIndex].Parameters.AsConstantForce().Magnitude = magnitude;
The only thing that's left is telling our Effect (remember, the object that links a joystick with EffectParameters?) that
something has changed in the EffectParameters. This is done by passing our EffectParameters object to the SetParameters method, as well as EffectParameterFlags - which is used to define
something so that only that specific piece of data needs to be re-uploaded to the device. In this case, we only modified the TypeSpecificParameters, so:
Code: Select all
Effects[blockIndex].SetParameters(EffectParams[blockIndex], EffectParameterFlags.TypeSpecificParameters);
(EffectParameterFlags are also used in GetParameters to only download a specific part of the EffectParameters. I'm pretty sure,
though not a 100% because there's no documentation this, it's also possible to just create new EffectParameters, only modify the TypeSpecificParameters and upload it with EffectParameterFlags.TypeSpecificParameters, which
shouldn't affect anything else)
Sidenote on BlockIndex
BlockIndex is simply used to tell the FFB device which effect the current packet is about (because an FFB device can keep track of (and probably play) multiple effects simultaneously). We don't need it in DX (since that's all managed for us in the background), however it's important when working with vJoy (because you actually only receive bare packets with it)
Disposing effects and the device
This is something that I'm still discovering in. There's some really weird behaviour when used in combination with vJoy. Sadly because I don't own an FFB device I can hardly test it.
Well that's it actually. It isn't hard to modify this data from FreePIE either, as you can see I already exposed the required methods to Python, however it's probably better to return an object instead of using a blockIndex alike mechanism.