/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.picking; import java.util.List; import org.jagatoo.input.devices.components.MouseButton; import org.openmali.FastMath; import org.openmali.spatial.bounds.Bounds; import org.openmali.spatial.polygons.Triangle; import org.openmali.vecmath2.Point3f; import org.openmali.vecmath2.Ray3f; import org.openmali.vecmath2.Tuple3f; import org.xith3d.render.Canvas3D; import org.xith3d.render.RenderPassConfig; import org.xith3d.scenegraph.GroupNode; import org.xith3d.scenegraph.Leaf; import org.xith3d.scenegraph.Node; import org.xith3d.scenegraph.View.CameraMode; import org.xith3d.utility.general.SortableList; import org.xith3d.utility.logging.X3DLog; /** * This is used to convert Mouse coordinates to World coordinates * * @author Arne Mueller * @author Amos Wenger (aka BlueSky) * @author Marvin Froehlich (aka Qudus) */ public class PickingLibrary { private static GeometryPickTester geomPickTester = new DefaultGeometryPickTester(); private static boolean isGeometryIgnored = false; /** * Sets the GeometryPickTester to use for Geometry-Ray intersection tests. * * @param geomPickTester */ public static void setGeometryPickTester( GeometryPickTester geomPickTester ) { PickingLibrary.geomPickTester = geomPickTester; } /** * @return the GeometryPickTester to use for Geometry-Ray intersection tests. */ public static GeometryPickTester getGeometryPickTester() { return ( geomPickTester ); } /** * Sets whether bounds-wise detected pick-result-candidates are checked * more prcisely for geometry-ray intersection. * * @param geomIgnored */ public static void setGeometryIgnored( boolean geomIgnored ) { PickingLibrary.isGeometryIgnored = geomIgnored; } /** * @return whether bounds-wise detected pick-result-candidates are checked * more prcisely for geometry-ray intersection. */ public static boolean isGeometryIgnored() { return ( PickingLibrary.isGeometryIgnored ); } /** * Finds all intersections of a ray with the bounds of all Nodes in a * given Vector of Groups. * In the next step there will be a closer look at these candidates * to check if the ray really intersects the geometries. * The PickResults will be in ascending order so that the nearest * one will be at index 0. * * Don't forget to set the static property Node.setDefaultPickable() to true. * If you are using (Ext)Xith3DEnvironment, if is already done. * * @param groups the groups that should be tested * @param pickRay the pick ray * * @return the List of intersections */ private static void getPickCandidates( GroupNode group, Ray3f pickRay, MouseButton button, List< PickResult > candidates, Point3f pos ) { // DFS (Depth First Search) for ( int i = 0; i < group.numChildren(); i++ ) { final Node node = group.getChild( i ); if ( node.isPickable() && node.isRenderable() ) { // test, if there are intersections of the ray with the bounds // if there is an intersection, record intersection point if possible (for quick exits) // only if there are intersections, keep searching final Bounds bounds = node.getWorldBounds(); if ( bounds == null ) { final String msg = "null bounds detected... skipping full (sub-) group.\n" + " One reson for null bounds is, that you try to pick on a HUD's graph." + " You should consider using one of the pick*() methods, that take a GroupNode instance. This is anyway faster."; X3DLog.error( msg ); continue; //throw new Error( msg ) ); } if ( bounds.intersects( pickRay, pos ) ) { if ( node instanceof GroupNode ) { // this is a group => look at the children getPickCandidates( (GroupNode)node, pickRay, button, candidates, pos ); } else if ( node instanceof Leaf ) { // record dist, because we're only interested in the closest intersection final float dist = pos.distanceSquared( pickRay.getOrigin() ); final PickResult pr = PickPool.allocatePickResult(); pr.set( (Leaf)node, dist, button ); candidates.add( pr ); } } } } } /** * Finds all intersections of a ray with the bounds of all Nodes in a * given Vector of Groups. * In the next step there will be a closer look at these candidates * to check if the ray really intersects the geometries. * The PickResults will be in ascending order so that the nearest * one will be at index 0. * * Don't forget to set the static property Node.setDefaultPickable() to true. * If you are using (Ext)Xith3DEnvironment, if is already done. * * @param groups the groups that should be tested * @param canvas the canvas to which the coordinates refer * @param pickRay the pick ray * @param button the mouse button, that was clicked * * @return the List of intersections */ private static SortableList< PickResult > getPickCandidates( List< ? extends GroupNode > groups, Ray3f pickRay, MouseButton button ) { final SortableList< PickResult > candidates = PickPool.allocatePickResultList(); final Point3f pos = Point3f.fromPool(); for ( int g = 0; g < groups.size(); g++ ) { final GroupNode group = (GroupNode)groups.get( g ); group.updateBounds( true ); getPickCandidates( group, pickRay, button, candidates, pos ); } Point3f.toPool( pos ); return ( candidates ); } /** * Does a closer look at the candidates. If the geometries intersect the ray, * the PickResults are left in the List, otherwise they are removed. * * @param pickCandidates result candidates, which's geometries are checked for intersection with the ray * @param pickRay the pick ray * @param onlyNearest if true, the method returns immediately after the first hit */ private static SortableList< PickResult > checkGeomIntersections( SortableList< PickResult > pickCandidates, Ray3f pickRay, boolean onlyNearest ) { final Triangle triang = PickPool.allocateTriangle(); final Ray3f transRay = PickPool.allocateRay3f(); float closestIntersect = Float.POSITIVE_INFINITY; final SortableList< PickResult > pickResults = PickPool.allocatePickResultList(); PickResult pr; boolean intersects; for ( int i = 0; i < pickCandidates.size(); i++ ) { pr = pickCandidates.get( i ); transRay.set( pickRay ); pr.transform( transRay ); final float intersectionDistance = geomPickTester.testGeometryIntersection( pr, transRay, closestIntersect, triang ); intersects = ( intersectionDistance >= 0.0f ); if ( intersects && onlyNearest ) { closestIntersect = intersectionDistance; } if ( intersects ) { // write the distance into the PickResult Tuple3f p = pr.tmpPos; p.scaleAdd( FastMath.sqrt( pr.getMinimumDistance() ), pickRay.getDirection(), pickRay.getOrigin() ); pr.setPos( p ); pickResults.add( pr ); // (MF) Commented out, since this can't work. The optimization must be done by the GeomPickTester. /* if (onlyNearest) { break; // don't use return here, since the objects need to be freed! } */ } else { PickPool.deallocatePickResult( pr ); } } PickPool.deallocateRay3f( transRay ); PickPool.deallocateTriangle( triang ); PickPool.deallocatePickResultList( pickCandidates ); return ( pickResults ); } /** * Finds all intersections of a given List of GroupNodes with a * given ray. * The PickResults will be in ascending order so that the nearest * one will be at index 0. * * Don't forget to set the static property Node.setDefaultPickable() to true. * If you are using Xith3DEnvironment, it is already done. * * @param groups the groups that should be tested * @param pickRay the pick ray * @param button the mouse button, that was clicked * @param l the listener to be notified of the PickResult * @param userObject the userObject to pass to the listener */ public static void pickAll( List< ? extends GroupNode > groups, Ray3f pickRay, MouseButton button, AllPickListener l, Object userObject ) { final long t0 = System.currentTimeMillis(); SortableList< PickResult > intersecting = getPickCandidates( groups, pickRay, button ); if ( intersecting.size() > 0 ) { if ( !isGeometryIgnored() ) { intersecting = checkGeomIntersections( intersecting, pickRay, false ); } intersecting.sort(); if ( intersecting.size() > 0 ) l.onObjectsPicked( intersecting, userObject, System.currentTimeMillis() - t0 ); else l.onPickingMissed( userObject, System.currentTimeMillis() - t0 ); } else l.onPickingMissed( userObject, System.currentTimeMillis() - t0 ); for ( int i = 0; i < intersecting.size(); i++ ) { PickPool.deallocatePickResult( intersecting.get( i ) ); } PickPool.deallocatePickResultList( intersecting ); } /** * Finds the closest intersection of a given List of NodeGroups with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * Don't forget to set the static property Node.setDefaultPickable() to true. * If you are using (Ext)Xith3DEnvironment, it is already done. * * @param groups the groups that should be tested * @param pickRay the pick ray * @param button the mouse button, that was clicked * @param l the listener to be notified of the PickResult */ public static void pickAll( List< ? extends GroupNode > groups, Ray3f pickRay, MouseButton button, AllPickListener l ) { pickAll( groups, pickRay, button, l, (Object)null ); } private static void calcPickRay( RenderPassConfig rpConfig, List< ? extends GroupNode > groups, PickRay pickRay, Canvas3D canvas, int x, int y ) { if ( groups.size() > 0 ) { pickRay.recalculate( rpConfig, canvas, x, y ); } else { pickRay.recalculate( CameraMode.VIEW_NORMAL, canvas, x, y ); } } /** * Finds the closest intersection of a given List of NodeGroups with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * @param rpConfig the RenderPassConfig to use * @param groups the groups that should be tested * @param canvas the canvas to which the coordinates refer * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult * @param userObject the userObject to pass to the listener */ public static void pickAll( RenderPassConfig rpConfig, List< ? extends GroupNode > groups, Canvas3D canvas, MouseButton button, int x, int y, AllPickListener l, Object userObject ) { if ( groups.size() == 0 ) return; final PickRay pickRay = PickPool.allocatePickRay(); calcPickRay( rpConfig, groups, pickRay, canvas, x, y ); pickAll( groups, pickRay, button, l, userObject ); PickPool.deallocatePickRay( pickRay ); } /** * Finds the closest intersection of a given List of NodeGroups with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * @param groups the groups that should be tested * @param canvas the canvas to which the coordinates refer * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult * @param userObject the userObject to pass to the listener */ public static void pickAll( List< ? extends GroupNode > groups, Canvas3D canvas, MouseButton button, int x, int y, AllPickListener l, Object userObject ) { if ( groups.size() == 0 ) return; final PickRay pickRay = PickPool.allocatePickRay(); calcPickRay( null, groups, pickRay, canvas, x, y ); pickAll( groups, pickRay, button, l, userObject ); PickPool.deallocatePickRay( pickRay ); } /** * Finds the closest intersection of a given List of NodeGroups with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * @param groups the groups that should be tested * @param canvas the canvas to which the coordinates refer * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult */ public static void pickAll( List< ? extends GroupNode > groups, Canvas3D canvas, MouseButton button, int x, int y, AllPickListener l ) { pickAll( groups, canvas, button, x, y, l, (Object)null ); } /** * Finds the closest intersection of a ray and a the nodes in a Group. * The ray is specified by screen coordinates. * You can use it, if you want to know where your mouse is pointing at. * * @param group the Branchgroup to intersect with the ray * @param canvas the canvas in which the pick is performed * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult * @param userObject the userObject to pass to the listener */ public static void pickAll( GroupNode group, Canvas3D canvas, MouseButton button, int x, int y, AllPickListener l, Object userObject ) { final List< GroupNode > groups = PickPool.allocateGroupList(); groups.add( group ); pickAll( groups, canvas, button, x, y, l, userObject ); PickPool.deallocateGroupList( groups ); } /** * Finds the closest intersection of a ray and a the nodes in a Group. * The ray is specified by screen coordinates. * You can use it, if you want to know where your mouse is pointing at. * * @param group the Branchgroup to intersect with the ray * @param canvas the canvas in which the pick is performed * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult */ public static void pickAll( GroupNode group, Canvas3D canvas, MouseButton button, int x, int y, AllPickListener l ) { pickAll( group, canvas, button, x, y, l, (Object)null ); } /** * Finds the closest intersection of a given List of GroupNodes with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * Don't forget to set the static property Node.setDefaultPickable() to true. * If you are using Xith3DEnvironment, it is already done. * * @param groups the groups that should be tested * @param pickRay the pick ray * @param button the mouse button, that was clicked * @param l the listener to be notified of the PickResult * @param userObject the userObject to pass to the listener */ public static void pickNearest( List< ? extends GroupNode > groups, Ray3f pickRay, MouseButton button, NearestPickListener l, Object userObject ) { final long t0 = System.currentTimeMillis(); SortableList< PickResult > intersecting = getPickCandidates( groups, pickRay, button ); if ( intersecting.size() > 0 ) { if ( !isGeometryIgnored() ) { intersecting = checkGeomIntersections( intersecting, pickRay, true ); } intersecting.sort(); if ( intersecting.size() > 0 ) l.onObjectPicked( intersecting.get( 0 ), userObject, System.currentTimeMillis() - t0 ); else l.onPickingMissed( userObject, System.currentTimeMillis() - t0 ); } else l.onPickingMissed( userObject, System.currentTimeMillis() - t0 ); for ( int i = 0; i < intersecting.size(); i++ ) { PickPool.deallocatePickResult( intersecting.get( i ) ); } PickPool.deallocatePickResultList( intersecting ); } /** * Finds the closest intersection of a given List of GroupNodes with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * Don't forget to set the static property Node.setDefaultPickable() to true. * If you are using (Ext)Xith3DEnvironment, it is already done. * * @param groups the groups that should be tested * @param pickRay the pick ray * @param button the mouse button, that was clicked * @param l the listener to be notified of the PickResult */ public static void pickNearest( List< ? extends GroupNode > groups, Ray3f pickRay, MouseButton button, NearestPickListener l ) { pickNearest( groups, pickRay, button, l, (Object)null ); } /** * Finds the closest intersection of a given List of GroupNodes with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * @param rpConfig the RenderPassConfig to use * @param groups the groups that should be tested * @param canvas the canvas to which the coordinates refer * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult * @param userObject the userObject to pass to the listener */ public static void pickNearest( RenderPassConfig rpConfig, List< ? extends GroupNode > groups, Canvas3D canvas, MouseButton button, int x, int y, NearestPickListener l, Object userObject ) { final PickRay pickRay = PickPool.allocatePickRay(); calcPickRay( rpConfig, groups, pickRay, canvas, x, y ); pickNearest( groups, pickRay, button, l, userObject ); PickPool.deallocatePickRay( pickRay ); } /** * Finds the closest intersection of a given List of GroupNodes with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * @param rpConfig the RenderPassConfig to use * @param group the Branchgroup to intersect with the ray * @param canvas the canvas to which the coordinates refer * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult * @param userObject the userObject to pass to the listener */ public static void pickNearest( RenderPassConfig rpConfig, GroupNode group, Canvas3D canvas, MouseButton button, int x, int y, NearestPickListener l, Object userObject ) { final List< GroupNode > groups = PickPool.allocateGroupList(); groups.add( group ); pickNearest( rpConfig, groups, canvas, button, x, y, l, userObject ); PickPool.deallocateGroupList( groups ); } /** * Finds the closest intersection of a given List of GroupNodes with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * @param rpConfig the RenderPassConfig to use * @param group the Branchgroup to intersect with the ray * @param canvas the canvas to which the coordinates refer * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult */ public static void pickNearest( RenderPassConfig rpConfig, GroupNode group, Canvas3D canvas, MouseButton button, int x, int y, NearestPickListener l ) { pickNearest( rpConfig, group, canvas, button, x, y, l, null ); } /** * Finds the closest intersection of a given List of GroupNodes with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * @param groups the groups that should be tested * @param canvas the canvas to which the coordinates refer * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult * @param userObject the userObject to pass to the listener */ public static void pickNearest( List< ? extends GroupNode > groups, Canvas3D canvas, MouseButton button, int x, int y, NearestPickListener l, Object userObject ) { pickNearest( null, groups, canvas, button, x, y, l, userObject ); } /** * Finds the closest intersection of a given List of GroupNodes with a * given ray. * You can use it, if you want to know where your mouse is pointing at. * * @param groups the groups that should be tested * @param canvas the canvas to which the coordinates refer * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult */ public static void pickNearest( List< ? extends GroupNode > groups, Canvas3D canvas, MouseButton button, int x, int y, NearestPickListener l ) { pickNearest( groups, canvas, button, x, y, l, (Object)null ); } /** * Finds the closest intersection of a ray and a the nodes in a Group. * The ray is specified by screen coordinates. * You can use it, if you want to know where your mouse is pointing at. * * @param group the Branchgroup to intersect with the ray * @param canvas the canvas in which the pick is performed * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult * @param userObject the userObject to pass to the listener */ public static void pickNearest( GroupNode group, Canvas3D canvas, MouseButton button, int x, int y, NearestPickListener l, Object userObject ) { final List< GroupNode > groups = PickPool.allocateGroupList(); groups.add( group ); pickNearest( groups, canvas, button, x, y, l, userObject ); PickPool.deallocateGroupList( groups ); } /** * Finds the closest intersection of a ray and a the nodes in a Group. * The ray is specified by screen coordinates. * You can use it, if you want to know where your mouse is pointing at. * * @param group the Branchgroup to intersect with the ray * @param canvas the canvas in which the pick is performed * @param button the mouse button, that was clicked * @param x the x-(mouse)coordinate (in pixels) * @param y the y-(mouse)coordinate (in pixels) * @param l the listener to be notified of the PickResult */ public static void pickNearest( GroupNode group, Canvas3D canvas, MouseButton button, int x, int y, NearestPickListener l ) { pickNearest( group, canvas, button, x, y, l, (Object)null ); } }