[SOLVED] Mapping a trigger

I have a wired Xbox controller that I’m trying to map with my vehicle game and I’m having trouble getting to grips with analog triggers.

So far as I can tell the trigger is an axis of its own with a positive and negative. That is to say when I’m not pressing it, it’s value is 1. When it’s in the middle it’s zero and fully pressed its -1.

So if I bind accelerate to the trigger only half of it works. If I bind both positive and negative, the reading both for positive and negative go from 1 to 0 to 1 as I press it from not pressed to fully pressed.

So I get that I shouldn’t bind one mapping to the positive and negative axis, so how do I bind it so the reading transitions correctly from 1 to -1?

I think on the xbox controllers, it’s even weirder than that since the left/right triggers work together. At least that’s what I remember from local testing when I finally got an xbox compatible gamepad.

My PS4 gamepad has them indepent but they also run -1 to 1… which is fun when you’ve also mapped it to keys because the -1 will constantly come through and override the key press.

In InputMapper, I’ve considered normalizing these buttons to 0…1. I do it in my code already. (Or at least provide the option of normalizing them so as not to break anyone already dealing with this.) It’s a pain because now I’ve had to map accel/brake keys separate from accel/brake triggers.

1 Like

If I look in control panel the triggers are using the same axis. It starts in the middle and each trigger moves the bar either side. In JME they are reported as axis 4 and 5 - separate axis.

I must be doing something wrong. I’ll have another stab at it tomorrow.

Mapping has always been confusing for me. Especially the joystick mapper. I understand the concept of mapping but for me at least it was annoyingly difficult mapping them manually to the properties file.

I should probably educate myself more in that area of the engine.

This is how my game does it, last time i checked it worked, but its been a while:

I have used an xbox 360 and xbox one alike controllers with this setup. (and i think its hard coded to them)
I don’t suggest copying it directly but at least it might help?

Might be useful to see a screen shot from the TestJoystick application.

I just tested my xbox controller again here and the triggers both show up as id:rx which is mapped to Z Axis. Left button goes from 0…1 and right button goes from 0…-1… and they fight each other such that if I hold both then the axis reports 0.

I think this is because they act like rudder pedals on a plane.

This is what my TestJoystick app says:

If I clear the joystick mappings then I see this:

The relevant section of the mappings file starts here:

The regex is saying that anything with ‘xbox’ (case insensitive) in the driver-provided name will hit the “XBOX” section of the config… the one immediately following that line.

The triggers on my gamepad naturally report ‘z’ (both of them). I understand from vague memory that this dual-trigger-one-axis thing is not universal across xbox gamepads.

Anyway, the normal JME ‘way’ wants to use ‘z’ as the right joystick’s left/right look. So the config says:
XBOX.rx=z
ie: if you see an axis marked ‘rx’ then treat it like ‘z’ from JME’s perspective. ‘rx’ for my gamepad is the left/right axis of the right analog stick.

Where as:
XBOX.z=rx
…says if there is an axis reported as ‘z’ then treat it as rx.
Which JME treats as the left trigger:

Also note the comment right after that as to why this appears to be backwards.

The natural XBOX mapping makes a lot more sense (one versus to trigger axis not withstanding)… but it’s harder to detect the other cases. So the default way is backwards.

1 Like

This is a screenshot straight from TestJoystick.java - mappings have not been cleared. Axis 4 and 5 are the lower analog triggers.

As far as I know it’s an “official” controller. If I use it on any game it recognizes it as such and “just works”. For example I play NFS Shift 2 here and there for inspiration on my vehicle game and it’s literally just “plug and play”.

image


Afterglow controllers are thrid party, so I assume they don’t report XBOX in their name?

Yeah. In the first screenshot it says “Xbox Controller”. And in Control Panel it shows as “Xbox One for Windows”.

image

If you comment out that line in the joystick mappings config and rerun test joystick then you can see what your joystick would look like with no mappings at all.

Pressing the right/left triggers will show stuff on the console. That stuff is diagnostically significant.

In the end, those are the raw values that we get from the jinput library and it’s what we have to work with. (At least, I assume that is the library in play here… I don’t know if you are running lwjgl3 and if lwjgl3 redoes the joystick code or not… I don’t remember.)

Interesting. I was running LWJGL3. Changing to LWJGL2 does in fact map a whole lot more of the buttons than with LWJGL3. The left and right trigger are mapped as the same axis (rx) - but other than that the mappings are fine.

image

Notice that it also detects a different name with LWJGL2 and more axis are detected. The name is the same as Windows Control Panel detects it.

So it seems the mapping.properties file may only be valid for LWJGL2.

So far I’m not impressed with the input handling (keyboard, joystick, otherwise) of lwjgl3.

AFAIK lwjgl3+GLFW uses SDL_GameControllerDB.

https://www.glfw.org/docs/latest/input_guide.html#gamepad

Edit:
Note GLFW clone of SDL mappings file seems is not up to date with the one in SDL_GameControllerDB GitHub. Looking for Afterglow in the above mappings file, I could find 2 mappings for it.

The one in SDL_GameControllerDB GitHub page seems to include 12 mappings for the Afterglow controller.

Just in case you want to give it a try with the newer one you can load it at runtime as described here:

https://www.glfw.org/docs/latest/input_guide.html#gamepad_mapping

3 Likes

Thanks @Ali_RS - I tried to do as you explained and it results in a native crash. Oh well. Thanks for trying anyway :slight_smile:

updateMappings(new File("gamecontrollerdb.txt"));
private static void updateMappings (File file) {

        if (!file.exists()) {
            throw new RuntimeException("File does not exist.");
        }

        byte[] bytes = new byte[0];

        try {
            bytes = Files.readAllBytes(file.toPath());
        } catch (IOException e) {
            e.printStackTrace();
        }

        // for some reason the loader wants a null char at the end of the file.
        byte[] newbytes = new byte[bytes.length + 1];
        System.arraycopy(bytes, 0, newbytes, 0, bytes.length);
        newbytes[newbytes.length - 1] = '\0';

        ByteBuffer byteBuffer = ByteBuffer.wrap(newbytes);
        byteBuffer.rewind();

        // always returns false if loaded before .start() or in constructor.
        // Crashes natively if loaded in simpleInit().
        boolean b = GLFW.glfwUpdateGamepadMappings(byteBuffer);

        // debugger breakpoint.
        String a = "b";
    }
1 Like

Tried your test and it crashes for me too. I reported it on LWJGL github, hopefully someone will help. I will let you know if I was able to resolve it.

1 Like

Meanwhile can you please test with the below test. It runs without error for me.

(Note you should use with JME ‘3.3.0-alpha2’ or prior)

public class TestGlfwGamepadMapping extends SimpleApplication {

    private static String SDL_GameControllerDB = null;

    public static void main( String... args ) {
        Configuration.STACK_SIZE.set(1024);
        TestGlfwGamepadMapping main = new TestGlfwGamepadMapping();
        main.start();
    }

    @Override
    public void simpleInitApp() {
        boolean b = GLFW.glfwUpdateGamepadMappings(getSDL_GameControllerDB());
        System.out.println("mapped=" + b);
    }


    public static String getSDL_GameControllerDB() {
        if (SDL_GameControllerDB == null) {
            SDL_GameControllerDB = loadFile("/gamecontrollerdb.txt");
        }
        return SDL_GameControllerDB;
    }

    private static URL getURL(String resourceFilePath) {
        final URL res = TestGlfwGamepadMapping.class.getResource(resourceFilePath);
        return res;
    }

    private static String loadFile(String resourceFilePath) {
        String res = "";
        try (
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(getURL(resourceFilePath).openStream()));
        ) {
            final StringBuffer sb = new StringBuffer();
            String tmp;
            while (true) {
                tmp = bufferedReader.readLine();
                if (tmp == null) {
                    break;
                }
                sb.append(tmp);
                sb.append("\n");
            }
            res = sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return res;
    }
}

Ok, regarding native crash, seems you need to change this

ByteBuffer byteBuffer = ByteBuffer.wrap(newbytes);
byteBuffer.rewind();

to

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bytes.length + 1);
byteBuffer.put(newbytes);
byteBuffer.rewind();
2 Likes

If this just a “test not updated to match new version” or is there something that broke in later versions?

Thanks - that solved the native crash. It still detects the joypad as “Xbox Controller” but the keys are mapped better. Not perfect, but a lot better.

I don’t know if it’s a good idea or not to include a check in the resources directory for a mapping file so the user can update the list themselves - much like we do for mapping.properties.

2 Likes

Yes. Latest JME version is using LWJGL 3.2.3 and that version of LWJGL has removed CharSequence overload of glfwUpdateGamepadMappings() due to OutOfMemoryError. (It’s recommended to use the ByteBuffer version instead)

That’s the reason I am increasing stack size in my test Configuration.STACK_SIZE.set(1024);

1 Like

Yes, seems a good idea to me.

btw, if you come up with a better mapping for your controller you may submit a PR on SDL_GameControllerDB GitHub.