Package com.ardor3d.bounding

Source Code of com.ardor3d.bounding.CollisionTree

/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/

package com.ardor3d.bounding;

import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

import com.ardor3d.intersection.Intersection;
import com.ardor3d.intersection.PrimitiveKey;
import com.ardor3d.math.Ray3;
import com.ardor3d.math.Vector3;
import com.ardor3d.math.type.ReadOnlyTransform;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.MeshData;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.Spatial;

/**
* CollisionTree defines a well balanced red black tree used for triangle accurate collision detection. The
* CollisionTree supports three types: Oriented Bounding Box, Axis-Aligned Bounding Box and Sphere. The tree is composed
* of a hierarchy of nodes, all but leaf nodes have two children, a left and a right, where the children contain half of
* the triangles of the parent. This "half split" is executed down the tree until the node is maintaining a set maximum
* of triangles. This node is called the leaf node. Intersection checks are handled as follows:<br>
* 1. The bounds of the node is checked for intersection. If no intersection occurs here, no further processing is
* needed, the children (nodes or triangles) do not intersect.<br>
* 2a. If an intersection occurs and we have children left/right nodes, pass the intersection information to the
* children.<br>
* 2b. If an intersection occurs and we are a leaf node, pass each triangle individually for intersection checking.<br>
* <br>
* Optionally, during creation of the collision tree, sorting can be applied. Sorting will attempt to optimize the order
* of the triangles in such a way as to best split for left and right sub-trees. This function can lead to faster
* intersection tests, but increases the creation time for the tree. The number of triangles a leaf node is responsible
* for is defined in CollisionTreeManager. It is actually recommended to allow CollisionTreeManager to maintain the
* collision trees for a scene.
*
* @see com.ardor3d.bounding.CollisionTreeManager
*/
public class CollisionTree implements Serializable {

    private static final long serialVersionUID = 1L;

    public enum Type {
        /** CollisionTree using Oriented Bounding Boxes. */
        OBB,
        /** CollisionTree using Axis-Aligned Bounding Boxes. */
        AABB,
        /** CollisionTree using Bounding Spheres. */
        Sphere;
    }

    // Default tree is axis-aligned
    protected Type _type = Type.AABB;

    // children trees
    protected CollisionTree _left;
    protected CollisionTree _right;

    // bounding volumes that contain the triangles that the node is handling
    protected BoundingVolume _bounds;
    protected BoundingVolume _worldBounds;

    /**
     * the list of primitives indices that compose the tree. This list contains all the triangles of the mesh and is
     * shared between all nodes of this tree. Stored here to allow for sorting.
     */
    protected int[] _primitiveIndices;

    // Defines the pointers into the triIndex array that this node is directly responsible for.
    protected int _start, _end;

    // Required Spatial information
    protected transient WeakReference<Mesh> _mesh;
    protected int _section;

    // Comparator used to sort triangle indices
    protected transient final TreeComparator _comparator = new TreeComparator();

    /**
     * Constructor creates a new instance of CollisionTree.
     *
     * @param type
     *            the type of collision tree to make
     * @see Type
     */
    public CollisionTree(final Type type) {
        _type = type;
    }

    /**
     * Recreate this Collision Tree for the given Node and child index.
     *
     * @param childIndex
     *            the index of the child to generate the tree for.
     * @param parent
     *            The Node that this tree should represent.
     * @param doSort
     *            true to sort primitives during creation, false otherwise
     */
    public void construct(final int childIndex, final int section, final Node parent, final boolean doSort) {
        final Spatial spat = parent.getChild(childIndex);
        if (spat instanceof Mesh) {
            _mesh = makeRef((Mesh) spat);
            _primitiveIndices = new int[((Mesh) spat).getMeshData().getPrimitiveCount(section)];
            for (int i = 0; i < _primitiveIndices.length; i++) {
                _primitiveIndices[i] = i;
            }
            createTree(section, 0, _primitiveIndices.length, doSort);
        }
    }

    /**
     * Recreate this Collision Tree for the given mesh.
     *
     * @param mesh
     *            The mesh that this tree should represent.
     * @param doSort
     *            true to sort primitives during creation, false otherwise
     */
    public void construct(final Mesh mesh, final boolean doSort) {
        _mesh = makeRef(mesh);
        if (mesh.getMeshData().getSectionCount() == 1) {
            _primitiveIndices = new int[mesh.getMeshData().getPrimitiveCount(0)];
            for (int i = 0; i < _primitiveIndices.length; i++) {
                _primitiveIndices[i] = i;
            }
            createTree(0, 0, _primitiveIndices.length, doSort);
        } else {
            // divide up the sections into the tree by adding intermediate nodes as needed.
            splitMesh(mesh, 0, mesh.getMeshData().getSectionCount(), doSort);
        }
    }

    protected void splitMesh(final Mesh mesh, final int sectionStart, final int sectionEnd, final boolean doSort) {
        _mesh = makeRef(mesh);

        // Split range in half
        final int rangeSize = sectionEnd - sectionStart;
        final int halfRange = rangeSize / 2; // odd number will give +1 to right.

        // left half:
        // if half size == 1, create as regular CollisionTree
        if (halfRange == 1) {
            // compute section
            final int section = sectionStart;

            // create the left child
            _left = new CollisionTree(_type);

            _left._primitiveIndices = new int[mesh.getMeshData().getPrimitiveCount(section)];
            for (int i = 0; i < _left._primitiveIndices.length; i++) {
                _left._primitiveIndices[i] = i;
            }
            _left._mesh = _mesh;
            _left.createTree(section, 0, _left._primitiveIndices.length, doSort);
        } else {
            // otherwise, make an empty collision tree and call split with new range
            _left = new CollisionTree(_type);
            _left.splitMesh(mesh, sectionStart, sectionStart + halfRange, doSort);
        }

        // right half:
        // if rangeSize - half size == 1, create as regular CollisionTree
        if (rangeSize - halfRange == 1) {
            // compute section
            final int section = sectionStart + 1;

            // create the left child
            _right = new CollisionTree(_type);

            _right._primitiveIndices = new int[mesh.getMeshData().getPrimitiveCount(section)];
            for (int i = 0; i < _right._primitiveIndices.length; i++) {
                _right._primitiveIndices[i] = i;
            }
            _right._mesh = _mesh;
            _right.createTree(section, 0, _right._primitiveIndices.length, doSort);
        } else {
            // otherwise, make an empty collision tree and call split with new range
            _right = new CollisionTree(_type);
            _right.splitMesh(mesh, sectionStart + halfRange, sectionEnd, doSort);
        }

        // Ok, now since we technically have no primitives, we need our bounds to be the merging of our children bounds
        // instead:
        _bounds = _left._bounds.clone(_bounds);
        _bounds.mergeLocal(_right._bounds);
        _worldBounds = _bounds.clone(_worldBounds);
    }

    /**
     * Creates a Collision Tree by recursively creating children nodes, splitting the primitives this node is
     * responsible for in half until the desired primitive count is reached.
     *
     * @param start
     *            The start index of the primitivesArray, inclusive.
     * @param end
     *            The end index of the primitivesArray, exclusive.
     * @param doSort
     *            True if the primitives should be sorted at each level, false otherwise.
     */
    public void createTree(final int section, final int start, final int end, final boolean doSort) {
        _section = section;
        _start = start;
        _end = end;

        if (_primitiveIndices == null) {
            return;
        }

        createBounds();

        // the bounds at this level should contain all the primitives this level is responsible for.
        _bounds.computeFromPrimitives(getMesh().getMeshData(), _section, _primitiveIndices, _start, _end);

        // check to see if we are a leaf, if the number of primitives we reference is less than or equal to the maximum
        // defined by the CollisionTreeManager we are done.
        if (_end - _start + 1 <= CollisionTreeManager.getInstance().getMaxPrimitivesPerLeaf()) {
            return;
        }

        // if doSort is set we need to attempt to optimize the referenced primitives. optimizing the sorting of the
        // primitives will help group them spatially in the left/right children better.
        if (doSort) {
            sortPrimitives();
        }

        // create the left child
        if (_left == null) {
            _left = new CollisionTree(_type);
        }

        _left._primitiveIndices = _primitiveIndices;
        _left._mesh = _mesh;
        _left.createTree(_section, _start, (_start + _end) / 2, doSort);

        // create the right child
        if (_right == null) {
            _right = new CollisionTree(_type);
        }
        _right._primitiveIndices = _primitiveIndices;
        _right._mesh = _mesh;
        _right.createTree(_section, (_start + _end) / 2, _end, doSort);
    }

    /**
     * Tests if the world bounds of the node at this level intersects a provided bounding volume. If an intersection
     * occurs, true is returned, otherwise false is returned. If the provided volume is invalid, false is returned.
     *
     * @param volume
     *            the volume to intersect with.
     * @return true if there is an intersect, false otherwise.
     */
    public boolean intersectsBounding(final BoundingVolume volume) {
        switch (volume.getType()) {
            case AABB:
                return _worldBounds.intersectsBoundingBox((BoundingBox) volume);
            case OBB:
                return _worldBounds.intersectsOrientedBoundingBox((OrientedBoundingBox) volume);
            case Sphere:
                return _worldBounds.intersectsSphere((BoundingSphere) volume);
            default:
                return false;
        }

    }

    /**
     * Determines if this Collision Tree intersects the given CollisionTree. If a collision occurs, true is returned,
     * otherwise false is returned. If the provided collisionTree is invalid, false is returned.
     *
     * @param collisionTree
     *            The Tree to test.
     * @return True if they intersect, false otherwise.
     */
    public boolean intersect(final CollisionTree collisionTree) {
        if (collisionTree == null) {
            return false;
        }

        collisionTree._worldBounds = collisionTree._bounds.transform(collisionTree.getMesh().getWorldTransform(),
                collisionTree._worldBounds);

        // our two collision bounds do not intersect, therefore, our primitives
        // must not intersect. Return false.
        if (!intersectsBounding(collisionTree._worldBounds)) {
            return false;
        }

        // check children
        if (_left != null) { // This is not a leaf
            if (collisionTree.intersect(_left)) {
                return true;
            }
            if (collisionTree.intersect(_right)) {
                return true;
            }
            return false;
        }

        // This is a leaf
        if (collisionTree._left != null) {
            // but collision isn't
            if (intersect(collisionTree._left)) {
                return true;
            }
            if (intersect(collisionTree._right)) {
                return true;
            }
            return false;
        }

        // both are leaves
        final ReadOnlyTransform transformA = getMesh().getWorldTransform();
        final ReadOnlyTransform transformB = collisionTree.getMesh().getWorldTransform();

        final MeshData dataA = getMesh().getMeshData();
        final MeshData dataB = collisionTree.getMesh().getMeshData();

        Vector3[] storeA = null;
        Vector3[] storeB = null;

        // for every primitive to compare, put them into world space and check for intersections
        for (int i = _start; i < _end; i++) {
            storeA = dataA.getPrimitiveVertices(_primitiveIndices[i], _section, storeA);
            // to world space
            for (int t = 0; t < storeA.length; t++) {
                transformA.applyForward(storeA[t]);
            }
            for (int j = collisionTree._start; j < collisionTree._end; j++) {
                storeB = dataB.getPrimitiveVertices(collisionTree._primitiveIndices[j], collisionTree._section, storeB);
                // to world space
                for (int t = 0; t < storeB.length; t++) {
                    transformB.applyForward(storeB[t]);
                }
                if (Intersection.intersection(storeA, storeB)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Determines if this Collision Tree intersects the given CollisionTree. If a collision occurs, true is returned,
     * otherwise false is returned. If the provided collisionTree is invalid, false is returned. All collisions that
     * occur are stored in lists as an integer index into the mesh's triangle buffer. where aList is the primitives for
     * this mesh and bList is the primitives for the test tree.
     *
     * @param collisionTree
     *            The Tree to test.
     * @param aList
     *            a list to contain the colliding primitives of this mesh.
     * @param bList
     *            a list to contain the colliding primitives of the testing mesh.
     * @return True if they intersect, false otherwise.
     */
    public boolean intersect(final CollisionTree collisionTree, final List<PrimitiveKey> aList,
            final List<PrimitiveKey> bList) {

        if (collisionTree == null) {
            return false;
        }

        collisionTree._worldBounds = collisionTree._bounds.transform(collisionTree.getMesh().getWorldTransform(),
                collisionTree._worldBounds);

        // our two collision bounds do not intersect, therefore, our primitives
        // must not intersect. Return false.
        if (!intersectsBounding(collisionTree._worldBounds)) {
            return false;
        }

        // if our node is not a leaf send the children (both left and right) to
        // the test tree.
        if (_left != null) { // This is not a leaf
            boolean test = collisionTree.intersect(_left, bList, aList);
            test = collisionTree.intersect(_right, bList, aList) || test;
            return test;
        }

        // This node is a leaf, but the testing tree node is not. Therefore,
        // continue processing the testing tree until we find its leaves.
        if (collisionTree._left != null) {
            boolean test = intersect(collisionTree._left, aList, bList);
            test = intersect(collisionTree._right, aList, bList) || test;
            return test;
        }

        // both this node and the testing node are leaves. Therefore, we can
        // switch to checking the contained primitives with each other. Any
        // that are found to intersect are placed in the appropriate list.
        final ReadOnlyTransform transformA = getMesh().getWorldTransform();
        final ReadOnlyTransform transformB = collisionTree.getMesh().getWorldTransform();

        final MeshData dataA = getMesh().getMeshData();
        final MeshData dataB = collisionTree.getMesh().getMeshData();

        Vector3[] storeA = null;
        Vector3[] storeB = null;

        boolean test = false;

        for (int i = _start; i < _end; i++) {
            storeA = dataA.getPrimitiveVertices(_primitiveIndices[i], _section, storeA);
            // to world space
            for (int t = 0; t < storeA.length; t++) {
                transformA.applyForward(storeA[t]);
            }
            for (int j = collisionTree._start; j < collisionTree._end; j++) {
                storeB = dataB.getPrimitiveVertices(collisionTree._primitiveIndices[j], collisionTree._section, storeB);
                // to world space
                for (int t = 0; t < storeB.length; t++) {
                    transformB.applyForward(storeB[t]);
                }
                if (Intersection.intersection(storeA, storeB)) {
                    test = true;
                    aList.add(new PrimitiveKey(_primitiveIndices[i], _section));
                    bList.add(new PrimitiveKey(collisionTree._primitiveIndices[j], collisionTree._section));
                }
            }
        }

        return test;
    }

    /**
     * intersect checks for collisions between this collision tree and a provided Ray. Any collisions are stored in a
     * provided list as primitive index values. The ray is assumed to have a normalized direction for accurate
     * calculations.
     *
     * @param ray
     *            the ray to test for intersections.
     * @param store
     *            a list to fill with the index values of the primitive hit. if null, a new List is created.
     * @return the list.
     */
    public List<PrimitiveKey> intersect(final Ray3 ray, final List<PrimitiveKey> store) {
        List<PrimitiveKey> result = store;
        if (result == null) {
            result = new ArrayList<PrimitiveKey>();
        }

        // if our ray doesn't hit the bounds, then it must not hit a primitive.
        if (!_worldBounds.intersects(ray)) {
            return result;
        }

        // This is not a leaf node, therefore, check each child (left/right) for intersection with the ray.
        if (_left != null) {
            _left._worldBounds = _left._bounds.transform(getMesh().getWorldTransform(), _left._worldBounds);
            _left.intersect(ray, result);
        }

        if (_right != null) {
            _right._worldBounds = _right._bounds.transform(getMesh().getWorldTransform(), _right._worldBounds);
            _right.intersect(ray, result);
        } else if (_left == null) {
            // This is a leaf node. We can therefore check each primitive this node contains. If an intersection occurs,
            // place it in the list.

            final MeshData data = getMesh().getMeshData();
            final ReadOnlyTransform transform = getMesh().getWorldTransform();

            Vector3[] points = null;
            for (int i = _start; i < _end; i++) {
                points = data.getPrimitiveVertices(_primitiveIndices[i], _section, points);
                for (int t = 0; t < points.length; t++) {
                    transform.applyForward(points[t]);
                }
                if (ray.intersects(points, null)) {
                    result.add(new PrimitiveKey(_primitiveIndices[i], _section));
                }
            }
        }
        return result;
    }

    /**
     * Returns the bounding volume for this tree node in local space.
     *
     * @return the bounding volume for this tree node in local space.
     */
    public BoundingVolume getBounds() {
        return _bounds;
    }

    /**
     * Returns the bounding volume for this tree node in world space.
     *
     * @return the bounding volume for this tree node in world space.
     */
    public BoundingVolume getWorldBounds() {
        return _worldBounds;
    }

    /**
     * creates the appropriate bounding volume based on the type set during construction.
     */
    private void createBounds() {
        switch (_type) {
            case AABB:
                _bounds = new BoundingBox();
                _worldBounds = new BoundingBox();
                break;
            case OBB:
                _bounds = new OrientedBoundingBox();
                _worldBounds = new OrientedBoundingBox();
                break;
            case Sphere:
                _bounds = new BoundingSphere();
                _worldBounds = new BoundingSphere();
                break;
            default:
                break;
        }
    }

    /**
     * sortPrimitives attempts to optimize the ordering of the subsection of the array of primitives this node is
     * responsible for. The sorting is based on the most efficient method along an axis. Using the TreeComparator and
     * quick sort, the subsection of the array is sorted.
     */
    public void sortPrimitives() {
        switch (_type) {
            case AABB:
                // determine the longest length of the box, this axis will be best for sorting.
                if (((BoundingBox) _bounds).getXExtent() > ((BoundingBox) _bounds).getYExtent()) {
                    if (((BoundingBox) _bounds).getXExtent() > ((BoundingBox) _bounds).getZExtent()) {
                        _comparator.setAxis(TreeComparator.Axis.X);
                    } else {
                        _comparator.setAxis(TreeComparator.Axis.Z);
                    }
                } else {
                    if (((BoundingBox) _bounds).getYExtent() > ((BoundingBox) _bounds).getZExtent()) {
                        _comparator.setAxis(TreeComparator.Axis.Y);
                    } else {
                        _comparator.setAxis(TreeComparator.Axis.Z);
                    }
                }
                break;
            case OBB:
                // determine the longest length of the box, this axis will be best for sorting.
                if (((OrientedBoundingBox) _bounds)._extent.getX() > ((OrientedBoundingBox) _bounds)._extent.getY()) {
                    if (((OrientedBoundingBox) _bounds)._extent.getX() > ((OrientedBoundingBox) _bounds)._extent.getZ()) {
                        _comparator.setAxis(TreeComparator.Axis.X);
                    } else {
                        _comparator.setAxis(TreeComparator.Axis.Z);
                    }
                } else {
                    if (((OrientedBoundingBox) _bounds)._extent.getY() > ((OrientedBoundingBox) _bounds)._extent.getZ()) {
                        _comparator.setAxis(TreeComparator.Axis.Y);
                    } else {
                        _comparator.setAxis(TreeComparator.Axis.Z);
                    }
                }
                break;
            case Sphere:
                // sort any axis, X is fine.
                _comparator.setAxis(TreeComparator.Axis.X);
                break;
            default:
                break;
        }

        _comparator.setMesh(getMesh());
        // TODO: broken atm
        // SortUtil.qsort(_primitiveIndices, _start, _end - 1, _comparator);
    }

    /**
     * @return the Mesh referenced by _mesh
     */
    private Mesh getMesh() {
        return _mesh.get();
    }

    /**
     * @param mesh
     *            Mesh object to reference.
     * @return a new reference to the given mesh.
     */
    private WeakReference<Mesh> makeRef(final Mesh mesh) {
        return new WeakReference<Mesh>(mesh);
    }
}
TOP

Related Classes of com.ardor3d.bounding.CollisionTree

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.