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 ....