Mouse picking issue

Hey all,



Ive got a strange issue here.  It seems that when I rotate a quad or box mouse picking no longer works correctly.



I have a built a test to show the problem, there is a 'custom' built plane which is added to a node (Y-axis rotation) which is then added to another node (X-axis rotation).  When the rotations are Zero, then picking works fine.  However when rotated all the picking results go out the window.  (Does rotation need to be accounted for in the picking algorithm??)



Use num pad to Rotate the object (space to switch between custom object and simple quad, wireframe helps here).



If someone would take a minute to have a gander at my problem I would REALLY appreciate it,  I have spent WAY to long on this issue already and am feeling rather stumped…







import com.jme.app.AbstractGame;
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.input.FirstPersonHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.intersection.BoundingPickResults;
import com.jme.intersection.PickResults;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Ray;
import com.jme.math.Triangle;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.shape.Quad;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.BufferUtils;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;



public class PickTest extends SimpleGame {

    Node nodeWrapper = new Node(), floorNode = new Node();
    Spatial tester = null, floor = null;
    float angle = 5 * FastMath.DEG_TO_RAD;
    Quaternion upRotation = new Quaternion().fromAngleAxis( angle, Vector3f.UNIT_X );
    Quaternion downRotation = new Quaternion().fromAngleAxis( -angle, Vector3f.UNIT_X );
    Quaternion leftRotation = new Quaternion().fromAngleAxis( angle, Vector3f.UNIT_Y );
    Quaternion rightRotation = new Quaternion().fromAngleAxis( -angle, Vector3f.UNIT_Y );
    MousePicker picker = new MousePicker();
    int width = 6, length = 10, tileSize = 16;

    public static void main( String[] args ) {
        new PickTest();
    }

    public PickTest() {
        this.setDialogBehaviour( AbstractGame.FIRSTRUN_OR_NOCONFIGFILE_SHOW_PROPS_DIALOG );
        this.start();
    }

    @Override
    protected void simpleUpdate() {

        boolean updateNode = false;

        if( KeyBindingManager.getKeyBindingManager().isValidCommand( "UP", false ) ){
            nodeWrapper.getLocalRotation().multLocal( upRotation );
            updateNode = true;
        }

        if( KeyBindingManager.getKeyBindingManager().isValidCommand( "DOWN", false ) ){
            nodeWrapper.getLocalRotation().multLocal( downRotation );
            updateNode = true;
        }

        if( KeyBindingManager.getKeyBindingManager().isValidCommand( "LEFT", false ) ){
            floorNode.getLocalRotation().multLocal( leftRotation );
            updateNode = true;
        }

        if( KeyBindingManager.getKeyBindingManager().isValidCommand( "RIGHT", false ) ){
            floorNode.getLocalRotation().multLocal( rightRotation );
            updateNode = true;
        }

        if( KeyBindingManager.getKeyBindingManager().isValidCommand( "TOGGLE", false ) ){
            if( floorNode.hasChild( floor ) ){
                floorNode.detachChild( floor );
                floorNode.attachChild( tester );
            } else{
                floorNode.detachChild( tester );
                floorNode.attachChild( floor );
            }

            updateNode = true;
        }


        if( updateNode ){
            rootNode.updateGeometricState( 0, true );
            floorNode.updateRenderState();
            floorNode.updateWorldData( 0 );
            floorNode.updateWorldVectors();
        }




        //  if( MouseInput.get().isButtonDown( 1 ) ){
        picker.setPickSpatial( rootNode );
        picker.checkMousePicks( true );
        if( picker.wasObjectPicked() ){
            System.out.print( "Picked: " + picker.getPickedObjectName() + "ttLoc:" + picker.getWorldPickLocation() );
            try{
                System.out.println( "ttTri: " + picker.getPickedTriangle().getCenter() );
            } catch( Exception e ){
                System.out.println( "" );
            }

        } else{
            System.out.println( "" );
        }
    //  }
    }

    @Override
    protected void simpleInitGame() {


        KeyBindingManager.getKeyBindingManager().set( "TOGGLE", KeyInput.KEY_SPACE );
        KeyBindingManager.getKeyBindingManager().set( "UP", KeyInput.KEY_NUMPAD8 );
        KeyBindingManager.getKeyBindingManager().set( "DOWN", KeyInput.KEY_NUMPAD2 );
        KeyBindingManager.getKeyBindingManager().set( "RIGHT", KeyInput.KEY_NUMPAD6 );
        KeyBindingManager.getKeyBindingManager().set( "LEFT", KeyInput.KEY_NUMPAD4 );

        floor = buildFloor();


        tester = new Quad( "", width * tileSize, length * tileSize );
        tester.getLocalRotation().fromAngleAxis( 3 * FastMath.HALF_PI, Vector3f.UNIT_X.clone() );
        tester.setModelBound( new BoundingBox() );
        tester.updateModelBound();

        floorNode.attachChild( floor );

        nodeWrapper.attachChild( floorNode );
        rootNode.attachChild( nodeWrapper );


        MouseInput.get().setCursorVisible( true );
        ( (FirstPersonHandler) this.input ).setButtonPressRequired( true );
    }

    private Spatial buildFloor() {

        final Vector3f[] verticies = new Vector3f[ ( length + 1 ) * ( width + 1 ) ];
        final Vector3f[] normals = new Vector3f[ ( length + 1 ) * ( width + 1 ) ];
        final Vector2f[] textures = new Vector2f[ ( length + 1 ) * ( width + 1 ) ];

        for( int row = 0; row < length + 1; ++row ){
            for( int col = 0; col < width + 1; ++col ){
                int index = col + ( row * ( width + 1 ) );
                verticies[index] = new Vector3f( col * tileSize, 0, row * tileSize );
                normals[index] = new Vector3f( 0, 1, 0 );
                textures[index] = new Vector2f( col / ( (float) width ), row / ( (float) length ) );
            }
        }

        final FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer( verticies );
        final FloatBuffer normalBuffer = BufferUtils.createFloatBuffer( normals );
        final FloatBuffer textureBuffer = BufferUtils.createFloatBuffer( textures );
        final IntBuffer indices = BufferUtils.createIntBuffer( getIndices() );

        TriMesh mesh = new TriMesh( "Floor", vertexBuffer, normalBuffer, null, textureBuffer, indices );

        mesh.setModelBound( new BoundingBox() );
        mesh.updateModelBound();
        mesh.setIsCollidable( true );

        // Center it at (0,0,0)
        mesh.getLocalTranslation().subtractLocal( width * tileSize / 2, 0, length * tileSize / 2 );
        mesh.updateGeometricState( 0, true );

        return mesh;

    }

    private int[] getIndices() {

        int numberOfVerts = length * width * 2 * 3;       // 2 Tris/cell, 3 Verts/Tri
        int[] indices = new int[ numberOfVerts ];   //

        for( int row = 0; row < length; ++row ){
            for( int col = 0; col < width; ++col ){
                int index = ( col + ( row * width ) ) * 6;

                // Make 2 triangles
                indices[index] = col + ( row * ( width + 1 ) );
                indices[index + 1] = col + ( ( row + 1 ) * ( width + 1 ) );
                indices[index + 2] = col + 1 + ( row * ( width + 1 ) );

                indices[index + 3] = indices[index + 2];
                indices[index + 4] = indices[index + 1];
                indices[index + 5] = indices[index + 1] + 1;
            }
        }

        return indices;
    }



    private class MousePicker {

        private TriMesh savedMesh = null;
        private Spatial pickNode = null;                             // Typically this will be the rootNode
        private ArrayList<String> pickedNames = null;
        private ArrayList<Integer> tempTriangles = null;
        private String closestPickedName = null;
        private Vector2f screenPos = null,  cursorPosition = null;
        private Vector3f worldPickLocation = null,  camLocation = null,  tempWorldPick = null;
        private Vector3f[] tempTriVerticies = null;
        private Ray ray = null;
        private Triangle pickedTriangle = null,  tempTriangle = null;
        //
        private boolean objectWasPicked = false,  triangleWasPicked = false,  useCanvas = false,  wasTriangleAccurate = false;

        public MousePicker() {
            this( false );
        }

        public MousePicker( boolean useCanvas ) {
            this( useCanvas, null );
        }

        public MousePicker( boolean useCanvas, Spatial pickNode ) {
            this.useCanvas = useCanvas;
            setPickSpatial( pickNode );

            pickedNames = new ArrayList<String>( 1 );
            tempTriangles = new ArrayList();

            screenPos = new Vector2f();
            worldPickLocation = new Vector3f();
            camLocation = new Vector3f();
            tempWorldPick = new Vector3f();
            tempTriVerticies = new Vector3f[ 3 ];

            ray = new Ray();
            pickedTriangle = new Triangle( new Vector3f(), new Vector3f(), new Vector3f() );
            tempTriangle = new Triangle( new Vector3f(), new Vector3f(), new Vector3f() );
        }

        public void setPickSpatial( Spatial spatial ) {
            pickNode = spatial;
        }

        public boolean checkMousePicks() {

            return checkMousePicks( false, null );
        }

        public boolean checkMousePicks( boolean triangleAccurate ) {

            return checkMousePicks( triangleAccurate, null );
        }

        public boolean checkMousePicks( boolean triangleAccurate, Vector2f cursorPosition ) {

            if( cursorPosition != null ){
                this.cursorPosition = cursorPosition.clone();
            }

            return runPicker( triangleAccurate );
        }

        private boolean runPicker( boolean triangleAccurate ) {


            if( pickNode == null ){
                return false;
            }

            clearPickResults();
            wasTriangleAccurate = triangleAccurate;

            float shortestDist = Float.MAX_VALUE;

            camLocation.set( DisplaySystem.getDisplaySystem().getRenderer().getCamera().getLocation() );

            if( cursorPosition != null ){
                screenPos.set( cursorPosition );
            } else{
                screenPos.set( MouseInput.get().getXAbsolute(), MouseInput.get().getYAbsolute() );
            }

            DisplaySystem.getDisplaySystem().getPickRay( screenPos, useCanvas, ray );

            PickResults results = new BoundingPickResults();
            results.setCheckDistance( true );
            pickNode.findPick( ray, results );

            if( results.getNumber() > 0 ){

                objectWasPicked = true;

                for( int i = 0; i < results.getNumber(); i++ ){
                    String pickedName = results.getPickData( i ).getTargetMesh().getParentGeom().getParent().getName();
                    pickedNames.add( pickedName );


                    if( triangleAccurate ){
                        TriMesh mesh = (TriMesh) results.getPickData( i ).getTargetMesh().getParentGeom();
                        mesh.updateWorldVectors();

                        mesh.findTrianglePick( ray, tempTriangles, 0 );

                        for( Integer index : tempTriangles ){

                            mesh.getTriangle( index, tempTriVerticies );

                            tempTriangle = setTriVerticies( tempTriangle, tempTriVerticies, mesh.getWorldTranslation() );

                            // check to make sure picked triangle is the closest possible triangle
                            if( ray.intersect( tempTriangle ) ){

                                // get the exact location (in World coordinates) of the pick
                                ray.intersectWhere( tempTriangle, tempWorldPick );

                                //      System.out.println( "tPicked: " + tempWorldPick );


                                // Check if its the closest triangle
                                if( tempWorldPick.distance( camLocation ) <= shortestDist ){

                                    closestPickedName = pickedName;
                                    triangleWasPicked = true;
                                    shortestDist = tempWorldPick.distance( camLocation );

                                    // Set pickedTriangle and then save target mesh
                                    setTriVerticies( pickedTriangle, tempTriVerticies, mesh.getWorldTranslation() );
                                    worldPickLocation.set( tempWorldPick );
                                    savedMesh = mesh;
                                }
                            }

                        }
                    }  // End Triangle Accurate


                }

            }

            if( triangleAccurate ){
                return triangleWasPicked;
            }

            return objectWasPicked;
        }

        public boolean wasObjectPicked() {
            return !pickedNames.isEmpty();
        }

        public String getPickedObjectName() {

            if( wasTriangleAccurate ){
                if( closestPickedName != null ){
                    return closestPickedName;
                } else{
                    return "";
                }
            }

            try{
                return pickedNames.get( 0 );
            } catch( Exception e ){
                return "";
            }
        }

        public ArrayList<String> getAllPickedObjects() {
            return pickedNames;
        }

        public Triangle getPickedTriangle() {

            if( triangleWasPicked ){
                return pickedTriangle;
            }
            return null;
        }

        public TriMesh getSavedMesh() {
            return savedMesh;
        }

        public Vector3f getWorldPickLocation() {
            return worldPickLocation;
        }

        public void clearPickResults() {
            triangleWasPicked = false;
            objectWasPicked = false;
            savedMesh = null;
            closestPickedName = null;
            tempTriangles.clear();
            pickedNames.clear();
            worldPickLocation.set( 0, 0, 0 );
            pickedTriangle.get( 0 ).set( 0, 0, 0 );
            pickedTriangle.get( 1 ).set( 0, 0, 0 );
            pickedTriangle.get( 2 ).set( 0, 0, 0 );
        }

        private Triangle setTriVerticies( Triangle triangle, Vector3f[] verts, Vector3f worldTranslation ) {

            for( int i = 0; i < verts.length; ++i ){
                triangle.get( i ).setX( verts[i].getX() + worldTranslation.getX() );
                triangle.get( i ).setY( verts[i].getY() + worldTranslation.getY() );
                triangle.get( i ).setZ( verts[i].getZ() + worldTranslation.getZ() );
            }

            triangle.calculateCenter();
            return triangle;

        }
    }
}



You should be able to just copy and paste the code into PickTest.java ....

You problem is you only take translation into account.  Pass the TriMesh parent in, instead of just the world translation, and then do something like this (tested in jme2, so syntax may vary):


        private Triangle setTriVerticies( Triangle triangle, Vector3f[] verts, TriMesh mesh ) {
            for( int i = 0; i < verts.length; ++i ){
                mesh.localToWorld(verts[i], triangle.get(i));
            }

            triangle.calculateCenter();
            return triangle;

        }



Once your triangles are in the correct worl coords (using scale, rotation and translation) it works fine for me.

You da man renanse, I was looking and looking through the API; sure I had seen something JUST like that.  Thanx for your help and the VERY timely response. :smiley:

hth. :slight_smile: