Thursday, 4 January 2007

Managed DirectInput samples bugs. Logitech G25 FF fails to initialize

Recently, I had the chance to test the magnificent Logitech G25 Racing Wheel, a superb product for PC racing enthusiasts. Of course, after investing two or three hours of my life with TOCA 2 and the wheel ( yeeeeaaaaaah !) , I started to play with it using Managed DirectInput, just to see what is able to do.

I had some problems initializing Force Feedback, and I contacted Joe Kreiner from Logitech, who was very kind, I have to say. They helped me to find the solution, and I describe here what we found:

1.- Managed DirectInput samples have a bug present there for a long time, that any developer probably already discovered. They seemed to have tested their applications with joysticks only, as their code works fine with them. However, with racing wheels (even the Microsoft one), the application reports that no Force Feedback device is present. To find it:

Theres a point in the code like this: if (axis.Length - 1 >= 1)

which tests if the device has 2 or more axis (don´t really understand the sense of comparing (AnyThing -1 >= 1) instead of (AnyThing >= 2) ... :o)

Anyway, replace the code with something like this: if (axis.Length >= 1) and it will work.


2.- The second bug appears when trying to create an instance of the type EffectObject, where an exception of the type "Value does not fall in the required range" is thrown with the G25 (not with other wheels, like the Logitech Driving Force Pro). The problem is in the method FillEffStruct(), where eff.SetAxes() is called twice:

eff.SetAxes(new int[axis.Length]);
...
eff.SetAxes(axis);

Even logitech´s engineers are not very sure what this method does, as there´s no explanation in the DX SDK docs. Anyway, removing the second call to SetAxes solves the problem.


As a conclusion, we are all impressed with the work done in the Managed version of DirectX and we also know it´s been developed by a small team of people with limited resources. Anyway, the documentation lacks lots of explanations, examples, and even methods and classes specs. Hope this will be fixed someday.

Cheers !

16 comments:

DayZ said...

i read this "Anyway, replace the code with something like this: if (axis.Length >= 1) and it will work.
" because i buy a g25 and dont work my force feedback in any games.
I hope to fix it ... but i dont know where i must write this text ...."Anyway, replace the code with something like this: if (axis.Length >= 1) and it will work.
" thks

Iñaki Ayucar said...

This is a programming post. It is related to DirectInput programming, with DirectX.

If your wheel doesn´t show any force feedback with games, it´s not a programming problem. It can be due to a lack of support for the G25 in the game you are trying or to a failure in you G25.

Remember that force feedback will only work if the G25 is plugged to the USB port and to the power line.

Also, you can try to un-plug and re-plug the usb cable of your G25, and lastly, if that doesn´t work, go to the Control Panel (in windows), then to Gaming Devices, select the G25 and click "Properties". Check all the force feedback properties.

Cheers.

Sean said...

Iñaki,

Thanks to your post I was able to play a force to the G25. Thank you!

I am also attempting to rotate the wheel. Do you have any advice for that?

Iñaki Ayucar said...

You´re welcome Sean.

About your question: you mean to apply a constant force to the wheel axis in order to rotate it?

If so, it´s easy. You can take a look to the DX SDK samples. Basically, what you have to do is applying an Effect of the kind "constant force" to the wheel axis object.

I copy here some code that creates an effect object for a "constant force" (using DI = Microsoft.DirectX.DirectInput):

Effect eff = new Effect();
// Allocate some memory for
//directions and axis.
eff.SetDirection(
new int[this.mAxis.Length]);

eff.SetAxes(
new int[this.mAxis.Length]);

eff.EffectType = eif;
eff.ConditionStruct =
new Condition[this.mAxis.Length];
eff.Duration = (int)DI.Infinite;
eff.Gain = 10000;
eff.Constant = new ConstantForce();
eff.Constant.Magnitude = 0;
eff.SamplePeriod = 0;
eff.TriggerButton =
(int)DI.Button.NoTrigger;
eff.TriggerRepeatInterval =
(int)DI.Infinite;
eff.Flags =
EffectFlags.ObjectOffsets | EffectFlags.Cartesian;
eff.UsesEnvelope = false;

Sean said...
This comment has been removed by the author.
Sean said...

Iñaki,

Many thanks for the help!

I had been leaving the magnitude at zero. Once I started putting positive 10k to negative 10k in there it worked like a charm :)

Cheers,
Sean

Engaged and Planning said...

I'm trying to get the code to work with a logitech rumblepad 2 (using a sine effect rather than a constant force effect) however it always gives me an argument out of expected range exception when I try to set the axis of the effect (I had to split the SetParameters calls up to figure out which parameter was causing the problem). My logitech reports two FF axis (8, 12) which are supposedly device specific axis offsets. Do I need to somehow convert these to non-device specific axis offsets? How would I do that?

Anonymous said...

Hi...

Did anyone solve the Problem with the Logitch Rumble Pad ???
I have the same issue....

cheers

Anonymous said...

I just saw in the logitech website that "Logitech Driving Force Pro" is not compatible with PC games. So, does it mean that I can not access to this device under DirectX?. I'd like to use that steeing wheel in a computer game engine (Torque).

Thanks for your response!

Iñaki Ayucar said...

The Driving Force Pro is perfectly compatible with PC, though it says it´s for PlayStation only. I don´t know why Logitech doesn´t sell this wheel for PC. There should be some kind of minor problems or something like that... no clue...

Anyway, I used it for years before the G25 came along. So you indeed will be able to use it through DirectX.

Cheers

Phil said...

Iñaki,

First I would like to thank you for your post. I've searched for the solution to this problem for awhile, and everyone points back to this post.

With your help, I was successful in working around the force feedback initialization bug with the G25.

However, I am having an issue with setting the steering wheel to a rotation I desire. I saw your post about setting the wheel by using a constant force effect, and I was able to do that fine. However, I have a problem where I can't get the wheel to rotate in any direction I want. For the most part, the wheel is either in its initial position, or it's all the way left or right.

I have a slider which modifies the magnitude (I assumed this is how you change the wheel rotation), and as I move the slider the wheel begins to turn a little but at some point it jumps to the maximum value. I'm curious if anyone has any idea how set the wheel to any desired rotation?

Here is the code I was using:

//
// Create the effect
//
// device = the DirectInput device
// effectInformation = constant
// force effect information
//
this.effect = new Effect();
this.effect.SetDirection(new int[_axesCount]);
this.effect.SetAxes(new int[_axesCount]);
this.effect.ConditionStruct = new Condition[_axesCount];
this.effect.EffectType = EffectType.ConstantForce;
this.effect.Duration = (int)DI.Infinite;
this.effect.Gain = 10000;
this.effect.SamplePeriod = 0;
this.effect.TriggerButton = (int)Microsoft.DirectX.DirectInput.Button.NoTrigger;
this.effect.TriggerRepeatInterval = (int)DI.Infinite;
this.effect.Flags = EffectFlags.ObjectOffsets | EffectFlags.Cartesian;

this.effect.Constant = new ConstantForce();
// This is the value I was changing
this.effect.Constant.Magnitude = 0;
this.effect.UsesEnvelope = false;

this.effectObject = new EffectObject(effectInformation.EffectGuid, this.effect, this.device));

...

//
// Update the magnitude
//
this.effect.Constant.Magnitude = value;

this.effectObject.SetParameters(this.effect, EffectParameterFlags.NoRestart);


I'm not sure why the jump in wheel rotation happens. Could it have anything to do with the flag being set to EffectFlags.Cartesian? Am I using the right EffectParameterFlag when updating the effect?

Because the Managed DirectX is no longer updated, I have had a hard time trying to find out what some of these flags actually do.

Anyways, I would appreciate any help. Thanks for your time.

Iñaki Ayucar said...

Hi Phil! Thanks for posting here.

Let me be sure I´m understanding you. You are trying to position the wheel at some certain degrees. To do soy, you are applying a constant force trying to make the wheel arrive to the desired position, right? You are aplying the force and it starts working well but suddenly it goes all the way right or left.

Please, correct me if I misunderstood something.

Checking your code, some things worth to mention:

1.- The most obvious: check your magnitude is well calculated, between the DirectInput allowed values, which are: -10000 .. 10000

2.- Whe you create the EffectObject, in the line:

this.effectObject = new EffectObject(effectInformation.EffectGuid, this.effect, this.device));

Make sure that the effectInformation you are using is the correspondant to the ConstantForce, no to other effect. You can check this with the following:

if(DInputHelper.GetTypeCode(effectInformation.EffectType) == (int)EffectType.ConstantForce)
{
}

3.- About the flags you are using, and the code to set the magitude, you can try this: when you set the magnitude, instead of accessing "this.effect.Constant.Magnitude = XXX" directly, try the following:

Effect eff = new Effect();

eff.Flags = EffectFlags.ObjectIds | EffectFlags.Cartesian;

effectObject.GetParameters(
ref eff,
EffectParameterFlags.AllParams);

eff.Constant.Magnitude = YOUR_MAGNITUDE_HERE;

effectObject.SetParameters(eff, EffectParameterFlags.Start | EffectParameterFlags.Envelope);

If nothing of all this stuff works, maybe you can send me your code and I´ll try it here.

Best,

Iñaki.

Phil said...

Iñaki,

Your assumptions on the question I had are correct. I am trying to position the wheel at some degree by adjusting the magnitude of the constant force effect.

To respond to your reply, I was already boundary checking (suggestion #1) and only changing the constant force effect (suggestion #2). I modified changing the effect like you mentioned (suggestion #3) and nothing was different.

I've implemented a quick application to show my point. I will e-mail you the source (sorry, I don't have any way of putting it somewhere). For those who might want to see what I've done, I'll include the highlights here:

public partial class MainForm : Form
{
//### Fields ###//
private Device device;
private Effect effect;
private List[EffectObject] effectObjects;



//### Constructors ###//
public MainForm()
{
InitializeComponent();

// Set the first available force feedback device
List[DeviceInstance] availableDevices = new List[DeviceInstance]();
foreach (DeviceInstance deviceInstance in Manager.GetDevices(DeviceClass.GameControl,
EnumDevicesFlags.AttachedOnly | EnumDevicesFlags.ForceFeeback))
{
availableDevices.Add(deviceInstance);
}

if (availableDevices.Count > 0)
{
this.device = new Device(availableDevices[0].ProductGuid);
}
else
{
throw new InputException("No available force feedback devices!");
}

// Configure the device
int[] axis = this.ConfigureDevice(ref this.device);

// Apply the constant force
this.effect = new Effect();
this.effect.SetDirection(new int[axis.Length]);
this.effect.SetAxes(new int[axis.Length]);
this.effect.ConditionStruct = new Condition[axis.Length];

this.effect.EffectType = EffectType.ConstantForce;
this.effect.Duration = (int)DI.Infinite;
this.effect.Gain = 10000;
this.effect.SamplePeriod = 0;
this.effect.TriggerButton = (int)Microsoft.DirectX.DirectInput.Button.NoTrigger;
this.effect.TriggerRepeatInterval = (int)DI.Infinite;
this.effect.Flags = EffectFlags.ObjectOffsets | EffectFlags.Cartesian;

this.effect.Constant = new ConstantForce();
this.effect.Constant.Magnitude = 0;
this.effect.UsesEnvelope = false;

this.effectObjects = new List[EffectObject]();
foreach (EffectInformation effectInformation in this.device.GetEffects(EffectType.All))
{
//If the joystick supports ConstantForce, then apply it.
if (DInputHelper.GetTypeCode(effectInformation.EffectType) == (int)EffectType.ConstantForce)
{
// Create the effect,using the passed in guid.
this.effectObjects.Add(new EffectObject(effectInformation.EffectGuid, this.effect, this.device));
}
}

this.magnitudeTrackBar.Value = this.effect.Constant.Magnitude;
this.magnitudeTrackBarValueLabel.Text = this.effect.Constant.Magnitude.ToString();
}


//### Events ###//
private void magnitudeTrackBar_Scroll(object sender, EventArgs e)
{
// Update the effect
this.effect = new Effect();
this.effect.Flags = EffectFlags.ObjectIds | EffectFlags.Cartesian;

foreach (EffectObject effectObject in this.effectObjects)
{
effectObject.GetParameters(ref this.effect, EffectParameterFlags.AllParams);

this.effect.Constant.Magnitude = this.magnitudeTrackBar.Value;

effectObject.SetParameters(this.effect, EffectParameterFlags.Start | EffectParameterFlags.Envelope);
}

this.magnitudeTrackBarValueLabel.Text = this.magnitudeTrackBar.Value.ToString();
}


private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
this.Stop();
}


private void MainForm_Load(object sender, EventArgs e)
{
this.Start();
}


//### Methods ###//
private int[] ConfigureDevice(ref Device _device)
{
//set cooperative level.
_device.SetCooperativeLevel(this,
CooperativeLevelFlags.Exclusive | CooperativeLevelFlags.Background);

//Set axis mode absolute.
_device.Properties.AxisModeAbsolute = true;

//Acquire joystick for capturing.
_device.Acquire();

//Configure axes
int[] axis = null;
foreach (DeviceObjectInstance doi in _device.Objects)
{
//Set axes ranges.
if ((doi.ObjectId & (int)DeviceObjectTypeFlags.Axis) != 0)
{
this.device.Properties.SetRange(ParameterHow.ById,
doi.ObjectId,
new InputRange(-5000, 5000));
}

int[] temp;

// Get info about first two FF axii on the device
if ((doi.Flags & (int)ObjectInstanceFlags.Actuator) != 0)
{
if (axis != null)
{
temp = new int[axis.Length + 1];
axis.CopyTo(temp, 0);
axis = temp;
}
else
{
axis = new int[1];
}

// Store the offset of each axis.
axis[axis.Length - 1] = doi.Offset;

if (axis.Length == 2)
{
break;
}
}
}

return (axis);
}


public void Start()
{
foreach (EffectObject effectObject in this.effectObjects)
{
effectObject.Start(1);
}
}


public void Stop()
{
foreach (EffectObject effectObject in this.effectObjects)
{
effectObject.Stop();
}
}
}


Formatting will surely be ugly, and I apologize for that. Also take note that List 'gator' brackets was replaced with List[TYPE] because the blog believes it's an incorrect HTML tag.

Thanks for your speedy response and suggestions. Hopefully, you can better analyze with the code in hand. :c)

Phil

Iñaki Ayucar said...

Phil, I´ve tried your code with one of my G25s and works like a charm... smooth and correct. So, maybe your G25 is broken. Does it work well with other samples, apps or games?

I only had one problem. The constructor of the class EffectObject was failing. In my case, if I pass to methods like "SetAxes" an array longer than 1 position, the constructor of "EffectObject" fails. Wasn´t it failing in your computer? That´d be strange...

Also, but this is not important, you were setting magnitudes in the range -5000..5000 instead of 10000.

Phil said...

Iñaki,

Well, I'm glad you were able to get it to work. :c) So, you didn't change anything? You basically built the project, ran it, and it works?

To further explain my experience, I was able to get any wheel rotation I wanted if I quickly moved the scroll bar back and forth. What I would do was set the scroll bar to a high value and the wheel would begin moving (towards the max) and then I would move to the scroll bar to a smaller value and it would stop. So, I was able to get any rotation with that method. However, that doesn't work like I expected because there is no consistent mapping between track bar value and wheel rotation. I wasn't able to set the track bar to a value and have the wheel always return to the rotation it was previously at that track bar value.

So, just to clarify what you experienced...

Let's say you wanted a 45 degree angle. You can move the track bar until you have that rotation, and anytime you return to that value on the track bar the wheel will have the 45 degree rotation? If so, you should have a mapping between track bar value and rotation. As an example:
trackValue = 100 rotation = 10
trackValue = 1000 rotation = 45
...
Correct?

I didn't realize the EffectObject constructor failed. You're right though, I get the same result. I never used anything that gave me an axis length greater then 1. Is there a better way to configure the device that wouldn't cause this issue?

I did notice the magnitude was set to -5000 to 5000, but I assumed it wasn't a big deal because I had my issues even within those bounds.

I'm interested to hear more about your experience. I hope the answer is as easy as a defective G25. :c)

Thanks,
Phil

Diegp said...

Hi Iñaki,

Sorry to raise an old post. Did you ever get a Periodic or Condition force to work in Managed C# and your G25? I can get it to load, but it wont playback. I can only seem to get a constant force working.. My periodic code is as follows:


var diEffect = new Effect();
var diPeriodic = new Periodic();
var diEnvelope = new Envelope();




foreach (EffectInformation effectInformation in device1.GetEffects(EffectType.All))

{
if (effectInformation.Name.Contains("Sawtooth Down"))
effect = effectInformation;


}



diPeriodic.Offset = 0;
diPeriodic.Period =100000;
diPeriodic.Phase = 0;
diPeriodic.Magnitude = 8000;



int[] rglDirection = { 1, 0 };


diEffect.SetDirection(rglDirection);
diEffect.SetAxes(new int[1]);
diEffect.UsesEnvelope = false;

diEffect.Periodic = diPeriodic;
diEffect.SamplePeriod = 0;
diEffect.Duration = (int)DI.Infinite;

diEffect.TriggerButton = (int)Microsoft.DirectX.DirectInput.Button.NoTrigger;
diEffect.TriggerRepeatInterval = (int)DI.Infinite;
diEffect.Flags = EffectFlags.Cartesian | EffectFlags.ObjectOffsets;


diEffect.EffectType = EffectType.Periodic;
effectObject = new EffectObject(effect.EffectGuid, diEffect, device1);