javax.media.j3d.WakeupOnCollisionEntry.java Source code

Java tutorial

Introduction

Here is the source code for javax.media.j3d.WakeupOnCollisionEntry.java

Source

/*
 * Copyright 1997-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.Vector;

/**
 * Class specifying a wakeup when the specified object
 * collides with any other object in the scene graph.
 *
 */
public final class WakeupOnCollisionEntry extends WakeupCriterion {

    // different types of WakeupIndexedList that use in GeometryStructure
    static final int COND_IN_GS_LIST = 0;
    static final int COLLIDEENTRY_IN_BS_LIST = 1;

    // total number of different IndexedUnorderedSet types
    static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2;

    /**
     * Use geometry in computing collisions.
     */
    public static final int USE_GEOMETRY = 10;

    /**
     * Use geometric bounds as an approximation in computing collisions.
     */
    public static final int USE_BOUNDS = 11;

    static final int GROUP = NodeRetained.GROUP;
    static final int BOUNDINGLEAF = NodeRetained.BOUNDINGLEAF;
    static final int SHAPE = NodeRetained.SHAPE;
    static final int MORPH = NodeRetained.MORPH;
    static final int ORIENTEDSHAPE3D = NodeRetained.ORIENTEDSHAPE3D;
    static final int BOUND = 0;

    /**
     * Accuracy mode one of USE_GEOMETRY or USE_BOUNDS
     */
    int accuracyMode;

    // Cached the arming Node being used when it is not BOUND
    NodeRetained armingNode;

    // A transformed Bounds of Group/Bounds, use by
    // BOUND, GROUP
    Bounds vwcBounds = null;

    // Use by BoundingLeaf, point to mirror BoundingLeaf
    // transformedRegion under this leaf is used.
    BoundingLeafRetained boundingLeaf = null;

    /**
     * Geometry atoms that this wakeup condition refer to.
     * Only use by SHAPE, MORPH, GROUP, ORIENTEDSHAPE
     */
    UnorderList geometryAtoms = null;

    // one of GROUP, BOUNDINGLEAF, SHAPE, MORPH, BOUND
    int nodeType;

    SceneGraphPath armingPath = null;
    Bounds armingBounds = null;

    // the following two references are set only after a collision
    // has occurred
    Bounds collidingBounds = null;
    SceneGraphPath collidingPath = null;

    /**
     * Constructs a new WakeupOnCollisionEntry criterion with
     * USE_BOUNDS for a speed hint.
     * @param armingPath the path used to <em>arm</em> collision
     * detection
     * @exception IllegalArgumentException if object associated with the
     * SceneGraphPath is other than a Group, Shape3D, Morph, or
     * BoundingLeaf node.
     */
    public WakeupOnCollisionEntry(SceneGraphPath armingPath) {
        this(armingPath, USE_BOUNDS);
    }

    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingPath the path used to <em>arm</em> collision
     * detection
     * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
     * accurately Java 3D will perform collision detection
     * @exception IllegalArgumentException if hint is not one of
     * USE_GEOMETRY or USE_BOUNDS.
     * @exception IllegalArgumentException if object associated with the
     * SceneGraphPath is other than a Group, Shape3D, Morph, or
     * BoundingLeaf node.
     */
    public WakeupOnCollisionEntry(SceneGraphPath armingPath, int speedHint) {
        this(new SceneGraphPath(armingPath), speedHint, null);
    }

    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingNode the Group, Shape, or Morph node used to
     * <em>arm</em> collision detection
     * @exception IllegalArgumentException if object is under a
     * SharedGroup node or object is other than a Group, Shape3D,
     * Morph or BoundingLeaf node.
     */
    public WakeupOnCollisionEntry(Node armingNode) {
        this(armingNode, USE_BOUNDS);
    }

    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingNode the Group, Shape, or Morph node used to
     * <em>arm</em> collision detection
     * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
     * accurately Java 3D will perform collision detection
     * @exception IllegalArgumentException if hint is not one of
     * USE_GEOMETRY or USE_BOUNDS.
     * @exception IllegalArgumentException if object is under a
     * SharedGroup node or object is other than a Group, Shape3D,
     * Morph or BoundingLeaf node.
     */
    public WakeupOnCollisionEntry(Node armingNode, int speedHint) {
        this(new SceneGraphPath(null, armingNode), speedHint, null);
    }

    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingBounds the bounds object used to <em>arm</em> collision
     * detection
     */
    public WakeupOnCollisionEntry(Bounds armingBounds) {
        this(null, USE_BOUNDS, (Bounds) armingBounds.clone());
    }

    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingPath the path used to <em>arm</em> collision
     * detection
     * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
     * accurately Java 3D will perform collision detection
     * @param armingBounds the bounds object used to <em>arm</em> collision
     * detection
     * @exception IllegalArgumentException if hint is not one of
     * USE_GEOMETRY or USE_BOUNDS.
     * @exception IllegalArgumentException if object associated with the
     * SceneGraphPath is other than a Group, Shape3D, Morph, or
     * BoundingLeaf node.
     */
    WakeupOnCollisionEntry(SceneGraphPath armingPath, int speedHint, Bounds armingBounds) {
        if (armingPath != null) {
            this.armingNode = (NodeRetained) armingPath.getObject().retained;
            nodeType = getNodeType(armingNode, armingPath, "WakeupOnCollisionEntry");
            this.armingPath = armingPath;
            validateSpeedHint(speedHint, "WakeupOnCollisionEntry4");
        } else {
            this.armingBounds = armingBounds;
            nodeType = BOUND;
        }
        accuracyMode = speedHint;
        WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES);
    }

    /**
     * Returns the path used in specifying the collision condition.
     * @return the SceneGraphPath object generated when arming this
     * criterion---null implies that a bounds object armed this criteria
     */
    public SceneGraphPath getArmingPath() {
        return (armingPath != null ? new SceneGraphPath(armingPath) : null);
    }

    /**
     * Returns the bounds object used in specifying the collision condition.
     * @return the Bounds object generated when arming this
     * criterion---null implies that a SceneGraphPath armed this criteria
     */
    public Bounds getArmingBounds() {
        return (armingBounds != null ? (Bounds) armingBounds.clone() : null);
    }

    /**
     * Retrieves the path describing the object causing the collision.
     * @return the SceneGraphPath that describes the triggering object.
     * @exception IllegalStateException if not called from within the
     * a behavior's processStimulus method which was awoken by a collision.
     */
    public SceneGraphPath getTriggeringPath() {
        if (behav == null) {
            throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry5"));
        }

        synchronized (behav) {
            if (!behav.inCallback) {
                throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry5"));
            }
        }
        return (collidingPath != null ? new SceneGraphPath(collidingPath) : null);
    }

    /**
     * Retrieves the Bounds object that caused the collision
     * @return the colliding Bounds object.
     * @exception IllegalStateException if not called from within the
     * a behavior's processStimulus method which was awoken by a collision.
     */
    public Bounds getTriggeringBounds() {
        if (behav == null) {
            throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry6"));
        }

        synchronized (behav) {
            if (!behav.inCallback) {
                throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry6"));
            }
        }
        return (collidingBounds != null ? (Bounds) (collidingBounds.clone()) : null);
    }

    /**
     * Node legality checker
     * throw Exception if node is not legal.
     * @return nodeType
     */
    static int getNodeType(NodeRetained armingNode, SceneGraphPath armingPath, String s)
            throws IllegalArgumentException {

        // check if SceneGraphPath is unique
        // Note that graph may not live at this point so we
        // can't use node.inSharedGroup.
        if (!armingPath.validate()) {
            throw new IllegalArgumentException(J3dI18N.getString(s + "7"));
        }

        if (armingNode.inBackgroundGroup) {
            throw new IllegalArgumentException(J3dI18N.getString(s + "1"));
        }

        // This should come before Shape3DRetained check
        if (armingNode instanceof OrientedShape3DRetained) {
            return ORIENTEDSHAPE3D;
        }

        if (armingNode instanceof Shape3DRetained) {
            return SHAPE;
        }

        if (armingNode instanceof MorphRetained) {
            return MORPH;
        }

        if (armingNode instanceof GroupRetained) {
            return GROUP;
        }

        if (armingNode instanceof BoundingLeafRetained) {
            return BOUNDINGLEAF;
        }

        throw new IllegalArgumentException(J3dI18N.getString(s + "0"));
    }

    /**
     * speedHint legality checker
     * throw Exception if speedHint is not legal
     */
    static void validateSpeedHint(int speedHint, String s) throws IllegalArgumentException {
        if ((speedHint != USE_GEOMETRY) && (speedHint != USE_BOUNDS)) {
            throw new IllegalArgumentException(J3dI18N.getString(s));
        }

    }

    /**
     * This is a callback from BehaviorStructure. It is
     * used to add wakeupCondition to behavior structure.
     */
    @Override
    void addBehaviorCondition(BehaviorStructure bs) {

        switch (nodeType) {
        case SHAPE: // Use geometryAtoms[].collisionBounds
        case ORIENTEDSHAPE3D:
            if (!armingNode.source.isLive()) {
                return;
            }
            if (geometryAtoms == null) {
                geometryAtoms = new UnorderList(1, GeometryAtom.class);
            }
            Shape3DRetained shape = (Shape3DRetained) armingNode;
            geometryAtoms.add(Shape3DRetained.getGeomAtom(shape.getMirrorShape(armingPath)));
            break;
        case MORPH: // Use geometryAtoms[].collisionBounds
            if (!armingNode.source.isLive()) {
                return;
            }
            if (geometryAtoms == null) {
                geometryAtoms = new UnorderList(1, GeometryAtom.class);
            }
            MorphRetained morph = (MorphRetained) armingNode;
            geometryAtoms.add(Shape3DRetained.getGeomAtom(morph.getMirrorShape(armingPath)));
            break;
        case BOUNDINGLEAF: // use BoundingLeaf.transformedRegion
            if (!armingNode.source.isLive()) {
                return;
            }
            this.boundingLeaf = ((BoundingLeafRetained) armingNode).mirrorBoundingLeaf;
            break;
        case BOUND: // use this.vwcBounds
            vwcBounds = (Bounds) armingBounds.clone();
            this.armingNode = behav;
            break;
        case GROUP:
            if (!armingNode.source.isLive()) {
                return;
            }
            if (accuracyMode == USE_GEOMETRY) {
                if (geometryAtoms == null) {
                    geometryAtoms = new UnorderList(1, GeometryAtom.class);
                }
                ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms);
            }
            // else use this.vwcBounds
        default:
        }

        behav.universe.geometryStructure.addWakeupOnCollision(this);
    }

    /**
     * This is a callback from BehaviorStructure. It is
     * used to remove wakeupCondition from behavior structure.
     */
    @Override
    void removeBehaviorCondition(BehaviorStructure bs) {
        vwcBounds = null;
        if (geometryAtoms != null) {
            geometryAtoms.clear();
        }
        boundingLeaf = null;
        behav.universe.geometryStructure.removeWakeupOnCollision(this);
    }

    // Set collidingPath & collidingBounds
    void setTarget(BHLeafInterface leaf) {
        SceneGraphPath path;
        Bounds bound;

        if (leaf instanceof GeometryAtom) {
            // Find the triggered Path & Bounds for this geometry Atom
            GeometryAtom geomAtom = (GeometryAtom) leaf;
            Shape3DRetained shape = geomAtom.source;

            path = getSceneGraphPath(shape.sourceNode, shape.key, shape.getCurrentLocalToVworld(0));
            bound = getTriggeringBounds(shape);

        } else {
            // Find the triggered Path & Bounds for this alternative
            // collision target
            GroupRetained group = (GroupRetained) leaf;
            path = getSceneGraphPath(group);
            bound = getTriggeringBounds(group);
        }

        if (path != null) {
            // colliding path may be null when branch detach before
            // user behavior retrieve the previous colliding path
            collidingPath = path;
            collidingBounds = bound;
        }
    }

    // Invoke from GeometryStructure  to update vwcBounds of GROUP
    void updateCollisionBounds(boolean reEvaluateGAs) {
        if (nodeType == GROUP) {
            GroupRetained group = (GroupRetained) armingNode;
            if (group.collisionBound != null) {
                vwcBounds = (Bounds) group.collisionBound.clone();
            } else {
                // this may involve recursive tree traverse if
                // BoundsAutoCompute is true, we can't avoid
                // since the bound under it may change by transform
                vwcBounds = group.getEffectiveBounds();
            }
            group.transformBounds(armingPath, vwcBounds);
        } else if (nodeType == BOUND) {
            vwcBounds.transform(armingBounds, behav.getCurrentLocalToVworld());
        }

        if (reEvaluateGAs && (nodeType == GROUP) && (accuracyMode == USE_GEOMETRY)) {
            geometryAtoms.clear();
            ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms);
        }
    }

    /**
     * Return the TriggeringBounds for node
     */
    static Bounds getTriggeringBounds(Shape3DRetained mirrorShape) {
        NodeRetained node = mirrorShape.sourceNode;

        if (node instanceof Shape3DRetained) {
            Shape3DRetained shape = (Shape3DRetained) node;
            if (shape.collisionBound == null) {
                // TODO: get bounds by copy
                return shape.getEffectiveBounds();
            }
            return shape.collisionBound;
        }

        MorphRetained morph = (MorphRetained) node;
        if (morph.collisionBound == null) {
            // TODO: get bounds by copy
            return morph.getEffectiveBounds();
        }
        return morph.collisionBound;
    }

    /**
     * Return the TriggeringBounds for node
     */
    static Bounds getTriggeringBounds(GroupRetained group) {
        if (group.collisionBound == null) {
            // TODO: get bounds by copy
            return group.getEffectiveBounds();
        }
        return group.collisionBound;
    }

    static SceneGraphPath getSceneGraphPath(GroupRetained group) {
        // Find the transform base on the key
        Transform3D transform = null;
        GroupRetained srcGroup = group.sourceNode;

        synchronized (srcGroup.universe.sceneGraphLock) {
            if (group.key == null) {
                transform = srcGroup.getCurrentLocalToVworld();
            } else {
                HashKey keys[] = srcGroup.localToVworldKeys;
                if (keys == null) {
                    // the branch is already detach when
                    // Collision got this message
                    return null;
                }
                transform = srcGroup.getCurrentLocalToVworld(group.key);
            }
            return getSceneGraphPath(srcGroup, group.key, transform);
        }

    }

    /**
     * return the SceneGraphPath of the geomAtom.
     * Find the alternative Collision target closest to the locale.
     */
    static SceneGraphPath getSceneGraphPath(NodeRetained startNode, HashKey key, Transform3D transform) {
        synchronized (startNode.universe.sceneGraphLock) {
            NodeRetained target = startNode;

            UnorderList path = new UnorderList(5, Node.class);
            NodeRetained nodeR = target;
            Locale locale = nodeR.locale;
            String nodeId;

            if (nodeR.inSharedGroup) {
                // getlastNodeId() will destroy this key
                if (key != null) {
                    key = new HashKey(key);
                } else {
                    key = new HashKey(startNode.localToVworldKeys[0]);
                }
            }

            do {
                if (nodeR.source.getCapability(Node.ENABLE_COLLISION_REPORTING)) {
                    path.add(nodeR.source);
                }

                if (nodeR instanceof SharedGroupRetained) {

                    // retrieve the last node ID
                    nodeId = key.getLastNodeId();
                    Vector<NodeRetained> parents = ((SharedGroupRetained) nodeR).parents;
                    NodeRetained prevNodeR = nodeR;
                    for (int i = parents.size() - 1; i >= 0; i--) {
                        NodeRetained linkR = parents.get(i);
                        if (linkR.nodeId.equals(nodeId)) {
                            nodeR = linkR;
                            break;
                        }
                    }
                    if (nodeR == prevNodeR) {
                        // the branch is already detach when
                        // Collision got this message
                        return null;
                    }
                } else if ((nodeR instanceof GroupRetained) && ((GroupRetained) nodeR).collisionTarget) {
                    // we need to find the collision target closest to the
                    // root of tree
                    target = nodeR;

                    if (key == null) {
                        transform = nodeR.getCurrentLocalToVworld(null);
                    } else {
                        transform = nodeR.getCurrentLocalToVworld(key);
                    }
                }
                nodeR = nodeR.parent;
            } while (nodeR != null); // reach Locale

            Node nodes[];
            if (target == startNode) { // in most case
                nodes = (Node[]) path.toArray(false);
            } else { // alternativeCollisionTarget is set
                nodes = (Node[]) path.toArray(target);
            }
            SceneGraphPath sgpath = new SceneGraphPath(locale, nodes, (Node) target.source);
            sgpath.setTransform(transform);
            return sgpath;
        }
    }

    @Override
    void setTriggered() {
        // if path not set, probably the branch is just detach.
        if (collidingPath != null) {
            super.setTriggered();
        }
    }

    /**
     * Perform task in addBehaviorCondition() that has to be
     * set every time the condition met.
     */
    @Override
    void resetBehaviorCondition(BehaviorStructure bs) {
        // The reference geometryAtom will not change once
        // Shape3D create so there is no need to set this.
    }
}