All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.j3d.utils.picking.PickTool Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution 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 Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or
 * intended for use in the design, construction, operation or
 * maintenance of any nuclear facility.
 *
 */

package com.sun.j3d.utils.picking;

import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.CompressedGeometry;
import javax.media.j3d.Geometry;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.IndexedGeometryArray;
import javax.media.j3d.Locale;
import javax.media.j3d.Morph;
import javax.media.j3d.Node;
import javax.media.j3d.PickBounds;
import javax.media.j3d.PickConeRay;
import javax.media.j3d.PickConeSegment;
import javax.media.j3d.PickCylinderRay;
import javax.media.j3d.PickCylinderSegment;
import javax.media.j3d.PickRay;
import javax.media.j3d.PickSegment;
import javax.media.j3d.PickShape;
import javax.media.j3d.SceneGraphPath;
import javax.media.j3d.Shape3D;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * The base class for picking operations.
 * The picking methods will return a PickResult object for each object picked,
 * which can then be queried to
 * obtain more detailed information about the specific objects that were
 * picked.
 * 

* The pick mode specifies the detail level of picking before the PickResult * is returned: *

*

    *
  • PickTool.BOUNDS - Pick using the bounds of the pickable nodes. The * PickResult returned will contain the SceneGraphPath to the picked Node. *
  • *
  • PickTool.GEOMETRY will pick using the geometry of the pickable nodes. * The PickResult returned will contain the SceneGraphPath to the picked Node. * Geometry nodes in the scene must have the ALLOW_INTERSECT capability set for * this mode.
  • *
  • PickTool.GEOMETRY_INTERSECT_INFO -is the same as GEOMETRY, but the * the PickResult will also include information on each intersection * of the pick shape with the geometry. The intersection information includes * the sub-primitive picked (that is, the point, line, triangle or quad), * the closest vertex to the center of the pick shape, and * the intersection's coordinate, normal, color and texture coordinates. * To allow this information to be generated, Shape3D and Morph nodes must have * the ALLOW_GEOMETRY_READ capability set and GeometryArrays must have the * ALLOW_FORMAT_READ, * ALLOW_COUNT_READ, and ALLOW_COORDINATE_READ capabilities set, plus the * ALLOW_COORDINATE_INDEX_READ capability for indexed geometry. * To inquire * the intersection color, normal or texture coordinates * the corresponding READ capability bits must be set on the GeometryArray. *
  • *
*

The utility method * * PickTool.setCapabilities(Node, int) * can be used before the scene graph is * made live to set the * capabilities of Shape3D, Morph or Geometry * nodes to allow picking. *

* A PickResult from a lower level of detail pick can be used to * inquire more detailed information if the capibility bits are set. * This can be used to filter the PickResults * before the more computationally intensive intersection processing. * For example, * the application can do a BOUNDS pick and then selectively inquire * intersections on some of the PickResults. This will save the effort of * doing intersection computation on the other PickResults. * However, inquiring the intersections from a GEOMETRY pick will make * the intersection computation happen twice, use GEOMETRY_INTERSECT_INFO * if you want to inquire the intersection information on all the PickResults. *

* When using pickAllSorted or pickClosest methods, the picks * will be sorted by the distance from the start point of the pick shape to * the intersection point. *

* Morph nodes cannot be picked using the displayed geometry in * GEOMETRY_INTERSECT_INFO mode due to limitations in the current Java3D core * API (the current * geometry of the the Morph cannot be inquired). Instead they are picked * using * the geometry at index 0 in the Morph, this limitation may be eliminated in a * future release of Java3D. *

* If the pick shape is a PickBounds, the pick result will contain only the * scene graph path, even if the mode is GEOMETRY_INTERSECT_INFO. */ public class PickTool { /* OPEN ISSUES: -- pickClosest() and pickAllSorted() using GEOMETRY and a non-PickRay shape => unsorted picking. -- Need to implement Morph geometry index 0 picking. */ private final boolean debug = true; protected boolean userDefineShape = false; PickShape pickShape; /** Used to store the BranchGroup used for picking */ BranchGroup pickRootBG = null; /** Used to store the Locale used for picking */ Locale pickRootL = null; /** Used to store a reference point used in determining how "close" points are. */ Point3d start = null; /* pick mode, one of BOUNDS, GEOMETRY, etc. */ int mode = BOUNDS; /** Use this mode to pick by bounds and get basic information on the pick. */ public static final int BOUNDS = 0x200; /** Use this mode to pick by geometry and get basic information on the pick. */ public static final int GEOMETRY = 0x100; /** Use this mode to pick by geometry and save information about the intersections (intersected primitive, intersection point and closest vertex). */ public static final int GEOMETRY_INTERSECT_INFO = 0x400; // Flags for the setCapabilities() method /** * Flag to pass to setCapabilities(Node, int) to set * the Node's capabilities to allow intersection tests, but not * inquire information about the intersections (use for GEOMETRY mode). * @see PickTool#setCapabilities */ public static final int INTERSECT_TEST = 0x1001; /** * Flag to pass to setCapabilities(Node, int) to set * the Node's capabilities to allow inquiry of the intersection * coordinate information. * @see PickTool#setCapabilities */ public static final int INTERSECT_COORD = 0x1002; /** * Flag to pass to setCapabilities(Node, int) to set * the Node's capabilities to allow inquiry of all intersection * information. * @see PickTool#setCapabilities */ public static final int INTERSECT_FULL = 0x1004; /* ============================ METHODS ============================ */ /** * Constructor with BranchGroup to be picked. */ public PickTool (BranchGroup b) { pickRootBG = b; } /** Returns the BranchGroup to be picked if the tool was initialized with a BranchGroup, null otherwise. */ public BranchGroup getBranchGroup() { return pickRootBG; } /** * Constructor with the Locale to be picked. */ public PickTool (Locale l) { pickRootL = l; } /** * Returns the Locale to be picked if the tool was initialized with * a Locale, null otherwise. */ public Locale getLocale () { return pickRootL; } /** * @deprecated This method does nothing other than return its * input parameter. */ public Locale setBranchGroup (Locale l) { return l; } /** * Sets the capabilities on the Node and it's components to allow * picking at the specified detail level. *

* Note that by default all com.sun.j3d.utils.geometry.Primitive * objects with the same parameters share their geometry (e.g., * you can have 50 spheres in your scene, but the geometry is * stored only once). Therefore the capabilities of the geometry * are also shared, and once a shared node is live, the * capabilities cannot be changed. To assign capabilities to * Primitives with the same parameters, either set the * capabilities before the primitive is set live, or specify the * Primitive.GEOMETRY_NOT_SHARED constructor parameter when * creating the primitive. * @param node The node to modify * @param level The capability level, must be one of INTERSECT_TEST, * INTERSECT_COORD or INTERSECT_FULL * @throws IllegalArgumentException if Node is not a Shape3D or Morph or * if the flag value is not valid. * @throws javax.media.j3d.RestrictedAccessException if the node is part * of a live or compiled scene graph. */ static public void setCapabilities(Node node, int level) { if (node instanceof Morph) { Morph morph = (Morph) node; switch (level) { case INTERSECT_FULL: /* intentional fallthrough */ case INTERSECT_COORD: morph.setCapability(Morph.ALLOW_GEOMETRY_ARRAY_READ); /* intentional fallthrough */ case INTERSECT_TEST: break; default: throw new IllegalArgumentException("Improper level"); } double[] weights = morph.getWeights(); for (int i = 0; i < weights.length; i++) { GeometryArray ga = morph.getGeometryArray(i); setCapabilities(ga, level); } } else if (node instanceof Shape3D) { Shape3D shape = (Shape3D) node; switch (level) { case INTERSECT_FULL: /* intentional fallthrough */ case INTERSECT_COORD: shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ); /* intentional fallthrough */ case INTERSECT_TEST: break; default: throw new IllegalArgumentException("Improper level"); } for (int i = 0; i < shape.numGeometries(); i++) { Geometry geo = shape.getGeometry(i); if (geo instanceof GeometryArray) { setCapabilities((GeometryArray)geo, level); } else if (geo instanceof CompressedGeometry) { setCapabilities((CompressedGeometry)geo, level); } } } else { throw new IllegalArgumentException("Improper node type"); } } static private void setCapabilities(GeometryArray ga, int level) { switch (level) { case INTERSECT_FULL: ga.setCapability(GeometryArray.ALLOW_COLOR_READ); ga.setCapability(GeometryArray.ALLOW_NORMAL_READ); ga.setCapability(GeometryArray.ALLOW_TEXCOORD_READ); /* intential fallthrough */ case INTERSECT_COORD: ga.setCapability(GeometryArray.ALLOW_COUNT_READ); ga.setCapability(GeometryArray.ALLOW_FORMAT_READ); ga.setCapability(GeometryArray.ALLOW_COORDINATE_READ); /* intential fallthrough */ case INTERSECT_TEST: ga.setCapability(GeometryArray.ALLOW_INTERSECT); break; } if (ga instanceof IndexedGeometryArray) { setCapabilities((IndexedGeometryArray)ga, level); } } static private void setCapabilities(IndexedGeometryArray iga, int level) { switch (level) { case INTERSECT_FULL: iga.setCapability(IndexedGeometryArray.ALLOW_COLOR_INDEX_READ); iga.setCapability(IndexedGeometryArray.ALLOW_NORMAL_INDEX_READ); iga.setCapability(IndexedGeometryArray.ALLOW_TEXCOORD_INDEX_READ); /* intential fallthrough */ case INTERSECT_COORD: iga.setCapability(IndexedGeometryArray.ALLOW_COORDINATE_INDEX_READ); /* intential fallthrough */ case INTERSECT_TEST: break; } } static private void setCapabilities(CompressedGeometry cg, int level) { switch (level) { case INTERSECT_FULL: /* intential fallthrough */ case INTERSECT_COORD: cg.setCapability(CompressedGeometry.ALLOW_GEOMETRY_READ); /* intential fallthrough */ case INTERSECT_TEST: cg.setCapability(CompressedGeometry.ALLOW_INTERSECT); break; } } // Methods used to define the pick shape /** Sets the pick shape to a user-provided PickShape object * @param ps The pick shape to pick against. * @param startPt The start point to use for distance calculations */ public void setShape (PickShape ps, Point3d startPt) { this.pickShape = ps; this.start = startPt; userDefineShape = (ps != null); } /** Sets the pick shape to use a user-provided Bounds object * @param bounds The bounds to pick against. * @param startPt The start point to use for distance calculations */ public void setShapeBounds (Bounds bounds, Point3d startPt) { this.pickShape = (PickShape) new PickBounds (bounds); this.start = startPt; userDefineShape = true; } /** Sets the picking detail mode. The default is BOUNDS. * @param mode One of BOUNDS, GEOMETRY, GEOMETRY_INTERSECT_INFO, or * @exception IllegalArgumentException if mode is not a legal value */ public void setMode (int mode) { if ((mode != BOUNDS) && (mode != GEOMETRY) && (mode != GEOMETRY_INTERSECT_INFO)) { throw new java.lang.IllegalArgumentException(); } this.mode = mode; } /** Gets the picking detail mode. */ public int getMode () { return mode; } /** Sets the pick shape to a PickRay. * @param start The start of the ray * @param dir The direction of the ray */ public void setShapeRay (Point3d start, Vector3d dir) { this.pickShape = (PickShape) new PickRay (start, dir); this.start = start; userDefineShape = true; } /** Sets the pick shape to a PickSegment. @param start The start of the segment p @param end The end of the segment */ public void setShapeSegment (Point3d start, Point3d end) { this.pickShape = (PickShape) new PickSegment (start, end); this.start = start; userDefineShape = true; } /** Sets the pick shape to a capped PickCylinder * @param start The start of axis of the cylinder * @param end The end of the axis of the cylinder * @param radius The radius of the cylinder */ public void setShapeCylinderSegment (Point3d start, Point3d end, double radius) { this.pickShape = (PickShape) new PickCylinderSegment (start, end, radius); this.start = start; userDefineShape = true; } /** Sets the pick shape to an infinite PickCylinder. * @param start The start of axis of the cylinder * @param dir The direction of the axis of the cylinder * @param radius The radius of the cylinder */ public void setShapeCylinderRay (Point3d start, Vector3d dir, double radius) { this.pickShape = (PickShape) new PickCylinderRay (start, dir, radius); this.start = start; userDefineShape = true; } /** Sets the pick shape to a capped PickCone * @param start The start of axis of the cone * @param end The end of the axis of the cone * @param angle The angle of the cone */ public void setShapeConeSegment (Point3d start, Point3d end, double angle) { this.pickShape = (PickShape) new PickConeSegment (start, end, angle); this.start = start; userDefineShape = true; } /** Sets the pick shape to an infinite PickCone. * @param start The start of axis of the cone * @param dir The direction of the axis of the cone * @param angle The angle of the cone */ public void setShapeConeRay (Point3d start, Vector3d dir, double angle) { this.pickShape = (PickShape) new PickConeRay (start, dir, angle); this.start = start; userDefineShape = true; } /** Returns the PickShape for this object. */ public PickShape getPickShape () { return pickShape; } /** Returns the start postion used for distance measurement. */ public Point3d getStartPosition () { return start; } /** Selects all the nodes that intersect the PickShape. @return An array of PickResult objects which will contain information about the picked instances. null if nothing was picked. */ public PickResult[] pickAll () { PickResult[] retval = null; switch (mode) { case BOUNDS: retval = pickAll(pickShape); break; case GEOMETRY: retval = pickGeomAll(pickShape); break; case GEOMETRY_INTERSECT_INFO: retval = pickGeomAllIntersect(pickShape); break; default: throw new RuntimeException("Invalid pick mode"); } return retval; } /** Select one of the nodes that intersect the PickShape @return A PickResult object which will contain information about the picked instance. null if nothing was picked. */ public PickResult pickAny () { PickResult retval = null; switch (mode) { case BOUNDS: retval = pickAny(pickShape); break; case GEOMETRY: retval = pickGeomAny(pickShape); break; case GEOMETRY_INTERSECT_INFO: retval = pickGeomAnyIntersect(pickShape); break; default: throw new RuntimeException("Invalid pick mode"); } return retval; } /** Select all the nodes that intersect the PickShape, returned sorted. The "closest" object will be returned first. See note above to see how "closest" is determined.

@return An array of PickResult objects which will contain information about the picked instances. null if nothing was picked. */ public PickResult[] pickAllSorted () { PickResult[] retval = null; // System.out.println ("PickTool.pickAllSorted."); switch (mode) { case BOUNDS: // System.out.println ("PickTool.pickAllSorted : Bounds"); retval = pickAllSorted(pickShape); break; case GEOMETRY: // System.out.println ("PickTool.pickAllSorted : Geometry"); // TODO - BugId 4351050. // pickGeomAllSorted is broken for PickCone and PickCylinder : // The current Shape3D.intersect() API doesn't return the distance for // PickCone and PickCylinder. // 2) TODO - BugId 4351579. // pickGeomClosest is broken for multi-geometry Shape3D node : // The current Shape3D.intersect() API does't return the closest intersected // geometry. retval = pickGeomAllSorted(pickShape); break; case GEOMETRY_INTERSECT_INFO: // System.out.println ("PickShape " + pickShape); // System.out.println ("PickTool.pickAllSorted : GEOMETRY_INTERSECT_INFO"); retval = pickGeomAllSortedIntersect(pickShape); break; default: throw new RuntimeException("Invalid pick mode"); } return retval; } /** Select the closest node that intersects the PickShape. See note above to see how "closest" is determined.

@return A PickResult object which will contain information about the picked instance. null if nothing was picked. */ public PickResult pickClosest () { PickResult retval = null; switch (mode) { case BOUNDS: retval = pickClosest(pickShape); break; case GEOMETRY: // System.out.println("pickCloset -- Geometry based picking"); // 1) TODO - BugId 4351050. // pickGeomClosest is broken for PickCone and PickCylinder : // The current Shape3D.intersect() API doesn't return the distance for // PickCone and PickCylinder. // 2) TODO - BugId 4351579. // pickGeomClosest is broken for multi-geometry Shape3D node : // The current Shape3D.intersect() API does't return the closest intersected // geometry. retval = pickGeomClosest(pickShape); break; case GEOMETRY_INTERSECT_INFO: // System.out.println ("PickShape " + pickShape); // System.out.println ("PickTool.pickClosest : GEOMETRY_INTERSECT_INFO"); retval = pickGeomClosestIntersect(pickShape); break; default: throw new RuntimeException("Invalid pick mode"); } return retval; } private PickResult[] pickAll (PickShape pickShape) { PickResult[] pr = null; SceneGraphPath[] sgp = null; if (pickRootBG != null) { sgp = pickRootBG.pickAll (pickShape); } else if (pickRootL != null) { sgp = pickRootL.pickAll (pickShape); } if (sgp == null) return null; // no match // Create PickResult array pr = new PickResult [sgp.length]; for (int i=0;i 1) { return sortPickResults (npr, distance); } else { // Don't have to sort if only one item return npr; } } private PickResult pickGeomAny (PickShape pickShape) { Node obj = null; int i; SceneGraphPath[] sgpa = null; if (pickRootBG != null) { sgpa = pickRootBG.pickAll(pickShape); } else if (pickRootL != null) { sgpa = pickRootL.pickAll(pickShape); } if (sgpa == null) return null; // no match for(i=0; i 0) { found[i] = true; cnt++; } } if (cnt == 0) return null; // no match PickResult[] newpr = new PickResult[cnt]; cnt = 0; // reset for reuse. for(i=0; i 0) { // System.out.println ("numIntersection " + numIntersection); found[i] = true; double minDist; double tempDist; int minIndex; boolean needToSwap = false; minDist = pr[i].getIntersection(0).getDistance(); minIndex = 0; for(int j=1; j tempDist) { minDist = tempDist; minIndex = j; needToSwap = true; } } //Swap if necc. if(needToSwap) { // System.out.println ("Swap is needed"); PickIntersection pi0 = pr[i].getIntersection(0); PickIntersection piMin = pr[i].getIntersection(minIndex); pr[i].intersections.set(0, piMin); pr[i].intersections.set(minIndex, pi0); } distArr[i] = pr[i].getIntersection(0).getDistance(); cnt++; } } // System.out.println ("PickTool.pickGeomAllSortedIntersect: geometry intersect check " // + " cnt " + cnt); if (cnt == 0) return null; // no match PickResult[] npr = new PickResult[cnt]; double[] distance = new double[cnt]; cnt = 0; // reset for reuse. for(i=0; i 1) { return sortPickResults (npr, distance); } else { // Don't have to sort if only one item return npr; } } private PickResult pickGeomClosestIntersect(PickShape pickShape) { PickResult[] pr = pickGeomAllSortedIntersect(pickShape); /* System.out.println ("PickTool.pickGeomClosestIntersect: pr.length " + pr.length); for(int i=0;i 0) { return pr; } } return null; } // ================================================================ // Sort Methods // ================================================================ private PickResult[] sortPickResults (PickResult[] pr, double[] dist) { int[] pos = new int [pr.length]; PickResult[] prsorted = new PickResult [pr.length]; // Initialize position array for (int i=0; i