Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mouse pointer moves to centre of canvas on click in OpenGL3 with Swing #2327

Open
terranprog opened this issue Nov 12, 2024 · 4 comments
Open

Comments

@terranprog
Copy link

With JME3.7 we can now embed OpenGL3 canvas in a Swing window. I have noticed an issue with the mouse with this configuration.

Steps to reproduce:

  1. Use JME 3.7.0-stable
  2. Be running OpenGL3
  3. Have a jme canvas embedded in a swing window
  4. Listen for jme mouse events using InputManager.addListener(ActionListener)
  5. Left click on the canvas
  6. In the ActionListener, get the position of the mouse using InputManager.getCursorPosition()

Observed
The mouseDown (i.e. keyPressed=true) will have the correct mouse position.
By the time the mouseUp event is processed (i.e. keyPressed=false), the position of the mouse has been moved to the centre of the canvas.

  • The mouse is physically displayed in the centre of the canvas
  • InputManager.getCursorPosition() will return a mouse position in the centre of the canvas

Notes:

  • This does not happen if you are not using Swing
  • This does not happen in OpenGL2 with Swing
  • Seen on both Windows 10 and current Debian linux stable (12.7)

A workaround (more a hack) is to

  • Store the location of the mousedown event
  • In the mouseup event, use java.awt.Robot to move the mouse back to where the mousedown event happened.
@terranprog
Copy link
Author

I should have added that I am using FlyByCamera with dragToRotate set to true. This turns out to be relevant.

A few things I have learnt so far:

The reason for the difference in behaviour between jme3-lwjgl and jme3-lwjgl3 is that they use different instances of com.jme3.input.MouseInput

jme3-lwjgl uses an instance of com.jme3.input.lwjgl.LwjglMouseInput (even if running in a Swing window)
jme3-lwjgl3 uses an instance of com.jme3.input.awt.AwtMouseInput

The unwanted recentering of the mouse cursor that I am seeing happens in com.jme3.input.awt.AwtMouseInput.setCursorVisible.

component.setCursor(newVisible ? null : getTransparentCursor());
    if (!newVisible) {
        recenterMouse(component);
    }

So every time the mouse cursor is set to not visible the cursor is moved to the centre of the canvas.

com.jme3.input.lwjgl.LwjglMouseInput has different code in setCursorVisible:
Mouse.setGrabbed(!visible);

com.jme3.input.awt.AwtMouseInput.recenterMouse has this comment:
// MHenze (cylab) Fix Issue 35: A method to generate recenter the mouse to allow the InputSystem to "grab" the mouse

According to git this Fix has been there since at least 2011.

So in summary, the two instances of MouseInput have different ways of "grabbing" the mouse.

Secondly...
The FlyByCamera has code to hide the mouse cursor when dragToRotate is initiated.

FlyByCamera.onAction(String name, boolean value, float tpf) {
    if (name.equals(CameraInput.FLYCAM_ROTATEDRAG) && dragToRotate) {
        inputManager.setCursorVisible(!value);

The interesting thing is that FlyByCamera treats left mouse clicks as ROTATEDRAG events.
inputManager.addMapping(CameraInput.FLYCAM_ROTATEDRAG, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

This seems questionable. A mouse click is not a mouse drag.

I can see at least two possible fixes for this issue:

  • Improve FlyByCamera so it can differentiate between mouse clicks and mouse drags. That way it would only hide the mouse cursor when the user actually clicks and drags. Which I assume is the intended behaviour.
  • Change AwtMouseInput so it doesn't always centre the mouse when hiding the mouse cursor. Perhaps this behaviour is only required if the mouse is outside the canvas? Or perhaps it could move the mouse to the current mouse position instead of the centre of the canvas? It is also possible the original fix is no longer required.

@capdevon
Copy link
Contributor

capdevon commented Nov 14, 2024

Hi, here is a test class to replicate the issue.

public class Test_SafeCanvas extends SimpleApplication {

    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setGammaCorrection(false); /* for lwjgl3 */
        settings.setResolution(640, 480);

        final Test_SafeCanvas app = new Test_SafeCanvas();
        app.setPauseOnLostFocus(false);
        app.setSettings(settings);
        app.createCanvas();
        app.startCanvas(true);

        JmeCanvasContext context = (JmeCanvasContext) app.getContext();
        Canvas canvas = context.getCanvas();
        canvas.setPreferredSize(new Dimension(settings.getWidth(), settings.getHeight()));

        try {
            Thread.sleep(3000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                app.stop();
            }
        });

        frame.getContentPane().add(canvas);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    @Override
    public void simpleInitApp() {
        flyCam.setDragToRotate(true);
        flyCam.setMoveSpeed(10f);
        viewPort.setBackgroundColor(ColorRGBA.DarkGray);

        Box b = new Box(1, 1, 1);
        Geometry geom = new Geometry("Box", b);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
        geom.setMaterial(mat);
        rootNode.attachChild(geom);
    }

}

terranprog pushed a commit to terranprog/jmonkeyengine that referenced this issue Nov 15, 2024
… click in OpenGL3 with Swing

Modify FlyByCamera to only hide the mouse cursor when the user actually rotates the camera, rather than when the user first presses the left mouse button.
When the mouse is clicked, instead of immediately hiding the cursor, a flag (hideCursorOnNextRotate) is set. If and when a rotate is detected, the flag is checked and if true the cursor is hidden and the flag is cleared.
terranprog pushed a commit to terranprog/jmonkeyengine that referenced this issue Nov 15, 2024
… click in OpenGL3 with Swing

Modify FlyByCamera to only hide the mouse cursor when the user actually rotates the camera, rather than when the user first presses the left mouse button.
When the mouse is clicked, instead of immediately hiding the cursor, a flag (hideCursorOnNextRotate) is set. If and when a rotate is detected, the flag is checked and if true the cursor is hidden and the flag is cleared.
terranprog pushed a commit to terranprog/jmonkeyengine that referenced this issue Nov 15, 2024
… click in OpenGL3 with Swing

Modify FlyByCamera to only hide the mouse cursor when the user actually rotates the camera, rather than when the user first presses the left mouse button.
When the mouse is clicked, instead of immediately hiding the cursor, a flag (hideCursorOnNextRotate) is set. If and when a rotate is detected, the flag is checked and if true the cursor is hidden and the flag is cleared.
@terranprog
Copy link
Author

I fixed the issue (for me) by modifying FlyByCamera to only hide the cursor when the view is actually rotated.
A pull request is here:
#2328

I first tried to fix it by modifying com.jme3.input.awt.AwtMouseInput but couldn't make it work. Also the change required seems more intrusive and more difficult to reason about,

@JNightRider
Copy link
Contributor

JNightRider commented Nov 25, 2024

Hello everyone.

It seems that this problem goes beyond simple mouse cursor positioning; It seems that if the flying camera is active this will eventually break the analog inputs.

By adding the following code block to the example provided by @capdevon, you can see that the mouse position is always the same (There is a small change in position, but it is minuscule - AnalogListener):

inputManager.addListener((ActionListener) (String name, boolean isPressed, float tpf) -> {
	Vector2f pos = inputManager.getCursorPosition();
	if (isPressed) {
		System.out.println("[DOWN] :" + pos);
	} else {
		System.out.println("[ UP ] :" + pos + "\n");
	}
}, "Action");
inputManager.addMapping("Action", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

inputManager.addListener((AnalogListener) (String name, float value, float tpf) -> {
	Vector2f pos = inputManager.getCursorPosition();
	System.out.println("[ANALOG] :" + pos);
}, "Analog");
 inputManager.addMapping("Analog", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

[ OUTPUT ]

[ANALOG] :(320.0, 240.0)
[ANALOG] :(320.0, 240.0)
[ANALOG] :(320.0, 240.0)
[ANALOG] :(320.0, 240.0)
[ANALOG] :(320.0, 240.0)
[ANALOG] :(320.0, 240.0)
[ANALOG] :(320.0, 240.0)
...

[ NOTE ]

Tests carried out in GNU/Linux, I don't know if this is the same on Windows...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants