/* * Copyright 2005-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. * */ package javax.media.j3d; import java.util.ArrayList; import java.util.Vector; import javax.vecmath.Point3d; import javax.vecmath.Point4d; /** * The PickInfo object contains the computed information about a pick hit. * The detailed information about each intersection of the PickShape * with the picked Node can be inquired. The PickInfo object is constructed with * basic information and more detailed information can be generated by setting the * appropriate mask to the flag argument in the pick methods of BranchGroup and * Locale. *

* * @see Locale * @see BranchGroup * * @since Java 3D 1.4 */ public class PickInfo extends Object { static final int PICK_ALL = 1; static final int PICK_ANY = 2; /* The SceneGraphPath of the intersected pickable item */ private SceneGraphPath sgp; /* The intersected pickable node object */ private Node node; /* A copy of LocalToVworld transform of the pickable node */ private Transform3D l2vw; /* The closest intersection point */ private Point3d closestIntersectionPoint; /* Distance between start point of pickShape and closest intersection point */ private double closestDistance; /* An array to store intersection results */ private IntersectionInfo[] intersectionInfoArr; /* The following references are for internal geometry computation use only */ private ArrayList intersectionInfoList = new ArrayList(); private boolean intersectionInfoListSorted = false; private Transform3D l2vwRef; private Node nodeRef; /** * Specifies a Pick using the bounds of the pickable nodes. */ public static final int PICK_BOUNDS = 1; /** * Specifies a Pick using the geometry of the pickable nodes. */ public static final int PICK_GEOMETRY = 2; /** * Specifies that this PickInfo returns the computed SceneGraphPath object. */ public static final int SCENEGRAPHPATH = 0x01; /** * Specifies that this PickInfo returns the computed intersected Node object. */ public static final int NODE = 0x02; /** * Specifies that this PickInfo returns the computed local to vworld transform. */ public static final int LOCAL_TO_VWORLD = 0x04; /** * Specifies that this PickInfo returns the closest intersection point. */ public static final int CLOSEST_INTERSECTION_POINT = 0x08; /** * Specifies that this PickInfo returns the closest intersection distance. */ public static final int CLOSEST_DISTANCE = 0x10; /** * Specifies that this PickInfo returns only the closest intersection * geometry information. */ public static final int CLOSEST_GEOM_INFO = 0x20; /** * Specifies that this PickInfo returns all the closest intersection * geometry informations. */ public static final int ALL_GEOM_INFO = 0x40; /** PickInfo Constructor */ PickInfo() { } void setSceneGraphPath(SceneGraphPath sgp) { this.sgp = sgp; } void setNode(Node node) { this.node = node; } void setLocalToVWorld(Transform3D l2vw) { this.l2vw = l2vw; } void setClosestIntersectionPoint(Point3d cIPt) { this.closestIntersectionPoint = cIPt; } void setClosestDistance(double cDist) { this.closestDistance = cDist; } void setLocalToVWorldRef(Transform3D l2vwRef) { this.l2vwRef = l2vwRef; } void setNodeRef(Node nodeRef) { this.nodeRef = nodeRef; } IntersectionInfo createIntersectionInfo() { return new IntersectionInfo(); } void insertIntersectionInfo(IntersectionInfo iInfo) { intersectionInfoList.add(iInfo); intersectionInfoListSorted = false; } void sortIntersectionInfoArray(IntersectionInfo[] iInfoArr) { class Sort { IntersectionInfo iInfoArr[]; Sort(IntersectionInfo[] iInfoArr) { // System.err.println("Sort IntersectionInfo ..."); this.iInfoArr = iInfoArr; } void sorting() { if (iInfoArr.length < 7) { // System.err.println(" -- insertSort."); insertSort(); } else { // System.err.println(" -- quicksort."); quicksort(0, iInfoArr.length-1); } } // Insertion sort on smallest arrays final void insertSort() { for (int i=0; i0 && (iInfoArr[j-1].distance > iInfoArr[j].distance); j--) { IntersectionInfo iInfo = iInfoArr[j]; iInfoArr[j] = iInfoArr[j-1]; iInfoArr[j-1] = iInfo; } } } final void quicksort( int l, int r ) { int i = l; int j = r; double k = iInfoArr[(l+r) / 2].distance; do { while (iInfoArr[i].distance0 && (pIArr[j-1].closestDistance > pIArr[j].closestDistance); j--) { PickInfo pI = pIArr[j]; pIArr[j] = pIArr[j-1]; pIArr[j-1] = pI; } } } final void quicksort( int l, int r ) { int i = l; int j = r; double k = pIArr[(l+r) / 2].closestDistance; do { while (pIArr[i].closestDistanceN IntersectionInfo objects containing all intersections of * the picked node in sorted order if flag is to set ALL_GEOM_INFO, or null if neither * bit is set. * @see Locale * @see BranchGroup */ public IntersectionInfo[] getIntersectionInfos() { if (intersectionInfoListSorted == false) { intersectionInfoArr = new IntersectionInfo[intersectionInfoList.size()]; intersectionInfoArr = intersectionInfoList.toArray(intersectionInfoArr); sortIntersectionInfoArray(intersectionInfoArr); } return intersectionInfoArr; } /** * Search the path from nodeR up to Locale. * Return the search path as ArrayList if found. * Note that the locale will not insert into path. */ static ArrayList initSceneGraphPath(NodeRetained nodeR) { ArrayList path = new ArrayList(5); do { if (nodeR.source.getCapability(Node.ENABLE_PICK_REPORTING)) { path.add(nodeR); } nodeR = nodeR.parent; } while (nodeR != null); // reach Locale return path; } static private Node[] createPath(NodeRetained srcNode, BranchGroupRetained bgRetained, GeometryAtom geomAtom, ArrayList initpath) { ArrayList path = retrievePath(srcNode, bgRetained, geomAtom.source.key); assert(path != null); return mergePath(path, initpath); } /** * Return true if bg is inside cachedBG or bg is null */ static private boolean inside(BranchGroupRetained bgArr[], BranchGroupRetained bg) { if ((bg == null) || (bgArr == null)) { return true; } for (int i=0; i < bgArr.length; i++) { if (bgArr[i] == bg) { return true; } } return false; } /** * search the full path from the bottom of the scene graph - * startNode, up to the Locale if endNode is null. * If endNode is not null, the path is found up to, but not * including, endNode or return null if endNode not hit * during the search. */ static private ArrayList retrievePath(NodeRetained startNode, NodeRetained endNode, HashKey key) { ArrayList path = new ArrayList(5); NodeRetained nodeR = startNode; if (nodeR.inSharedGroup) { // getlastNodeId() will destroy this key key = new HashKey(key); } do { if (nodeR == endNode) { // we found it ! return path; } if (nodeR.source.getCapability(Node.ENABLE_PICK_REPORTING)) { path.add(nodeR); } if (nodeR instanceof SharedGroupRetained) { // retrieve the last node ID String nodeId = key.getLastNodeId(); Vector parents = ((SharedGroupRetained)nodeR).parents; int sz = parents.size(); NodeRetained prevNodeR = nodeR; for(int i=0; i< sz; i++) { NodeRetained linkR = parents.get(i); if (linkR.nodeId.equals(nodeId)) { nodeR = linkR; // Need to add Link to the path report path.add(nodeR); // since !(endNode instanceof Link), we // can skip the check (nodeR == endNode) and // proceed to parent of link below break; } } if (nodeR == prevNodeR) { // branch is already detach return null; } } nodeR = nodeR.parent; } while (nodeR != null); // reach Locale if (endNode == null) { // user call pickxxx(Locale locale, PickShape shape) return path; } // user call pickxxx(BranchGroup endNode, PickShape shape) // if locale is reached and endNode not hit, this is not // the path user want to select return null; } /** * copy p1, (follow by) p2 into a new array, p2 can be null * The path is then reverse before return. */ static private Node[] mergePath(ArrayList p1, ArrayList p2) { int s = p1.size(); int len; int i; int l; if (p2 == null) { len = s; } else { len = s + p2.size(); } Node nodes[] = new Node[len]; l = len-1; for (i=0; i < s; i++) { nodes[l-i] = (Node)p1.get(i).source; } for (int j=0; i< len; i++, j++) { nodes[l-i] = (Node)p2.get(j).source; } return nodes; } /** * Sort the GeometryAtoms distance from shape in ascending order * geomAtoms.length must be >= 1 */ static void sortGeomAtoms(GeometryAtom geomAtoms[], PickShape shape) { final double distance[] = new double[geomAtoms.length]; Point4d pickPos = new Point4d(); for (int i=0; i < geomAtoms.length; i++) { shape.intersect(geomAtoms[i].source.vwcBounds, pickPos); distance[i] = pickPos.w; } class Sort { GeometryAtom atoms[]; Sort(GeometryAtom[] atoms) { this.atoms = atoms; } void sorting() { if (atoms.length < 7) { insertSort(); } else { quicksort(0, atoms.length-1); } } // Insertion sort on smallest arrays final void insertSort() { for (int i=0; i0 && (distance[j-1] > distance[j]); j--) { double t = distance[j]; distance[j] = distance[j-1]; distance[j-1] = t; GeometryAtom p = atoms[j]; atoms[j] = atoms[j-1]; atoms[j-1] = p; } } } final void quicksort( int l, int r ) { int i = l; int j = r; double k = distance[(l+r) / 2]; do { while (distance[i] getPickInfos(ArrayList initpath, BranchGroupRetained bgRetained, GeometryAtom geomAtoms[], Locale locale, int flags, int pickType) { ArrayList pickInfoList = new ArrayList(5); NodeRetained srcNode; ArrayList text3dList = null; if ((geomAtoms == null) || (geomAtoms.length == 0)) { return null; } for (int i=0; i < geomAtoms.length; i++) { assert((geomAtoms[i] != null) && (geomAtoms[i].source != null)); PickInfo pickInfo = null; Shape3DRetained shape = geomAtoms[i].source; srcNode = shape.sourceNode; // Fix to Issue 274 : NPE With Simultaneous View and Content Side PickingBehaviors // This node isn't under the selected BG for pick operation. if (!inside(shape.branchGroupPath,bgRetained)) { continue; } if (srcNode == null) { // The node is just detach from branch so sourceNode = null continue; } // Special case, for Text3DRetained, it is possible // for different geomAtoms pointing to the same // source Text3DRetained. So we need to combine // those cases and report only once. if (srcNode instanceof Shape3DRetained) { Shape3DRetained s3dR = (Shape3DRetained) srcNode; GeometryRetained geomR = null; for(int cnt=0; cnt pickInfoList = null; if (node instanceof Locale) { locale = (Locale) node; } else if ( node instanceof BranchGroupRetained) { bgRetained = (BranchGroupRetained) node; locale = bgRetained.locale; } synchronized (locale.universe.sceneGraphLock) { ArrayList initPath = null; if ( bgRetained != null) { initPath = initSceneGraphPath(bgRetained); } pickInfoList = getPickInfos(initPath, bgRetained, geomAtoms, locale, flags, pickType); } // We're done with PICK_BOUNDS case, but there is still more work for PICK_GEOMETRY case. if((mode == PICK_GEOMETRY) && (pickInfoList != null) && ((pickInfoListSize = pickInfoList.size()) > 0)) { //System.err.println("PickInfo.pick() - In geometry case : pickInfoList.size() is " + pickInfoListSize); Node pickNode = null; // Order is impt. Need to do in reverse order. for(int i = pickInfoListSize - 1; i >= 0; i--) { PickInfo pickInfo = pickInfoList.get(i); pickNode = pickInfo.getNode(); if( pickNode == null) { // Use the piggy reference from getPickInfos() pickNode = pickInfo.getNodeRef(); } if (pickNode instanceof Shape3D) { /* * @exception CapabilityNotSetException if the mode is * PICK_GEOMETRY and the Geometry.ALLOW_INTERSECT capability bit * is not set in any Geometry objects referred to by any shape * node whose bounds intersects the PickShape. * * @exception CapabilityNotSetException if flags contains any of * CLOSEST_INTERSECTION_POINT, CLOSEST_DISTANCE, CLOSEST_GEOM_INFO * or ALL_GEOM_INFO, and the capability bits that control reading of * coordinate data are not set in any GeometryArray object referred * to by any shape node that intersects the PickShape. * The capability bits that must be set to avoid this exception are * as follows : * * By-copy geometry : GeometryArray.ALLOW_COORDINATE_READ * By-reference geometry : GeometryArray.ALLOW_REF_DATA_READ * Indexed geometry : IndexedGeometryArray.ALLOW_COORDINATE_INDEX_READ * (in addition to one of the above) * */ if (!pickNode.getCapability(Shape3D.ALLOW_GEOMETRY_READ)) { throw new CapabilityNotSetException(J3dI18N.getString("PickInfo0")); } for (int j = 0; j < ((Shape3D)pickNode).numGeometries(); j++) { Geometry geo = ((Shape3D)pickNode).getGeometry(j); if(geo == null) { continue; } if(!geo.getCapability(Geometry.ALLOW_INTERSECT)) { throw new CapabilityNotSetException(J3dI18N.getString("PickInfo1")); } if (geo instanceof GeometryArray) { if(!geo.getCapability(GeometryArray.ALLOW_COORDINATE_READ)) throw new CapabilityNotSetException(J3dI18N.getString("PickInfo2")); if(!geo.getCapability(GeometryArray.ALLOW_COUNT_READ)) throw new CapabilityNotSetException(J3dI18N.getString("PickInfo3")); if(!geo.getCapability(GeometryArray.ALLOW_FORMAT_READ)) throw new CapabilityNotSetException(J3dI18N.getString("PickInfo4")); if (geo instanceof IndexedGeometryArray) { if(!geo.getCapability(IndexedGeometryArray.ALLOW_COORDINATE_INDEX_READ)) throw new CapabilityNotSetException(J3dI18N.getString("PickInfo5")); } } else if (geo instanceof CompressedGeometry) { if(!geo.getCapability(CompressedGeometry.ALLOW_GEOMETRY_READ)) throw new CapabilityNotSetException(J3dI18N.getString("PickInfo0")); } } if (((Shape3DRetained)(pickNode.retained)).intersect(pickInfo, pickShape, flags) == false) { // System.err.println(" ---- geom " + i + " not intersected"); pickInfoList.remove(i); } else if(pickType == PICK_ANY) { pickInfoArr = new PickInfo[1]; pickInfoArr[0] = pickInfo; return pickInfoArr; } } else if (pickNode instanceof Morph) { /* * @exception CapabilityNotSetException if the mode is * PICK_GEOMETRY and the Geometry.ALLOW_INTERSECT capability bit * is not set in any Geometry objects referred to by any shape * node whose bounds intersects the PickShape. * * @exception CapabilityNotSetException if flags contains any of * CLOSEST_INTERSECTION_POINT, CLOSEST_DISTANCE, CLOSEST_GEOM_INFO * or ALL_GEOM_INFO, and the capability bits that control reading of * coordinate data are not set in any GeometryArray object referred * to by any shape node that intersects the PickShape. * The capability bits that must be set to avoid this exception are * as follows : * * By-copy geometry : GeometryArray.ALLOW_COORDINATE_READ * By-reference geometry : GeometryArray.ALLOW_REF_DATA_READ * Indexed geometry : IndexedGeometryArray.ALLOW_COORDINATE_INDEX_READ * (in addition to one of the above) * */ if (!pickNode.getCapability(Morph.ALLOW_GEOMETRY_ARRAY_READ)) { throw new CapabilityNotSetException(J3dI18N.getString("PickInfo6")); } int numGeo = ((MorphRetained)(pickNode.retained)).getNumGeometryArrays(); for (int j = 0; j < numGeo; j++) { GeometryArray geo = ((Morph)pickNode).getGeometryArray(j); if(geo == null) { continue; } if(!geo.getCapability(Geometry.ALLOW_INTERSECT)) { throw new CapabilityNotSetException(J3dI18N.getString("PickInfo1")); } if(!geo.getCapability(GeometryArray.ALLOW_COORDINATE_READ)) throw new CapabilityNotSetException(J3dI18N.getString("PickInfo2")); if(!geo.getCapability(GeometryArray.ALLOW_COUNT_READ)) throw new CapabilityNotSetException(J3dI18N.getString("PickInfo3")); if(!geo.getCapability(GeometryArray.ALLOW_FORMAT_READ)) throw new CapabilityNotSetException(J3dI18N.getString("PickInfo4")); if (geo instanceof IndexedGeometryArray) { if(!geo.getCapability(IndexedGeometryArray.ALLOW_COORDINATE_INDEX_READ)) throw new CapabilityNotSetException(J3dI18N.getString("PickInfo5")); } } if (((MorphRetained)(pickNode.retained)).intersect(pickInfo, pickShape, flags) == false) { pickInfoList.remove(i); } else if(pickType == PICK_ANY) { pickInfoArr = new PickInfo[1]; pickInfoArr[0] = pickInfo; return pickInfoArr; } } } } // System.err.println("PickInfo : pickInfoList " + pickInfoList); if ((pickInfoList != null) && (pickInfoList.size() > 0)) { // System.err.println(" --- : pickInfoList.size() " + pickInfoList.size()); // System.err.println(" --- : pickInfoList's sgp " + // ((PickInfo)(pickInfoList.get(0))).getSceneGraphPath()); pickInfoArr = new PickInfo[pickInfoList.size()]; return pickInfoList.toArray(pickInfoArr); } return null; } /** * The IntersectionInfo object holds extra information about an intersection * of a PickShape with a Node as part of a PickInfo. Information such as * the intersected geometry, the intersected point, and the vertex indices * can be inquired. * The local coordinates, normal, color and texture coordiantes of at the * intersection can be computed, if they are present and readable, using the * interpolation weights and vertex indices. *

* If the Shape3D being picked has multiple geometry arrays, the possible arrays * of IntersectionInfo are stored in the PickInfo and referred to by a geometry * index. If the picked geometry is of type, Text3D or CompressGeometry, * getVertexIndices is invalid. If the picked Node is an Morph * object, the geometry used in pick computation is alway at index 0. *

* * @since Java 3D 1.4 */ public class IntersectionInfo extends Object { /* The index to the intersected geometry in the pickable node */ private int geomIndex; /* The reference to the intersected geometry in the pickable object */ private Geometry geom; /* The intersection point */ private Point3d intersectionPoint; /* Distance between start point of pickShape and intersection point */ private double distance; /* The vertex indices of the intersected primitive in the geometry */ private int[] vertexIndices; /* The interpolation weights for each of the verticies of the primitive */ // private float[] weights; Not supported. Should be done in util. package /** IntersectionInfo Constructor */ IntersectionInfo() { } void setGeometryIndex(int geomIndex) { this.geomIndex = geomIndex; } void setGeometry(Geometry geom) { this.geom = geom; } void setIntersectionPoint(Point3d intersectionPoint) { assert(intersectionPoint != null); this.intersectionPoint = new Point3d(intersectionPoint); } void setDistance(double distance) { this.distance = distance; } void setVertexIndices(int[] vertexIndices) { assert(vertexIndices != null); this.vertexIndices = new int[vertexIndices.length]; for(int i=0; i