[3.1] error with Xinput gamepad axis

This is the code:

public class TestPad extends SimpleApplication  implements AnalogFunctionListener{

    public static final String GROUP_MOVEMENT = "group movement";
    
    public static final FunctionId F_MOVE = new FunctionId(GROUP_MOVEMENT, "Move");
    InputMapper inputMapper;

    public static void main(String[] args){
        TestPad app = new TestPad();
        AppSettings settings = new AppSettings(true);
        settings.setUseJoysticks(true);
        app.setSettings(settings);
        app.start();
    }

    @Override
    public void simpleInitApp() {
        GuiGlobals.initialize(this);
        inputMapper = GuiGlobals.getInstance().getInputMapper();
        inputMapper.map(F_MOVE, InputState.Negative, Axis.JOYSTICK_LEFT_Y);
        inputMapper.map(F_MOVE, KeyInput.KEY_UP);
        inputMapper.map(F_MOVE, InputState.Negative, KeyInput.KEY_DOWN);
        inputMapper.addAnalogListener(this, F_MOVE);
        inputMapper.activateGroup(GROUP_MOVEMENT);
        
    }

    @Override
    public void valueActive(FunctionId fi, double d, double d1) {
        System.out.println(d);
    }
}

If I connect a DirectInput device everything is ok, but if I connect a Xinput device and press an analog axis I get this error:

WARN: no axis mapping for:JoystickAxis[name=Rotazione Y, parent=Gamepad F310 (Controller), id=2, logicalId=ry, isAnalog=true, isRelative=false, deadZone=0.0]
WARN: no axis mapping for:JoystickAxis[name=Rotazione X, parent=Gamepad F310 (Controller), id=3, logicalId=rx, isAnalog=true, isRelative=false, deadZone=0.0]

Which is very similar to this output with another Xinput device (the xbox controller):smile:

I think those warnings need to be removed. Whether they indicate an actual problem on your end or not is one thing but the a) the warnings are printlns instead of logs, and b) it seems wrong to warn about every random thing the user might hit on their joystick gets a warning output if you havenā€™t setup a mapping for it. We donā€™t do that for any of the other inputs.

Now, if you feel like there actually should be a mapping for that axis then thatā€™s another thing but itā€™s possible that something just needs to be configured at the JME level to map the axis properly in that case.

Well, the TestJoystick donā€™t show this warning and actually responds to my input.
Which makes me think that the problem is probably Lemur-specificā€¦

Well, pretend those warning lines arenā€™t there. Is everything else working otherwise?

If the test joystick app moves the right sticks when you wiggle yours then Lemur is using the same thing.

No. The application above donā€™t respond to input. But TestJoystick does.

That is the problem: TestJoystick works but the application above doesnā€™t.

Itā€™s weird because they both are essentially doing the same logic.

TestJoystick runs the axis object through a set of if/else == checks. InputMapper uses a hashmap.

But since DefaultJoystickAxis is used internally and it provides no custom hashCode() or .equals(), the hashmap should essentially be doing the same as == checkes.

TestJoystick:

            if( axis == axis.getJoystick().getXAxis() ) {
                setXAxis(value);
            } else if( axis == axis.getJoystick().getYAxis() ) {
                setYAxis(-value);
            } else if( axis == axis.getJoystick().getAxis(JoystickAxis.Z_AXIS) ) {
                // Note: in the above condition, we could check the axis name but
                //       I have at least one joystick that reports 2 "Z Axis" axes.
                //       In this particular case, the first one is the right one so
                //       a name based lookup will find the proper one.  It's a problem
                //       because the erroneous axis sends a constant stream of values.
                setZAxis(value);
            } else if( axis == axis.getJoystick().getAxis(JoystickAxis.Z_ROTATION) ) {
                setZRotation(-value);
...

InputMapper hashmap setup:

        joystickAxisMap.put(j.getXAxis(), Axis.JOYSTICK_LEFT_X);
        joystickAxisMap.put(j.getYAxis(), Axis.JOYSTICK_LEFT_Y);
        joystickAxisMap.put(j.getAxis(JoystickAxis.Z_AXIS), Axis.JOYSTICK_RIGHT_X);
        joystickAxisMap.put(j.getAxis(JoystickAxis.Z_ROTATION), Axis.JOYSTICK_RIGHT_Y);

And lookup:

            Axis axis = joystickAxisMap.get(a);
            if( axis == null ) {
                System.out.println( "WARN: no axis mapping for:" + a );
                return;
            }

And now I realize what that warning is aboutā€¦ and itā€™s a valid warning (though should probably be a log).

So then we have to look at if test joystick is doing different ā€˜gamepadā€™ detection since the two paths will map differently.

TestJoystick makes no distinction between a regular joystick and a gamepad. It doesnā€™t care because itā€™s just showing raw values.

Things like FlyCam did because a single joystick tends to map to the wrong ā€˜gamepadā€™ joystick otherwiseā€¦ Lemur does similar and tries to detect the difference to map things differently. (or at least thatā€™s my recollection.)

Anyway, InputMapper is doing this check:

        if( j.getAxis(JoystickAxis.Z_ROTATION) != null
            && j.getAxis(JoystickAxis.Z_AXIS) != null ) {

            mapGamepad(j);
            return;
        }

So it may be that on this particular gamepad, one of those is returning null. Makes me wonder if that check should be an || instead of an &&.

In TestJoystick, can you provide the information about which axis is controlling which portions of which stick for this particular gamepad?

Or if you are really adventurous, step through the InputMapper code in debugger and figure out what the values are and why it doesnā€™t dive into that if block.

These are the axis for DirectInput:

  axes:7
   JoystickAxis[name=Rotazione Z, parent=Logitech Dual Action, id=0, logicalId=rz, isAnalog=true, isRelative=false, deadZone=0.0]
   JoystickAxis[name=Asse Z, parent=Logitech Dual Action, id=1, logicalId=z, isAnalog=true, isRelative=false, deadZone=0.0]
   JoystickAxis[name=Asse Y, parent=Logitech Dual Action, id=2, logicalId=y, isAnalog=true, isRelative=false, deadZone=0.0]
   JoystickAxis[name=Asse X, parent=Logitech Dual Action, id=3, logicalId=x, isAnalog=true, isRelative=false, deadZone=0.0]
   JoystickAxis[name=Hat Switch, parent=Logitech Dual Action, id=4, logicalId=pov, isAnalog=false, isRelative=false, deadZone=0.0]
   JoystickAxis[name=pov_x, parent=Logitech Dual Action, id=5, logicalId=pov_x, isAnalog=false, isRelative=false, deadZone=0.0]
   JoystickAxis[name=pov_y, parent=Logitech Dual Action, id=6, logicalId=pov_y, isAnalog=false, isRelative=false, deadZone=0.0]

Please note that thereā€™s both a Z rotation and a Z axis.

The axis for Xinput:

  axes:8
   JoystickAxis[name=Asse Y, parent=Gamepad F310 (Controller), id=0, logicalId=y, isAnalog=true, isRelative=false, deadZone=0.0]
   JoystickAxis[name=Asse X, parent=Gamepad F310 (Controller), id=1, logicalId=x, isAnalog=true, isRelative=false, deadZone=0.0]
   JoystickAxis[name=Rotazione Y, parent=Gamepad F310 (Controller), id=2, logicalId=ry, isAnalog=true, isRelative=false, deadZone=0.0]
   JoystickAxis[name=Rotazione X, parent=Gamepad F310 (Controller), id=3, logicalId=rx, isAnalog=true, isRelative=false, deadZone=0.0]
   JoystickAxis[name=Asse Z, parent=Gamepad F310 (Controller), id=4, logicalId=z, isAnalog=true, isRelative=false, deadZone=0.0]
   JoystickAxis[name=Hat Switch, parent=Gamepad F310 (Controller), id=5, logicalId=pov, isAnalog=false, isRelative=false, deadZone=0.0]
   JoystickAxis[name=pov_x, parent=Gamepad F310 (Controller), id=6, logicalId=pov_x, isAnalog=false, isRelative=false, deadZone=0.0]
   JoystickAxis[name=pov_y, parent=Gamepad F310 (Controller), id=7, logicalId=pov_y, isAnalog=false, isRelative=false, deadZone=0.0]

Please note thereā€™s only one Z axisā€¦

Will provide information about TestJoystick mappings later, but I remember that left thumstick worked correctly, the right thumstick ā€œXā€ was the sum of the front triggers combined, and the right thumbstick ā€œYā€ wasnā€™t managed.

Iā€™ve tweaked the joystick mapping files and got good results. So good, in fact, that Iā€™d like them to be merged into jme. Hereā€™s my very first PR Added support to Logitech F310 by Pesegato Ā· Pull Request #376 Ā· jMonkeyEngine/jmonkeyengine Ā· GitHub

Iā€™ve also tentatively added support for an alternate version of the xbox360 controller. On my user it was recognized with a differently worded string so I copypasted from xbox360 controller.

Cool, thanks.

So, I applied this to the built in mappings last weekā€¦ does this fix the problem with Lemur or is that still a separate issue?

And if it is still an issue, can you confirm that test joystick properly moves the right stick, both up and down and side to side? Because it seems like if the problem is what I think it is then only one of those will work.

To clarify: there are 2 different issues at play.

Issue 1: some Xinput and DirectInput devices didnā€™t have proper low level mappings. My commit should have fixed that, Iā€™m currently waiting to receive confirmation from my user but that should arrive soon. With these mappings the TestJoystick shows perfect results (however standard Xinput limitations still apply but thatā€™s outside of our scope).

Issue 2: Lemur has an issue with the management input axis. About this issue: should I compile the latest nightly of Lemur/Lemurproto? (in this case please donā€™t tell me simply ā€˜yesā€™ but tell a line or two about dependencies and such, amount of breakage expected and the like :stuck_out_tongue: )

You shouldnā€™t have any breakage with the latest lemur, really. But I also didnā€™t change anything joystick related.

If the proper mappings have been added to make z and rz or whatever then Lemur should work. If not then there is some initialization order thing that is happening that I canā€™t debug myself without one of the gamepads and being able to run it in the dodgy XInput mode.

OK, will test and report back! :smile:

With the new mappings the axis works :smile:

Front triggers donā€™t work with Xinput but thatā€™s probably unavoidableā€¦

Cool. Thanks for testing.

Iā€™ve noticed that every now and then the ā€œneutralā€ position of the axis, instead of being 0, becomes something like 0.145ā€¦
or -0.145ā€¦

I can easily filter this value from java code, but why is this? Am I really supposed to manage values over a certain threshold?

Set a deadzone I guess. The axis in xbox360 (and probably every controller ever) are analogue potentiometers (as opposed to digital pots), sampled into 256 values (-127 to 128), and this value sent down the wire to your computer.

Since the potentiometer and the rest of the mechanism is mechanical in nature, there is going to be some variance which is only made worse with wear and tear. Its probably not a good idea to have the controller itself smooth out these values or have an inbuilt deadzone, so youā€™ll need to do it yourself.

http://javadoc.jmonkeyengine.org/com/jme3/input/InputManager.html <ā€” you can set the deadzone on joystick axes here, I think I found 1 to be about right for my controller. You are lucky you donā€™t need to deal with debouncing buttonsā€¦

1 Like

@pspeed how to set the deadzone with InputMapper? Thanks!

I will have to add it because itā€™s hard-coded to 0.01 right now (JME defaults to 0.05). My own joysticks only exhibited this issue when in analog mode so I waited.

JMEā€™s setting is global but that feels wrong to me. I may see if there is a way we can add a per-joystick setting.

Actually, even though you set a global axis, JME will expose a joystick axisā€™s suggested dead zone. I wonder if this is useful. (I mean I could easily add a setter but I wonder if the joystick itself is reporting good values.)

Can you maybe print the JoystickAxis.getDeadZone() for your controller? Itā€™s possible TestJoystick prints this information as DefaultJoystickAxis() includes it in its toString(). (I donā€™t remember if TestJoystick dumps the axes or not.)