package com.jme.bounding;
/*
* Copyright (c) 2003-2008 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions 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 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.Serializable;
import java.util.ArrayList;
import com.jme.intersection.Intersection;
import com.jme.math.*;
import com.jme.scene.*;
import com.jme.util.*;
import DPJRuntime.*;
/**
* 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 heirarchy 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>
* 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.
*
* @author Mark Powell
* @see com.jme.bounding.CollisionTreeManager
*/
public class CollisionTree<region R, RMesh> {
// R is specific to this node.
// RMesh is for structures shared by all nodes in this tree (mesh & triIndex)
// Root nodes of collision trees should have R == RMesh
// Regions for left, right subtrees
static region Left, Right;
// children trees
protected CollisionTree<R:Left, RMesh> left in R:Left;
protected CollisionTree<R:Right, RMesh> right in R:Right;
// bounding volumes that contain the triangles that the node is
// handling
protected BoundingVolume<R> bounds in R;
// the list of triangle indices that compose the tree. This list
// contains all the triangles of the mesh and is shared between
// all nodes of this tree.
protected ArrayInt<RMesh> triIndex in RMesh;
// Defines the pointers into the triIndex array that this node is
// directly responsible for.
protected int start in R, end in R;
// Required Spatial information
protected TriMesh<RMesh> mesh in RMesh;
public static final int maxTrisPerLeaf = 16;
/**
* Constructor creates a new instance of CollisionTree.
*
* @param type
* the type of collision tree to make
* @see Type
*/
public CollisionTree() {
}
/**
* This constructor creates a copy of t with appropriate hierarchical region typing
* @param t
*/
public CollisionTree(CollisionTree<*, RMesh> t)
/*writes R, R:Left, R:Right, RMesh*/
{
left = t.left == null ? null : new CollisionTree<R:Left, RMesh>(t.left);
right = t.right == null ? null : new CollisionTree<R:Right, RMesh>(t.right);
bounds = new BoundingBox<R>((BoundingBox)t.bounds);
triIndex = t.triIndex;
start = t.start;
end = t.end;
mesh = t.mesh;
}
/**
* Recreate this Collision Tree for the given mesh.
*
* @param mesh
* The trimesh that this OBBTree should represent.
* @param doSort
* true to sort triangles during creation, false otherwise
*/
public void construct(TriMesh<RMesh> mesh, boolean doSort) {
this.mesh = mesh;
triIndex = mesh.getTriangleIndices(triIndex);
createTree(0, triIndex.length, doSort);
}
/**
* Creates a Collision Tree by recursively creating children nodes,
* splitting the triangles this node is responsible for in half until the
* desired triangle count is reached.
*
* @param start
* The start index of the tris array, inclusive.
* @param end
* The end index of the tris array, exclusive.
* @param doSort
* True if the triangles should be sorted at each level, false
* otherwise.
*/
public void createTree(int start, int end, boolean doSort) {
this.start = start;
this.end = end;
if (triIndex == null) {
return;
}
createBounds();
// the bounds at this level should contain all the triangles this level
// is reponsible for.
bounds.computeFromTris(triIndex, mesh, start, end);
// check to see if we are a leaf, if the number of triangles we
// reference is less than or equal to the maximum defined by the
// CollisionTreeManager we are done.
if (end - start + 1 <= maxTrisPerLeaf) {
left = null;
right = null;
return;
}
// if doSort is set we need to attempt to optimize the referenced
// triangles.
// optimizing the sorting of the triangles will help group them
// spatially
// in the left/right children better.
if (doSort) {
System.err.println("Tried to sort tris -- not supported");
//sortTris();
}
// create the left child
if (left == null) {
left = new CollisionTree<R:Left, RMesh>();
}
left.triIndex = this.triIndex;
left.mesh = this.mesh;
left.createTree(start, (start + end) / 2, doSort);
// create the right child
if (right == null) {
right = new CollisionTree<R:Right, RMesh>();
}
right.triIndex = this.triIndex;
right.mesh = this.mesh;
right.createTree((start + end) / 2, end, doSort);
}
/**
* creates the appropriate bounding volume based on the type set during
* construction.
*/
private void createBounds() {
if (bounds == null) {
bounds = new BoundingBox<R>();
}
}
public static <region R1, R2> boolean
intersectsBounding_r(BoundingVolume<R1> myWorldBounds, BoundingVolume<R2> volume)
reads R1, R2
{
return myWorldBounds.intersectsBoundingBox((BoundingBox<R2>)volume);
}
public <region R_cT, RLists | R_cT:* # RLists, R:* # RLists, RMesh:* # RLists> boolean
intersect(CollisionTree<R_cT, R_cT> collisionTree,
ParallelArrayList<RLists> aList, ParallelArrayList<RLists> bList, int cutoff)
{
if (collisionTree == null) {
return false;
}
region RTemps, RmyWB;
Matrix3f<RTemps> tempMat = new Matrix3f<RTemps>();
Vector3f<RTemps> tempVa = new Vector3f<RTemps>();
Vector3f<RTemps> tempVb = new Vector3f<RTemps>();
// Generate myWorldBounds
BoundingBox<RmyWB> worldBounds = bounds.<region RMesh, RMesh, RMesh, RmyWB, RTemps, RTemps, RTemps>
transform_r(mesh.getWorldRotation(), mesh.getWorldTranslation(), mesh.getWorldScale(), null, tempMat, tempVa, tempVb);
return this.<region R_cT, R_cT, RTemps, RLists, RmyWB>intersect(collisionTree, aList, bList,
worldBounds, cutoff, null, null, null);
}
/**
* 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 triangles for this mesh and bList is the
* triangles for the test tree.
*
* @param collisionTree
* The Tree to test.
* @param aList
* a list to contain the colliding triangles of this mesh.
* @param bList
* a list to contain the colliding triangles of the testing mesh.
* @return True if they intersect, false otherwise.
*/
public
<region R_cT, RctMesh, RTemps, RLists, Rwb |
RLists # R:*, RLists # R_cT:*, RLists # Rwb, RLists # RMesh:*, RLists # RctMesh:*,
RTemps:* # R:*, RTemps:* # R_cT:*, RTemps:* # Rwb, RTemps:* # RMesh:*, RTemps:* # RctMesh:*>
boolean
intersect(CollisionTree<R_cT, RctMesh> collisionTree,
ParallelArrayList<RLists> aList, ParallelArrayList<RLists> bList,
BoundingVolume<Rwb> myWorldBounds, int cutoff,
Matrix3f<RTemps> tempMat, Vector3f<RTemps> tempVa, Vector3f<RTemps> tempVb)
reads R:*, R_cT:*, Rwb, RMesh:*, RctMesh:*
writes RTemps:*, RLists
{
// Temporaries for BoundingBox.transform_r (vectors also reused later)
if (tempMat == null) {
tempMat = new Matrix3f<RTemps>();
tempVa = new Vector3f<RTemps>();
tempVb = new Vector3f<RTemps>();
}
// our two collision bounds do not intersect, therefore, our triangles
// must not intersect. Return false.
region R_cTWB;
BoundingVolume<R_cTWB> ctWorldBounds = collisionTree.bounds.<region RctMesh, RctMesh, RctMesh, R_cTWB, RTemps, RTemps, RTemps>
transform_r(collisionTree.mesh.getWorldRotation(), collisionTree.mesh.getWorldTranslation(),
collisionTree.mesh.getWorldScale(), null, tempMat, tempVa, tempVb);
if (!intersectsBounding_r(myWorldBounds, ctWorldBounds)) {
return false;
}
region RTemps2, RLists2;
// 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
if (cutoff > 0) {
boolean test1, test2;
ParallelArrayList<RLists2> aList2 = new ParallelArrayList<RLists2>();
ParallelArrayList<RLists2> bList2 = new ParallelArrayList<RLists2>();
cobegin {
test1 = collisionTree./*<region R:Left, RMesh, RTemps, RLists, R_cTWB>*/
intersect(left, bList, aList, ctWorldBounds, cutoff-1, tempMat, tempVa, tempVb);
test2 = collisionTree.<region R:Right, RMesh, RTemps2, RLists2, R_cTWB>
intersect(right, bList2, aList2, ctWorldBounds, cutoff-1, null, null, null);
}
if (test2) {
aList.addAll(aList2);
bList.addAll(bList2);
return true;
} else if (test1) {
return true;
} else {
return false;
}
} else {
boolean test = collisionTree.intersect(left, bList, aList, ctWorldBounds, 0, tempMat, tempVa, tempVb);
test = collisionTree.intersect(right, bList, aList, ctWorldBounds, 0, tempMat, tempVa, tempVb) || 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) {
if (cutoff > 0) {
boolean test1, test2;
ParallelArrayList<RLists2> aList2 = new ParallelArrayList<RLists2>();
ParallelArrayList<RLists2> bList2 = new ParallelArrayList<RLists2>();
cobegin {
test1 = this./*<region R_cT:Left, RctMesh, RTemps, RLists, Rwb>*/
intersect(collisionTree.left, aList, bList, myWorldBounds, cutoff-1, tempMat, tempVa, tempVb);
test2 = this.<region R_cT:Right, RctMesh, RTemps2, RLists2, Rwb>
intersect(collisionTree.right, aList2, bList2, myWorldBounds, cutoff-1, null, null, null);
}
if (test2) {
aList.addAll(aList2);
bList.addAll(bList2);
return true;
} else if (test1) {
return true;
} else {
return false;
}
} else {
boolean test = this.intersect(collisionTree.left, aList, bList, myWorldBounds, 0, tempMat, tempVa, tempVb);
test = this.intersect(collisionTree.right, aList, bList, myWorldBounds, 0, tempMat, tempVa, tempVb) || test;
return test;
}
}
// both this node and the testing node are leaves. Therefore, we can
// switch to checking the contained triangles with each other. Any
// that are found to intersect are placed in the appropriate list.
Quaternion<RMesh> roti = mesh.getWorldRotation();
Vector3f<RMesh> scalei = mesh.getWorldScale();
Vector3f<RMesh> transi = mesh.getWorldTranslation();
Quaternion<RctMesh> rotj = collisionTree.mesh.getWorldRotation();
Vector3f<RctMesh> scalej = collisionTree.mesh.getWorldScale();
Vector3f<RctMesh> transj = collisionTree.mesh.getWorldTranslation();
boolean test = false;
// Temporaries to contain information for ray intersection
// Converted from fields to provide thread-safety
final Vector3f<RTemps> tempVc = new Vector3f<RTemps>();
final Vector3f<RTemps> tempVd = new Vector3f<RTemps>();
final Vector3f<RTemps> tempVe = new Vector3f<RTemps>();
final Vector3f<RTemps> tempVf = new Vector3f<RTemps>();
Vector3f.Array<RTemps> verts = new Vector3f.Array<RTemps>(3);
Vector3f.Array<RTemps> target = new Vector3f.Array<RTemps>(3);
// Temporaries for Intersection.intersection_r
Vector3f<RTemps> e1 = new Vector3f<RTemps>();
Vector3f<RTemps> e2 = new Vector3f<RTemps>();
Vector3f<RTemps> n1 = new Vector3f<RTemps>();
Vector3f<RTemps> n2 = new Vector3f<RTemps>();
ArrayFloat<RTemps> isect1 = new ArrayFloat<RTemps>(2);
ArrayFloat<RTemps> isect2 = new ArrayFloat<RTemps>(2);
for (int i = start; i < end; i++) {
mesh.<region RTemps>getTriangle(triIndex[i], verts);
roti.mult(tempVa.set(verts[0]).multLocal(scalei), tempVa).addLocal(transi);
roti.mult(tempVb.set(verts[1]).multLocal(scalei), tempVb).addLocal(transi);
roti.mult(tempVc.set(verts[2]).multLocal(scalei), tempVc).addLocal(transi);
for (int j = collisionTree.start; j < collisionTree.end; j++) {
collisionTree.mesh.<region RTemps>getTriangle(collisionTree.triIndex[j], target);
rotj.mult(tempVd.set(target[0]).multLocal(scalej), tempVd).addLocal(transj);
rotj.mult(tempVe.set(target[1]).multLocal(scalej), tempVe).addLocal(transj);
rotj.mult(tempVf.set(target[2]).multLocal(scalej), tempVf).addLocal(transj);
if (Intersection.intersection_r(tempVa, tempVb, tempVc, tempVd, tempVe, tempVf, e1, e2, n1, n2, isect1, isect2)) {
test = true;
aList.add(triIndex[i]);
bList.add(collisionTree.triIndex[j]);
}
}
}
return test;
}
}