[SOLVED] JME Cursor position issue

Ok forum, we’ve been jusing JME for a while now as a sort of 3d scientific visualization tool. Since we have upgraded to JME 3.2 we’ve noticed a different behavior from the mouse. At first we noticed the issue as dead spots in the screen in which mouse manipulation was ignored. We further discovered these dead spots only happened on the vertical axis and resulted from a faulty inputmanager.getCursorPosition() return. We’ve had this problem for quite some time and various forays into solving this issue have been unsuccessful. I am including a link (at least that’s what I hope to do since this is my first forum question) that illustrates the issue. In the video I am only switching the JME version. Absolutely nothing else is changed.

Jmonkey Mouse position issue.

Has anyone else had a similar problem? Any ideas on what our malfunction is? I can’t really post our code as it’s extensive. If it comes down to it, I may have to replicate the problem with a much simpler version.

What was the return?

Lemur uses getCursorPosition() for its input 100% of the time and I so far haven’t seen any deadspots. Maybe there is something else also in effect?

Seems like the only cursor position related change in the last 2-3 years is this one:
https://github.com/jMonkeyEngine/jmonkeyengine/commit/26ae3ec806c3bad233ae2c9192fc6051aafb5aeb#diff-467efdf320cdebad3a23fd633a90a74c

…but I don’t see how that would affect ongoing positions.

History of the LWJGL interface:

History of InputManager:

Here is what went into 3.2.1 stable:

Edit: …ugh… that last link is useless. Click on the github link and not the “Allows build…” link.

1 Like

Are you using HiDPI by any chance? And LWJGL 2 or 3?

Thanks for the quick responses. Yes usually if I have an problem, I’m not the only one. We had to adjust a few other things in our software to work with JME 3.2, items such as line thickness etc. that were changed to be more consistent with OpenGL implementations. But this weird cursor position thing has been irritating me for months. It is an issue across several of our machines in 3 different OSs. I’m working on a super simple code example that I can post. I am assuming when I get it stripped down the issue will disappear which will mean, of course, that it’s something in our code that is being translated differently between the JME versions. When I get a chance I will review those other inputManager links and see if I can get a clue. Thanks again guys.

1 Like

I don’t guess so since I don’t know what HiDPI is…Just using JME libraries out of the box.

One other thing I should add…this actually cropped up with 3.1. Since it was Beta we were hoping it would go away with 3.2.

JME isn’t supporting HiDPI (yet) it seems. There the mouse coordinates as well as the viewports behave differently. But I guess you would know if you are using such displays.

You are then probably using LWJGL 2. You could try LWJGL 3 just to try to narrow this down. This means switching out the LWJGL 2 jar to LWJGL 3 one. I assume it is that easy in the JME JDK.

Are you ever modifying the Vector that is returned from getCursorPosition() anywwhere? Or otherwise holding onto it and potentially modifying it at strange times?

I have to reiterate that I’ve never seen anything like this and all of my apps exclusively use getCursorPosition() to do hit testing, picking, etc…

1 Like

Yes, I’ve fallen to this a couple of time. Remember to use object.clone() !

For example, I once put Color.GREEN on a Material, and then changed its value… and all the greens on my unrelated geometries became blue… :slight_smile:

I use getCursorPosition() directly before taking the values for mouse rotation. No adjustment are done to it that I can find, and like I show in my original video, the only difference is JME version. It’s acting as if it doesn’t know the correct position of the bottom of the viewport.

I will try and switch the LWJGL libraries, though since no one else has seen this, I have to assume there is some slight weirdness in my code that makes the window think it’s in a different position between the JME versions

Still fiddling with this. While tracing I was reminded that JME 3.2 and I guess 3.1 uses LegacyApplication when using a class that extends SimpleApplication. In JME 3.0 this refers to the interface Application. Just a factoid, that may or may not be pertinent.

I have managed to make an extremely simple example that replicates our issue. I am posting code that simply outputs the cursor position as it is moved through the 3D context. When using JME 3.1 or greater, cursor position falls to 0 before reaching the bottom of the viewport. When using JME 3.0 cursor position is shown as expected through the full range of the viewport. The effect on user input is when there is no change in the y position (mouse is below the 0 point) no rotation or dragging occurs until the mouse moves back above 0.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */


import com.jme3.system.AppSettings;
import com.jme3.system.JmeCanvasContext;
import com.jme3.util.JmeFormatter;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

/**
 *
 * @author dmorgan
 */
public class JMEMouseTest extends JFrame
{

    private static JmeCanvasContext context;
//    private static JmeCanvasContext context2;
    private static Canvas canvas;
//    private static Canvas canvas2;
    private static MouseTest_SimpleApp mouseTestApp;

    public JMEMouseTest()
    {
        this.add(canvas);
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    }
    /**
     * @param args the command line arguments
     */
    public static void main(String args[])
    {
        String arg = "true";
        if (args.length > 0)
        {
            arg = args[0];
        }
        final String flag = arg;
        JmeFormatter formatter = new JmeFormatter();

        Handler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(formatter);

        Logger.getLogger("").removeHandler(Logger.getLogger("").getHandlers()[0]);
        Logger.getLogger("").addHandler(consoleHandler);

        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                // switch to system l&f for native font rendering etc.
                try
                {
                    UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
                } catch (Exception ex)
                {
                    Logger.getLogger(getClass().getName()).log(Level.INFO, "can not enable system look and feel", ex);
                }
                JPopupMenu.setDefaultLightWeightPopupEnabled(false);
                JMEMouseTest frame = new JMEMouseTest();

                frame.initFrameSize();
                frame.setVisible(true);
            }
        });
        createCanvas();
    }

    public static void createCanvas()
    {
        AppSettings settings = new AppSettings(true);
        settings.setWidth(1300);
        settings.setHeight(860);

        mouseTestApp = new MouseTest_SimpleApp();
        mouseTestApp.setPauseOnLostFocus(false);
        mouseTestApp.setSettings(settings);

        mouseTestApp.createCanvas();

        context = (JmeCanvasContext) mouseTestApp.getContext();

        canvas = context.getCanvas();
    }
    public void initFrameSize()
    {
        Toolkit tk = Toolkit.getDefaultToolkit();
        Dimension d1 = tk.getScreenSize();
        int height = d1.height;
        int width = d1.width;
        final Dimension d = new Dimension(width, height);
        final int numerator = 9;
        final int denominator = 10;
        setSize(d.width * numerator / denominator, d.height * (numerator) / denominator);
        setLocationRelativeTo(null);
    }
}


/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */


import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AppStateManager;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.petrabytes.volume.ui.viewstates.AxisPointer_ViewState;

/**
 *
 * @author dmorgan
 */
public class MouseTest_SimpleApp extends SimpleApplication
{
    protected float zoom = 1f;
    protected float zoomFactor = 1.2f;
    protected boolean mouseRButtonDown = false;
    protected boolean mouseLButtonDown = false;
    protected boolean mouseMoving = false;
    protected boolean z_rotate = false;
    protected boolean y_rotate = false;
    protected boolean x_rotate = false;

    private final static float TRANS_SPEED = 5f;
    private final static float ROTATE_SPEED = 10f;

    private Node main = new Node("Main");
    private Node axis = new Node("Axis");
//    private Node gridAxis = new Node("GridAxis");

//    private AxisPointer_ViewState axisPointer;

    private AppStateManager appStateManager;

    public MouseTest_SimpleApp()
    {
        main.setCullHint(Spatial.CullHint.Never);
//        gridAxis.setCullHint(Spatial.CullHint.Never);
        rootNode.attachChild(main);
        rootNode.attachChild(axis);
    }

    @Override
    public void simpleInitApp()
    {

        appStateManager = new AppStateManager(this);

//        axisPointer = new AxisPointer_ViewState(this);
//
//        appStateManager.attach(axisPointer);

        appStateManager.update(speed);

        flyCam.setEnabled(false);

        inputManager.clearMappings();

        _setupInputs();
        
        this.viewPort.setBackgroundColor(ColorRGBA.White);

        System.out.println("Completed initializing 3D graphics engine.");
    }

    private void _setupInputs()
    {
        if (inputManager.hasMapping("Right Mouse Button"))
        {
            inputManager.clearMappings();
        }

        inputManager.addMapping("Right Mouse Button",
                new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
        inputManager.addMapping("Left Mouse Button",
                new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addMapping("Zoom In",
                new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
        inputManager.addMapping("Zoom Out",
                new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
        inputManager.addMapping("Move Right",
                new MouseAxisTrigger(MouseInput.AXIS_X, true));
        inputManager.addMapping("Move Left",
                new MouseAxisTrigger(MouseInput.AXIS_X, false));
        inputManager.addMapping("Move Up",
                new MouseAxisTrigger(MouseInput.AXIS_Y, false));
        inputManager.addMapping("Move Down",
                new MouseAxisTrigger(MouseInput.AXIS_Y, true));

        inputManager.addMapping("RotateRight",
                new MouseAxisTrigger(MouseInput.AXIS_X, true));
        inputManager.addMapping("RotateLeft",
                new MouseAxisTrigger(MouseInput.AXIS_X, false));
        inputManager.addMapping("RotateUp",
                new MouseAxisTrigger(MouseInput.AXIS_Y, false));
        inputManager.addMapping("RotateDown",
                new MouseAxisTrigger(MouseInput.AXIS_Y, true));

        inputManager.addListener(analogListener, "RotateUp", "RotateDown", "RotateRight", "RotateLeft", "Zoom In", "Zoom Out",
                "Move Right", "Move Left", "Move Up", "Move Down");
        inputManager.addListener(actionListener, "Right Mouse Button", "Left Mouse Button");

    }
    private ActionListener actionListener = new ActionListener()
    {
        public void onAction(String name, boolean keyPressed, float tpf)
        {
            if (name.equals("Right Mouse Button"))
            {
                mouseRButtonDown = keyPressed;
            }
            if (name.equals("Left Mouse Button"))
            {
                mouseLButtonDown = keyPressed;
                if (!keyPressed)
                {
                    _leftMouseClick();
                }
            }
        }
    };

    private void _leftMouseClick()
    {

    }

    private AnalogListener analogListener = new AnalogListener()
    {
        public void onAnalog(String name, float value, float tpf)
        {

            if (mouseLButtonDown)
            {
                mouseMoving = true;
                if (name.equals("RotateUp"))
                {
                    mouseRotateUD(value);
                } else if (name.equals("RotateDown"))
                {
                    mouseRotateUD(-value);
                }
                if (name.equals("RotateRight"))
                {
                    mouseRotateRL(-value);
                } else if (name.equals("RotateLeft"))
                {
                    mouseRotateRL(value);
                }
            }
            if (mouseRButtonDown)
            {
                if (name.equals("Move Right"))
                {
                    mouseDragRL(-value);
                }
                if (name.equals("Move Left"))
                {
                    mouseDragRL(value);
                }
                if (name.equals("Move Up"))
                {
                    mouseDragUD(-value);
                }
                if (name.equals("Move Down"))
                {
                    mouseDragUD(value);
                }
            }
            if (name.equals("Zoom In"))
            {
                mouseZoomIn();
            }
            if (name.equals("Zoom Out"))
            {
                mouseZoomOut();
            }
            if (name.equals("Move Right") || name.equals("Move Left") || name.equals("Move Up") || name.equals("Move Down"))
            {
                _updateMousePositionText();
            }
        }
    };

    private void _updateMousePositionText()
    {
        Vector2f pos = inputManager.getCursorPosition();
        
        Node curseNode = (Node) guiNode.getChild("Cursor Node");
        if (curseNode == null)
        {
            curseNode = new Node("Cursor Node");
            BitmapFont myFont = assetManager.loadFont("Interface/Fonts/Console.fnt");
            BitmapText hudText = new BitmapText(myFont, false);
            hudText.setSize(myFont.getCharSet().getRenderedSize());      // font size
            hudText.setColor(ColorRGBA.Blue);
            hudText.setText("Cursor position: " + pos.toString());
            hudText.setName("Cursor Label");
            hudText.setLocalTranslation(20, hudText.getLineHeight() - 22, 0);
            curseNode.move(800, 25, 0);
            curseNode.attachChild(hudText);
            guiNode.attachChild(curseNode);
        } else
        {
            BitmapText hudText = (BitmapText) curseNode.getChild("Cursor Label");
            String text = "Cursor position: " + pos.toString();
            hudText.setText(text);
        }

    }

    private void mouseDragRL(float val)
    {
        Vector3f v = new Vector3f(cam.getLocation().x + -val * TRANS_SPEED, cam.getLocation().y, cam.getLocation().z);
        Vector3f v2 = new Vector3f(val * TRANS_SPEED, 0f, 0f);

        cam.setLocation(v);
//        this.axisPointer.drag(v2.negate());
    }

    private void mouseDragUD(float val)
    {
        Vector3f v = new Vector3f(cam.getLocation().x, cam.getLocation().y + val * TRANS_SPEED, cam.getLocation().z);
        Vector3f v2 = new Vector3f(0f, -val * TRANS_SPEED, 0f);
        cam.setLocation(v);
//        this.axisPointer.drag(v2.negate());

    }

    private void mouseZoomIn()
    {

    }

    private void mouseZoomOut()
    {

    }

    public float getZoom()
    {
        return zoom;
    }

    private void mouseRotateUD(float val)
    {

        Vector3f localVector = new Vector3f();
        Quaternion mouse_quat = new Quaternion();
        Node n = (Node) this.viewPort.getScenes().get(0);
        if (n.getChild("Main") == null)
        {
            return;
        }
        n.getChild("Main").worldToLocal(Vector3f.UNIT_X, localVector);
        Quaternion rotation = mouse_quat.fromAngleAxis(-val * this.ROTATE_SPEED, localVector);
        n.getChild("Main").rotate(rotation);

        if (n.getName().equals("Root Node"))
        {
//            this.axisPointer.rotatePointerUD(-val * this.ROTATE_SPEED);
        }

        n.updateGeometricState();
    }

    private void mouseRotateRL(float val)
    {

        Vector3f localVector = new Vector3f();
        Quaternion mouse_quat = new Quaternion();
        Node n = (Node) this.viewPort.getScenes().get(0);
        if (n.getChild("Main") == null)
        {
            return;
        }
        n.getChild("Main").worldToLocal(Vector3f.UNIT_Y, localVector);
        Quaternion rotation = mouse_quat.fromAngleAxis(val * this.ROTATE_SPEED, localVector);
        n.getChild("Main").rotate(rotation);

        if (n.getName().equals("Root Node"))
        {
//            this.axisPointer.rotatePointerLR(-val * this.ROTATE_SPEED);
        }

        n.updateGeometricState();
    }

    private void setRotation(Quaternion q)
    {
        main.setLocalRotation(q);
//        axisPointer.setRotation(q);

    }

    public void rotateRight()
    {
        float deg90 = (float) Math.toRadians(-90);
        Quaternion rotate = new Quaternion();
        rotate.fromAngleAxis(deg90, new Vector3f(0f, 1f, 0f));
        setRotation(rotate);
    }

    public void rotateLeft()
    {
        float deg90 = (float) Math.toRadians(90);
        Quaternion rotate = new Quaternion();
        rotate.fromAngleAxis(deg90, new Vector3f(0f, 1f, 0f));
        setRotation(rotate);
    }

    public void rotateUp()
    {
        setRotation(Quaternion.IDENTITY.inverse());
    }

    public void rotateDown()
    {
        float deg90 = (float) Math.toRadians(180);
        Quaternion rotate = new Quaternion();
        rotate.fromAngleAxis(deg90, new Vector3f(1f, 0f, 0f));
        setRotation(rotate);
    }

    public void rotateFront()
    {
        float deg90 = (float) Math.toRadians(-90);
        Quaternion rotate = new Quaternion();
        rotate.fromAngleAxis(deg90, new Vector3f(1f, 0f, 0f));
        setRotation(rotate);
    }

    public void rotateBack()
    {
        float deg90 = (float) Math.toRadians(180);
        Quaternion rotate = new Quaternion();
        rotate.fromAngleAxis(deg90, new Vector3f(0f, 1f, 1f));
        setRotation(rotate);
    }

    public void rotateNiceView()
    {
        rotateFront();
        Vector3f localVector = new Vector3f();
        float deg45 = (float) Math.toRadians(-45);
        Quaternion mouse_quat = new Quaternion();
        Node n = (Node) this.viewPort.getScenes().get(0);
        n.getChild("Main").worldToLocal(Vector3f.UNIT_Y, localVector);
        Quaternion rotation = mouse_quat.fromAngleAxis(deg45, localVector);
        n.getChild("Main").rotate(rotation);
        if (n.getName().equals("Root Node"))
        {
//            this.axisPointer.rotatePointerLR(-deg45);
        }
        n.updateGeometricState();

    }

    @Override
    public void simpleRender(RenderManager rm)
    {

    }

    public boolean isMouseRButtonDown()
    {
        return mouseRButtonDown;
    }

    public boolean isMouseLButtonDown()
    {
        return mouseLButtonDown;
    }

    public boolean isZrotate()
    {
        return z_rotate;
    }

    public void setZrotate(boolean z_rotate)
    {
        this.z_rotate = z_rotate;
    }

    public boolean isYrotate()
    {
        return y_rotate;
    }

    public void setYrotate(boolean y_rotate)
    {
        this.y_rotate = y_rotate;
    }

    public boolean isXrotate()
    {
        return x_rotate;
    }

    public void setXrotate(boolean x_rotate)
    {
        this.x_rotate = x_rotate;
    }

    public AxisPointer_ViewState getAxisPointer()
    {
        return null;
//        return axisPointer;
    }

    @Override
    public void simpleUpdate(float tpf)
    {
        if (cam.isViewportChanged())
        {
//            this.axisPointer.setResetPos(true);
//            axisPointer.update(30);
        }
    }
}

1 Like

I gave it a try.

Running on jMonkeyEngine 3.1-stable
LWJGL 2.9.3 context running on thread jME3 Main

Couldn’t make it go to zero before cursor reached bottom. Worked fine for me.

1 Like

Random questions unrelated to your issue but they were still strange.

Why do you create your own app state manager and then never properly update except during init when it doesn’t even have any app states? The application already has a state manager and already updates it for you.

Also, why are you calling updateGeometricState() all over the place? This is called automatically and the fact that you do this makes me wonder if there was a reason caused by another issue in your code.

Also also, is this:

…and attempt to see if ‘n’ is the JME root node or just “any random node that happens to have the name ‘Root Node’”? If the latter, then spot on. Else, I’d suggest you simply compare it to the root node so as not to catch any other nodes randomly names “Root Node”.

For example:

if( n == getRootNode() ) {
}

https://javadoc.jmonkeyengine.org/com/jme3/app/SimpleApplication.html#getRootNode--

Note: while this doesn’t seem to be the issue in your case, I thought I’d point out as a general debugging rule that it is important to try to only test the thing you are testing. As the code is written, a half-dozen things could potentially go wrong that prevents the text from changing. The best test would be to put a:
System.out.println(“Cursor position:” + inputManager.getCursorPosition());
…at the top of the onAnalog method.

Given that the call to updateMousePositionText() is down at the bottom of that method, any number of other errors/bugs could prevent it from being called for one reason or another… all that don’t have anything directly to do with getCursorPosition().

Maybe that’s what you’ve already done in your own app and this was only for illustrative purposes but I thought I would mention it because it’s a SUPER common mistake I see even among 20+ year experienced professionals.

1 Like

So the code I posted is a very stripped down version of our prototype code. I wanted to simplify it enough to post on here and still get the same behavior. We actually have quite a few view states that we manage with the app state manager, it’s completely pointless in this example :slight_smile: Same with the .equals"Root Node" segment. That’s not really the name of the root node I don’t think but some other node we named that. I think that we had a PiP we were trying and we had separate nodes for the main and pip area to maintain their own rotation and zoom states. Again of no purpose at all for this example.

Keep in mind that I am getting different behavior based on whether I am using my 3.0 libraries or 3.1/3.2 libraries without any changes at all to the source.

Based on the results that JESTERRRRR got, I am going to check my libraries and and see if that is the problem. The version I posted, despite what is left in there, just a mouse position output. It’s working correctly for him and not for me, so my defect must be in how the libraries are installed.

We actually use a packaged installation that includes the JME libraries among a lot of others. I am going to dig in there and see where it went wrong.

Oh! The updateGeometricState lines, I’m trying to recall why those were there. I recall we were having trouble with the bounding boxes being correct and tried that…before we learned that it doesn’t work for nodes attached to the scene graph. Not sure why they are there now, and they certainly have nothing to do in this example! I’m guessing it’s one of those lines that don’t do anything, so no one got rid of it after they found out it doesn’t do anything. Useless bloat.

Sure… it just seemed odd to make your own when the application already has one.

Sure… but the rest of us aren’t. And anyway, I already said my suggestions had nothing really to do with your problem… just general points I noticed.

And the debugging suggestion still holds. For example, maybe any of those lines above where the actual check is done throws an exception that for some other reason never shows up to the console. Too many variables. I’m sure that’s not what’s happening but there’s also no real reason to leave it to chance.

1 Like

I agree. Thanks for your help!

1 Like