/* * Copyright 1996-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; /** * The Node class provides an abstract class for all Group and Leaf * Nodes. It provides a common framework for constructing a Java 3D * scene graph, including bounding volumes and parent pointers. */ abstract class NodeRetained extends SceneGraphObjectRetained implements NnuId { // All the node types in the scene graph static final int BACKGROUND = 1; static final int CLIP = 2; static final int LINEARFOG = 3; static final int EXPONENTIALFOG = 4; static final int AMBIENTLIGHT = 5; static final int DIRECTIONALLIGHT = 6; static final int POINTLIGHT = 7; static final int SPOTLIGHT = 8; static final int LINK = 9; static final int MORPH = 10; static final int SHAPE = 11; static final int BACKGROUNDSOUND = 12; static final int POINTSOUND = 13; static final int CONESOUND = 14; static final int SOUNDSCAPE = 15; static final int VIEWPLATFORM = 16; static final int BEHAVIOR = 17; static final int SWITCH = 18; static final int BRANCHGROUP = 19; static final int ORDEREDGROUP = 20; static final int DECALGROUP = 21; static final int SHAREDGROUP = 22; static final int GROUP = 23; static final int TRANSFORMGROUP = 24; static final int BOUNDINGLEAF = 25; static final int MODELCLIP = 26; static final int ALTERNATEAPPEARANCE= 27; static final int ORIENTEDSHAPE3D = 28; static final int VIEWSPECIFICGROUP = 29; static final int NUMNODES = 29; // traverse flags static final int CONTAINS_VIEWPLATFORM = 0x1; /** * The universe that we are in */ VirtualUniverse universe = null; /** * The locale that this node is attatched to. This is only non-null * if this instance is directly linked into a locale. */ Locale locale = null; /** * The node's parent. */ NodeRetained parent = null; /** * The node's internal identifier. */ String nodeId = null; /** * An int that represents the nodes type. Used for quick if tests * in the traverser. */ int nodeType; // This keeps track of how many times this Node is refernced, refCount > 1 // if node is in a shared group int refCount = 0; /** * This is the index for the child, as seen by its parent. */ int childIndex = -1; /** * This boolean is true when the node is in a sharedGroup */ boolean inSharedGroup = false; /** * This indicates if the node is pickable. If this node is not * pickable then neither are any children */ boolean pickable = true; /** * The collidable setting; see getCollidable and setCollidable. */ boolean collidable = true; // A list of localToVworld transforms. If inSharedGroup is false, // then only localToVworld[0][] is valid. // Note: this contains reference to the actual transforms in the // TransformGroupRetained Transform3D localToVworld[][] = null; int localToVworldIndex[][] = null; static final int LAST_LOCAL_TO_VWORLD = 0; static final int CURRENT_LOCAL_TO_VWORLD = 1; // A parallel array to localToVworld. This is the keys for // localToVworld transforms in shared groups. HashKey localToVworldKeys[] = null; /** * This boolean is true when the geometric bounds for the node is * automatically updated */ boolean boundsAutoCompute = true; // "effective" bounds in local coordinate if boundsAutoCompute == F, // used for internal operations, not used if boundsAutoCompute == T Bounds localBounds; // Bounds set by the API Bounds apiBounds; protected Bounds cachedBounds=null; // Cached auto compute bounds, could we use localBounds ? protected boolean validCachedBounds = false; // Fix to Issue 514 /** * Each element, p, of branchGroupPaths is a list of BranchGroup from * root of the tree to this. * For BranchGroup under a non-shared group this size of * branchGroupPaths is always 1. Otherwise, the size is equal to * the number of possible paths to reach this node. * This variable is used to cached BranchGroup for fast picking. * For non BranchGroupRetained class this is a reference to * the previous BranchGroupRetained branchGroupPaths. */ ArrayList branchGroupPaths = new ArrayList(1); // background node whose geometry branch contains this node BackgroundRetained geometryBackground = null; // closest parent which is a TransformGroupRetained or sharedGroupRetained GroupRetained parentTransformLink = null; // closest parent which is a SwitchRetained or sharedGroupRetained GroupRetained parentSwitchLink = null; // static transform if a parent transform group is merged during compile. TransformGroupRetained staticTransform = null; // orderedId assigned by OrderedGroup parent Integer orderedId = null; // Id use for quick search. int nnuId; NodeRetained() { // Get a not necessary unique Id. nnuId = NnuIdManager.getId(); localBounds = new BoundingBox((Bounds)null); } @Override public int getId() { return nnuId; } @Override public int equal(NnuId obj) { int keyId = obj.getId(); if(nnuId < keyId) { return -1; } else if(nnuId > keyId) { return 1; } else { // Found it! return 0; } } Bounds getLocalBounds(Bounds bounds) { return (Bounds)bounds.clone(); } /** * Sets the geometric bounds of a node. * @param bounds the bounding object for the node */ void setBounds(Bounds bounds) { apiBounds = bounds; if (source.isLive()) { if (!boundsAutoCompute) { if (bounds != null) { localBounds = getLocalBounds(bounds); if (staticTransform != null) { localBounds.transform(staticTransform.transform); } } else { if(localBounds != null) { localBounds.set((Bounds)null); } else { localBounds = new BoundingBox((Bounds)null); } } } } else { if (bounds != null) { localBounds = getLocalBounds(bounds); if (staticTransform != null) { localBounds.transform(staticTransform.transform); } } else { if(localBounds != null) { localBounds.set((Bounds)null); } else { localBounds = new BoundingBox((Bounds)null); } } } } /** * Gets the bounding object of a node. * @return the node's bounding object */ Bounds getEffectiveBounds() { Bounds b = null; if (localBounds != null && !localBounds.isEmpty()) { b = (Bounds) localBounds.clone(); if (staticTransform != null) { Transform3D invTransform = staticTransform.getInvTransform(); b.transform(invTransform); } } return b; } Bounds getBounds() { return apiBounds; } /** * ONLY needed for SHAPE, MORPH, and LINK node type. * Compute the combine bounds of bounds and its localBounds. */ void computeCombineBounds(Bounds bounds) { // Do nothing except for Group, Shape3D, Morph, and Link node. } /** * Sets the automatic calcuation of geometric bounds of a node. * @param autoCompute is a boolean value indicating if automatic calcuation * of bounds */ void setBoundsAutoCompute(boolean autoCompute) { if (this.boundsAutoCompute==autoCompute) { return; } this.boundsAutoCompute = autoCompute; dirtyBoundsCache(); } /** * Gets the auto Compute flag for the geometric bounds. * @return the node's auto Compute flag for the geometric bounding object */ boolean getBoundsAutoCompute() { return boundsAutoCompute; } /** * Replaces the specified parent by a new parent. * @param parent the new parent */ void setParent(NodeRetained parent) { this.parent = parent; } /** * Returns the parent of the node. * @return the parent. */ NodeRetained getParent() { return parent; } // Transform the input bound by the current LocalToVWorld void transformBounds(SceneGraphPath path, Bounds bound) { if (!((NodeRetained) path.item.retained).inSharedGroup) { bound.transform(getCurrentLocalToVworld()); } else { HashKey key = new HashKey(""); path.getHashKey(key); bound.transform(getCurrentLocalToVworld(key)); } } // Note : key will get modified in this method. private void computeLocalToVworld( NodeRetained caller, NodeRetained nodeR, HashKey key, Transform3D l2Vw) { int i; // To handle localToVworld under a SG. if(nodeR instanceof SharedGroupRetained) { // Get the immediate parent's id and remove last id from key. String nodeId = key.getLastNodeId(); SharedGroupRetained sgRetained = (SharedGroupRetained) nodeR; // Search for the right parent. for(i=0; i= 0) { return localToVworld[i][localToVworldIndex[i][CURRENT_LOCAL_TO_VWORLD]]; } } } return new Transform3D(); } /** * Get the last localToVworld transform for a node */ Transform3D getLastLocalToVworld() { if (localToVworld != null) { return localToVworld[0][localToVworldIndex[0][LAST_LOCAL_TO_VWORLD]]; } else { return new Transform3D(); } } Transform3D getLastLocalToVworld(int index) { return localToVworld[index][localToVworldIndex[index][LAST_LOCAL_TO_VWORLD]]; } Transform3D getLastLocalToVworld(HashKey key) { if (localToVworld != null) { if (!inSharedGroup) { return localToVworld[0][localToVworldIndex[0][LAST_LOCAL_TO_VWORLD]]; } else { int i = key.equals(localToVworldKeys, 0, localToVworldKeys.length); if(i>= 0) { return localToVworld[i][localToVworldIndex[i][LAST_LOCAL_TO_VWORLD]]; } } } return new Transform3D(); } // Do nothing for NodeRetained. void setAuxData(SetLiveState s, int index, int hkIndex) { } void setNodeData(SetLiveState s) { localToVworld = s.localToVworld; localToVworldIndex = s.localToVworldIndex; localToVworldKeys = s.localToVworldKeys; // reference to the last branchGroupPaths branchGroupPaths = s.parentBranchGroupPaths; parentTransformLink = s.parentTransformLink; parentSwitchLink = s.parentSwitchLink; } // set pickable, recursively update cache result void setPickable(boolean pickable) { if (this.pickable == pickable) return; this.pickable = pickable; if (source.isLive()) { synchronized(universe.sceneGraphLock) { boolean pick[]; if (!inSharedGroup) { pick = new boolean[1]; } else { pick = new boolean[localToVworldKeys.length]; } findPickableFlags(pick); updatePickable(localToVworldKeys, pick); } } } void updatePickable(HashKey pickKeys[], boolean pick[]) { for (int i=0; i < pick.length; i++) { if (!pickable) { pick[i] = false; } } } // get pickable boolean getPickable() { return pickable; } // set collidable, recursively update cache result void setCollidable(boolean collidable) { if (this.collidable == collidable) return; this.collidable = collidable; if (source.isLive()) { synchronized(universe.sceneGraphLock) { boolean collide[]; if (!inSharedGroup) { collide = new boolean[1]; } else { collide = new boolean[localToVworldKeys.length]; } findCollidableFlags(collide); updateCollidable(localToVworldKeys, collide); } } } // get collidable boolean getCollidable() { return collidable; } void updateCollidable(HashKey keys[], boolean collide[]) { for (int i=0; i < collide.length; i++) { if (!collidable) { collide[i] = false; } } } /** * For the default, just pass up to parent */ void notifySceneGraphChanged(boolean globalTraverse){} void recombineAbove() {} synchronized void updateLocalToVworld() {} @Override void setLive(SetLiveState s) { int oldrefCount = refCount; doSetLive(s); if (oldrefCount <= 0) super.markAsLive(); } // The default set of setLive actions. @Override void doSetLive(SetLiveState s) { int i; int oldrefCount = refCount; refCount += s.refCount; if(!(locale == null || universe == s.universe)) throw new IllegalSharingException(J3dI18N.getString("NodeRetained3")); if(s.locale == null) System.err.println("NodeRetained.setLive() locale is null"); locale = s.locale; inSharedGroup = s.inSharedGroup; if (oldrefCount <= 0) { if (listIdx == null) { universe = s.universe; } else { // sync with getIdxUsed() if (s.universe != universe) { synchronized (this) { universe = s.universe; incIdxUsed(); } } } } s.universe.numNodes++; // pickable & collidable array have the same length for (i=0; i < s.pickable.length; i++) { if (!pickable) { s.pickable[i] = false; } if (!collidable) { s.collidable[i] = false; } } if (oldrefCount <= 0) super.doSetLive(s); if (inBackgroundGroup) { geometryBackground = s.geometryBackground; } setNodeData(s); } /** * remove the localToVworld transform for this node. */ void removeNodeData(SetLiveState s) { if (refCount <= 0) { localToVworld = null; localToVworldIndex = null; localToVworldKeys = null; // restore to default and avoid calling clear() // that may clear parent reference branchGroupPaths branchGroupPaths = new ArrayList(1); parentTransformLink = null; parentSwitchLink = null; } else { // Set it back to its parent localToVworld data. This is b/c the parent has // changed it localToVworld data arrays. localToVworld = s.localToVworld; localToVworldIndex = s.localToVworldIndex; localToVworldKeys = s.localToVworldKeys; // Reference of parent branchGroupPaths will not change // no need to reset parentSwitchLink or parentTransformLink // because there are not per path data } } // The default set of clearLive actions void clearLive(SetLiveState s) { refCount-=s.refCount; if (refCount <= 0) { super.clearLive(); // don't remove the nodeId unless there are no more references if (nodeId != null) { universe.nodeIdFreeList.addElement(nodeId); nodeId = null; } } universe.numNodes--; removeNodeData(s); if(refCount <= 0) { locale = null; geometryBackground = null; } } // search up the parent to determine if this node is pickable void findPickableFlags(boolean pick[]) { NodeRetained nodeR = this; if (!inSharedGroup) { pick[0] = true; nodeR = nodeR.parent; while (nodeR != null) { if (!nodeR.pickable) { pick[0] = false; break; } nodeR = nodeR.parent; } } else { HashKey key; for (int i=0; i < pick.length; i++) { nodeR = this; pick[i] = true; key = new HashKey(localToVworldKeys[i]); do { if (nodeR instanceof SharedGroupRetained) { String nodeId = key.getLastNodeId(); Vector parents = ((SharedGroupRetained)nodeR).parents; int sz = parents.size(); NodeRetained prevNodeR = nodeR; for(int j=0; j< sz; j++) { NodeRetained linkR = parents.get(j); if (linkR.nodeId.equals(nodeId)) { nodeR = linkR; break; } } if (prevNodeR == nodeR) { // branch is already detach return; } } else { nodeR = nodeR.parent; } if (nodeR == null) break; if (!nodeR.pickable) { pick[i] = false; break; } } while (true); } } } // search up the parent to determine if this node is collidable void findCollidableFlags(boolean collide[]) { NodeRetained nodeR = this; if (!inSharedGroup) { collide[0] = true; nodeR = nodeR.parent; while (nodeR != null) { if (!nodeR.collidable) { collide[0] = false; break; } nodeR = nodeR.parent; } } else { HashKey key; for (int i=0; i < collide.length; i++) { nodeR = this; collide[i] = true; key = new HashKey(localToVworldKeys[i]); do { if (nodeR instanceof SharedGroupRetained) { String nodeId = key.getLastNodeId(); Vector parents = ((SharedGroupRetained)nodeR).parents; int sz = parents.size(); NodeRetained prevNodeR = nodeR; for(int j=0; j< sz; j++) { NodeRetained linkR = parents.get(j); if (linkR.nodeId.equals(nodeId)) { nodeR = linkR; break; } } if (nodeR == prevNodeR) { return; } } else { nodeR = nodeR.parent; } if (nodeR == null) break; if (!nodeR.collidable) { collide[i] = false; break; } } while (true); } } } void findTransformLevels(int transformLevels[]) { NodeRetained nodeR = this; TransformGroupRetained tg; if (!inSharedGroup) { transformLevels[0] = -1; while (nodeR != null) { if (nodeR.nodeType == NodeRetained.TRANSFORMGROUP) { tg = (TransformGroupRetained)nodeR; transformLevels[0] = tg.transformLevels[0]; break; } nodeR = nodeR.parent; } } else { HashKey key; int i,j; for (i=0; i < transformLevels.length; i++) { nodeR = this; transformLevels[i] = -1; key = new HashKey(localToVworldKeys[i]); do { if (nodeR == null) break; else if (nodeR instanceof SharedGroupRetained) { // note that key is truncated after getLastNodeId String nodeId = key.getLastNodeId(); Vector parents = ((SharedGroupRetained)nodeR).parents; int sz = parents.size(); NodeRetained prevNodeR = nodeR; for (j=0; j< sz; j++) { NodeRetained linkR = parents.get(j); if (linkR.nodeId.equals(nodeId)) { nodeR = linkR; break; } } if (prevNodeR == nodeR) { // branch is already detach return; } } else if (nodeR.nodeType == NodeRetained.TRANSFORMGROUP) { tg = (TransformGroupRetained)nodeR; if (tg.inSharedGroup) { j = key.equals(tg.localToVworldKeys, 0, tg.localToVworldKeys.length); transformLevels[i] = tg.transformLevels[j]; } else { transformLevels[i] = tg.transformLevels[0]; } break; } nodeR = nodeR.parent; } while (true); } } } @Override boolean isStatic() { if (source.getCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ) || source.getCapability(Node.ALLOW_PARENT_READ) || source.getCapability(Node.ENABLE_PICK_REPORTING) || source.getCapability(Node.ENABLE_COLLISION_REPORTING) || source.getCapability(Node.ALLOW_BOUNDS_READ) || source.getCapability(Node.ALLOW_BOUNDS_WRITE) || source.getCapability(Node.ALLOW_PICKABLE_READ) || source.getCapability(Node.ALLOW_PICKABLE_WRITE) || source.getCapability(Node.ALLOW_COLLIDABLE_READ) || source.getCapability(Node.ALLOW_COLLIDABLE_WRITE) || source.getCapability(Node.ALLOW_AUTO_COMPUTE_BOUNDS_READ) || source.getCapability(Node.ALLOW_AUTO_COMPUTE_BOUNDS_WRITE)) { return false; } return true; } @Override void merge(CompileState compState) { staticTransform = compState.staticTransform; if (compState.parentGroup != null) { compState.parentGroup.compiledChildrenList.add(this); } parent = compState.parentGroup; if (staticTransform != null) { mergeTransform(staticTransform); } } @Override void mergeTransform(TransformGroupRetained xform) { if (localBounds != null) { localBounds.transform(xform.transform); } } int[] processViewSpecificInfo(int mode, HashKey k, View v, ArrayList vsgList, int[] keyList, ArrayList leafList) { return keyList; } @Override VirtualUniverse getVirtualUniverse() { return universe; } void searchGeometryAtoms(UnorderList list) {} /** * Make the boundsCache of this node and all its parents dirty */ void dirtyBoundsCache() { // Possible optimisation is to not traverse up the tree // if the cachedBounds==null. However this is not the case // if the node is the child of a SharedGroup if (VirtualUniverse.mc.cacheAutoComputedBounds) { // Issue 514 : NPE in Wonderland : triggered in cached bounds computation validCachedBounds = false; if (parent!=null) { parent.dirtyBoundsCache(); } } } }