Spacemouse inputs breaking app (technical)

Hello,

There is an issue you are probably aware of, where Spacemouse causes glitches, so I decided to do some debugging.
First let me show you what is going on in this video:

As you can see, NomadSculpt seems to be constantly processing input that is being mapped to ‘Joy Button 3x’. I am not doing anything here with the Spacemouse btw, i am only clicking the input assign buttons to indicate that it is automatically assigning Joy Button 3x. It ranges from at least 32 to 37.

Whenever I disconnect my Spacemouse it stops.

So I decided to do some debugging of the process and found that NomadSculpt is probably using Direct8Input to gather input from this device. So I decided I make my own implementation and log all devices input using Direct8Input to see if I would also receive a continuous stream of seemingly random input, but I did not.

Hopefully this gives a clearer picture as to what is going wrong.

If you could let me know what is being used to gather the input of this device (Direct8Input, GLFW?) then I could potentially find out if this is a Nomad issue, or a Spacemouse issue and provide more information.

THE PLATFORM: Windows 10
NOMAD APP VERSION: 2.9.13
Spacemouse version latest.

For reference, some of my own logging outside of NomadSculpt, this is only logged when I interact with the Spacemouse. So this does not indicate anything wrong with the Spacemouse.

Creating dev[7] "SpaceMouse Enterprise".
  Capabilities:
    Axes:                  6
    Buttons:               286
    POVs:                  0
    Firmware revision:     0x00000000
    Hardware revision:     0x00000000
    FF sample period:      0
    FF min time resolution:0
    FF driver version:     0x00000000
    Flags:
      DIDC_ATTACHED
      DIDC_EMULATED
  Device properties:
    Instance name:         SpaceMouse Enterprise
    Product name:          SpaceMouse Enterprise
    Joystick ID:          17
    Axis mode:            absolute
Opened dev[7] "SpaceMouse Enterprise" with 294 input objects.

Streaming input events from 3 device(s).
Press ESC to quit.

seq=1110     time=7526812  dev[7] SpaceMouse Enterprise    offset=4    Y Rotation                   value=35772
seq=1112     time=7526828  dev[7] SpaceMouse Enterprise    offset=12   Z Axis                       value=33895
seq=1112     time=7526828  dev[7] SpaceMouse Enterprise    offset=4    Y Rotation                   value=37556
seq=1114     time=7526843  dev[7] SpaceMouse Enterprise    offset=12   Z Axis                       value=36054
seq=1114     time=7526843  dev[7] SpaceMouse Enterprise    offset=4    Y Rotation                   value=37275
seq=1116     time=7526859  dev[7] SpaceMouse Enterprise    offset=12   Z Axis                       value=34364
seq=1116     time=7526859  dev[7] SpaceMouse Enterprise    offset=4    Y Rotation                   value=34834
seq=1118     time=7526875  dev[7] SpaceMouse Enterprise    offset=12   Z Axis                       value=32767
seq=1118     time=7526875  dev[7] SpaceMouse Enterprise    offset=4    Y Rotation                   value=32768
seq=1120     time=7526890  dev[7] SpaceMouse Enterprise    offset=4    Y Rotation                   value=32767
seq=1122     time=7527937  dev[7] SpaceMouse Enterprise    offset=0    Collection 4                 raw data=33519
seq=1124     time=7527953  dev[7] SpaceMouse Enterprise    offset=0    Collection 4                 raw data=32767
seq=1126     time=7527968  dev[7] SpaceMouse Enterprise    offset=16   Y Axis                       value=32673
seq=1126     time=7527968  dev[7] SpaceMouse Enterprise    offset=0    Collection 4                 raw data=32956
seq=1128     time=7527984  dev[7] SpaceMouse Enterprise    offset=16   Y Axis                       value=32767
seq=1128     time=7527984  dev[7] SpaceMouse Enterprise    offset=0    Collection 4                 raw data=32767
seq=1130     time=7528000  dev[7] SpaceMouse Enterprise    offset=16   Y Axis                       value=32205
seq=1130     time=7528000  dev[7] SpaceMouse Enterprise    offset=0    Collection 4                 raw data=33331
seq=1132     time=7528015  dev[7] SpaceMouse Enterprise    offset=16   Y Axis                       value=32580
seq=1132     time=7528015  dev[7] SpaceMouse Enterprise    offset=0    Collection 4                 raw data=33050
seq=1134     time=7528031  dev[7] SpaceMouse Enterprise    offset=16   Y Axis                       value=32767
seq=1134     time=7528031  dev[7] SpaceMouse Enterprise    offset=0    Collection 4                 raw data=32767
seq=1136     time=7529968  dev[7] SpaceMouse Enterprise    offset=12   Z Axis                       value=31644
seq=1136     time=7529968  dev[7] SpaceMouse Enterprise    offset=8    X Rotation                   value=36336

Thanks for the report.

I don’t have any space mouse at the moment so I cannot reliably support these.

Also, results are very inconsistent depending on the spacemouse version/configuration. Not to mention it usually reports multiple joystick (the space mouse itself and the KMJ emulation, keyboard/mouse/joystick). So I need to filter these according to the name…

In the binding “advanced” menu you can also control the dead zone drifting (it seems like every controller has it and if the camera is moving, Nomad cannot sculpt). If you toggle off the checkbox it will ignore this device inputs.

In Nomad you can display the log (debug → log or shortcuts → logs).
It shows at least the name of inputs.

On desktop I use glfw for gamepads indeed.

“joy button” are basically the INPUT_KEY_JOY_BUTTON one.
Not sure if I need to ignore these if I detect a spaceMouse.
Honestly I wouldn’t bother too much, I feel like each time I tweak something it breaks the setup for someon else. It’s simply too hard to blind program.

Here the glfw code that I use for gamepads.
I only tested it with a ps4 controller.

static bool onGamepad(int id, GLFWgamepadstate &state) {
    if (vonceGreen("gamepad:", glfwGetJoystickName(id))) vlog();

    bool bDirty = 0;

    const auto &buttons = state.buttons;
    bDirty |= gKeys.setButton(INPUT_KEY_GC_A, buttons[GLFW_GAMEPAD_BUTTON_A]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_B, buttons[GLFW_GAMEPAD_BUTTON_B]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_X, buttons[GLFW_GAMEPAD_BUTTON_X]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_Y, buttons[GLFW_GAMEPAD_BUTTON_Y]);

    bDirty |= gKeys.setButton(INPUT_KEY_GC_L1, buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_R1, buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER]);

    bDirty |= gKeys.setButton(INPUT_KEY_GC_SELECT, buttons[GLFW_GAMEPAD_BUTTON_BACK]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_START, buttons[GLFW_GAMEPAD_BUTTON_START]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_HOME, buttons[GLFW_GAMEPAD_BUTTON_GUIDE]);

    bDirty |= gKeys.setButton(INPUT_KEY_GC_LSTICK, buttons[GLFW_GAMEPAD_BUTTON_LEFT_THUMB]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_RSTICK, buttons[GLFW_GAMEPAD_BUTTON_RIGHT_THUMB]);

    bDirty |= gKeys.setButton(INPUT_KEY_GC_PAD_LEFT, buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_PAD_RIGHT, buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_PAD_UP, buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP]);
    bDirty |= gKeys.setButton(INPUT_KEY_GC_PAD_DOWN, buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN]);

    const auto &axes = state.axes;
    bDirty |= gKeys.setAxis(INPUT_KEY_GC_L2, axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER]);
    bDirty |= gKeys.setAxis(INPUT_KEY_GC_R2, axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER]);

    bDirty |= gKeys.setRange(INPUT_KEY_GC_LSTICK_LEFT, INPUT_KEY_GC_LSTICK_RIGHT, axes[GLFW_GAMEPAD_AXIS_LEFT_X]);
    bDirty |= gKeys.setRange(INPUT_KEY_GC_LSTICK_UP, INPUT_KEY_GC_LSTICK_DOWN, axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);

    bDirty |= gKeys.setRange(INPUT_KEY_GC_RSTICK_LEFT, INPUT_KEY_GC_RSTICK_RIGHT, axes[GLFW_GAMEPAD_AXIS_RIGHT_X]);
    bDirty |= gKeys.setRange(INPUT_KEY_GC_RSTICK_UP, INPUT_KEY_GC_RSTICK_DOWN, axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
    return bDirty;
}

static bool onJoystick(int jid, bool bSpacemouse) {
    int nbAxes = 0;
    int nbButtons = 0;
    int nbHats = 0;
    const float *axes = glfwGetJoystickAxes(jid, &nbAxes);
    const unsigned char *hats = glfwGetJoystickHats(jid, &nbHats);
    const unsigned char *buttons = glfwGetJoystickButtons(jid, &nbButtons);

    if (vonceGreen("joystick :", glfwGetJoystickName(jid))) {
        vonceGreen("axes     :", nbAxes);
        vonceGreen("buttons  :", nbButtons);
        vonceGreen("hats     :", nbHats);
        vlog();
    }

    bool bDirty = 0;

    const int neg = bSpacemouse ? INPUT_KEY_SPACEMOUSE_AXIS_NEG : INPUT_KEY_JOY_AXIS_NEG;
    const int pos = bSpacemouse ? INPUT_KEY_SPACEMOUSE_AXIS_POS : INPUT_KEY_JOY_AXIS_POS;
    for (int i = 0; i < nbAxes; ++i) {
        bDirty |= gKeys.setRange(-1, -1, axes[i], neg + i, pos + i);
    }

    // hats are also present there
    for (int i = 0; i < nbButtons; ++i) {
        bDirty |= gKeys.setButton(-1, buttons[i] == GLFW_PRESS, INPUT_KEY_JOY_BUTTON + i);
    }

    for (int i = 0; i < nbHats; ++i) {
        bDirty |= gKeys.setButton(-1, hats[i] & GLFW_HAT_LEFT, INPUT_KEY_JOY_HAT_LEFT + i);
        bDirty |= gKeys.setButton(-1, hats[i] & GLFW_HAT_RIGHT, INPUT_KEY_JOY_HAT_RIGHT + i);
        bDirty |= gKeys.setButton(-1, hats[i] & GLFW_HAT_UP, INPUT_KEY_JOY_HAT_UP + i);
        bDirty |= gKeys.setButton(-1, hats[i] & GLFW_HAT_DOWN, INPUT_KEY_JOY_HAT_DOWN + i);
    }
    return bDirty;
}

static void pollGamepads() {
    if (!glfwGetWindowAttrib(s_window, GLFW_FOCUSED)) return;

    static int gamepad = -1;
    static int joystick = -1;
    static int spacemouse = -1;
    static int kmj = -1;

    for (int id = GLFW_JOYSTICK_1; id <= GLFW_JOYSTICK_LAST; ++id) {
        if (!glfwJoystickPresent(id)) continue;

        const char *nameC = glfwGetJoystickName(id);
        if (!nameC) continue;

        const std::string name = String::lower(nameC);
        // 3Dconnexion KMJ Emulator
        // SpaceMouse Pro Wireless
        if (name.contains("kmj emulator")) {
            kmj = id;
        } else if (name.contains("spacemouse") || name.contains("3dconnexion")) {
            spacemouse = id;
        } else if (glfwJoystickIsGamepad(id)) {
            gamepad = id;
        } else {
            joystick = id;
        }
    }

    // gamepad (independent)
    if (gamepad >= 0) {
        GLFWgamepadstate state;
        if (glfwGetGamepadState(gamepad, &state)) onGamepad(gamepad, state);
    }

    if (spacemouse >= 0) {
        onJoystick(spacemouse, 1);
    } else if (kmj >= 0) {
        onJoystick(kmj, 0);
    } else if (joystick >= 0) {
        onJoystick(joystick, 0);
    }
}

Your information was very helpful in finding the issue.

For context, I’m using a 3dConnexion Spacemouse Enterprise which has buttons, and disabled KMJ emulation after experiencing this issue without any difference. They don’t work with NomadSculpt because it’s not using the Spacemouse SDK, but the glfwGetJoystickButtons call does return 286 buttons, of which it says 220+ are being pressed, causing NomadSculpt to process 220+ buttons each poll, resulting in NomadSculpt spazzing out.

This is some output I generated based on the code you provided:

Detected jid 0 [SpaceMouse Enterprise] | axes=6 buttons=286 hats=0 gamepad_mapping=no
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223
joystick id: 0 with name: SpaceMouse Enterprise
onJoystick called: 0 spacemouse: 1
nbAxes: 6 nbButtons: 286 nbHats: 0
Amount of pressed buttons: 223

I have tested this by telling NomadSculpt I don’t have any buttons at all. Which fixes the problem because then it no longer does that.Unless you have a reason to process the buttons on other spacemouse models, you might as well not process buttons at all in that case? I doubt any of them with buttons will have their buttons working in NomadSculpt

This is probably a bug on 3dConnexion’s side, because a similar thing happens in some Unity games because they also process it as a joystick. At least I now know why.

If you need any more information just let me know.

Ok I’ll ignore the buttons for spaceMouse joystick, thanks!