blob: 36609224e951327fa114145ebfc2665c50418749 [file] [log] [blame]
/*
* Copyright (c) 2009-2010 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.
*/
package com.jme3.collision.bih;
import com.jme3.bounding.BoundingBox;
import com.jme3.bounding.BoundingSphere;
import com.jme3.bounding.BoundingVolume;
import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResults;
import com.jme3.collision.UnsupportedCollisionException;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.scene.CollisionData;
import com.jme3.scene.Mesh;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.scene.mesh.VirtualIndexBuffer;
import com.jme3.scene.mesh.WrappedIndexBuffer;
import com.jme3.util.TempVars;
import java.io.IOException;
import static java.lang.Math.max;
import java.nio.FloatBuffer;
public class BIHTree implements CollisionData {
public static final int MAX_TREE_DEPTH = 100;
public static final int MAX_TRIS_PER_NODE = 21;
private Mesh mesh;
private BIHNode root;
private int maxTrisPerNode;
private int numTris;
private float[] pointData;
private int[] triIndices;
private transient CollisionResults boundResults = new CollisionResults();
private transient float[] bihSwapTmp;
private static final TriangleAxisComparator[] comparators = new TriangleAxisComparator[]
{
new TriangleAxisComparator(0),
new TriangleAxisComparator(1),
new TriangleAxisComparator(2)
};
private void initTriList(FloatBuffer vb, IndexBuffer ib) {
pointData = new float[numTris * 3 * 3];
int p = 0;
for (int i = 0; i < numTris * 3; i += 3) {
int vert = ib.get(i) * 3;
pointData[p++] = vb.get(vert++);
pointData[p++] = vb.get(vert++);
pointData[p++] = vb.get(vert);
vert = ib.get(i + 1) * 3;
pointData[p++] = vb.get(vert++);
pointData[p++] = vb.get(vert++);
pointData[p++] = vb.get(vert);
vert = ib.get(i + 2) * 3;
pointData[p++] = vb.get(vert++);
pointData[p++] = vb.get(vert++);
pointData[p++] = vb.get(vert);
}
triIndices = new int[numTris];
for (int i = 0; i < numTris; i++) {
triIndices[i] = i;
}
}
public BIHTree(Mesh mesh, int maxTrisPerNode) {
this.mesh = mesh;
this.maxTrisPerNode = maxTrisPerNode;
if (maxTrisPerNode < 1 || mesh == null) {
throw new IllegalArgumentException();
}
bihSwapTmp = new float[9];
FloatBuffer vb = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
IndexBuffer ib = mesh.getIndexBuffer();
if (ib == null) {
ib = new VirtualIndexBuffer(mesh.getVertexCount(), mesh.getMode());
} else if (mesh.getMode() != Mode.Triangles) {
ib = new WrappedIndexBuffer(mesh);
}
numTris = ib.size() / 3;
initTriList(vb, ib);
}
public BIHTree(Mesh mesh) {
this(mesh, MAX_TRIS_PER_NODE);
}
public BIHTree() {
}
public void construct() {
BoundingBox sceneBbox = createBox(0, numTris - 1);
root = createNode(0, numTris - 1, sceneBbox, 0);
}
private BoundingBox createBox(int l, int r) {
TempVars vars = TempVars.get();
Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY));
Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY));
Vector3f v1 = vars.vect3,
v2 = vars.vect4,
v3 = vars.vect5;
for (int i = l; i <= r; i++) {
getTriangle(i, v1, v2, v3);
BoundingBox.checkMinMax(min, max, v1);
BoundingBox.checkMinMax(min, max, v2);
BoundingBox.checkMinMax(min, max, v3);
}
BoundingBox bbox = new BoundingBox(min, max);
vars.release();
return bbox;
}
int getTriangleIndex(int triIndex) {
return triIndices[triIndex];
}
private int sortTriangles(int l, int r, float split, int axis) {
int pivot = l;
int j = r;
TempVars vars = TempVars.get();
Vector3f v1 = vars.vect1,
v2 = vars.vect2,
v3 = vars.vect3;
while (pivot <= j) {
getTriangle(pivot, v1, v2, v3);
v1.addLocal(v2).addLocal(v3).multLocal(FastMath.ONE_THIRD);
if (v1.get(axis) > split) {
swapTriangles(pivot, j);
--j;
} else {
++pivot;
}
}
vars.release();
pivot = (pivot == l && j < pivot) ? j : pivot;
return pivot;
}
private void setMinMax(BoundingBox bbox, boolean doMin, int axis, float value) {
Vector3f min = bbox.getMin(null);
Vector3f max = bbox.getMax(null);
if (doMin) {
min.set(axis, value);
} else {
max.set(axis, value);
}
bbox.setMinMax(min, max);
}
private float getMinMax(BoundingBox bbox, boolean doMin, int axis) {
if (doMin) {
return bbox.getMin(null).get(axis);
} else {
return bbox.getMax(null).get(axis);
}
}
// private BIHNode createNode2(int l, int r, BoundingBox nodeBbox, int depth){
// if ((r - l) < maxTrisPerNode || depth > 100)
// return createLeaf(l, r);
//
// BoundingBox currentBox = createBox(l, r);
// int axis = depth % 3;
// float split = currentBox.getCenter().get(axis);
//
// TriangleAxisComparator comparator = comparators[axis];
// Arrays.sort(tris, l, r, comparator);
// int splitIndex = -1;
//
// float leftPlane, rightPlane = Float.POSITIVE_INFINITY;
// leftPlane = tris[l].getExtreme(axis, false);
// for (int i = l; i <= r; i++){
// BIHTriangle tri = tris[i];
// if (splitIndex == -1){
// float v = tri.getCenter().get(axis);
// if (v > split){
// if (i == 0){
// // no left plane
// splitIndex = -2;
// }else{
// splitIndex = i;
// // first triangle assigned to right
// rightPlane = tri.getExtreme(axis, true);
// }
// }else{
// // triangle assigned to left
// float ex = tri.getExtreme(axis, false);
// if (ex > leftPlane)
// leftPlane = ex;
// }
// }else{
// float ex = tri.getExtreme(axis, true);
// if (ex < rightPlane)
// rightPlane = ex;
// }
// }
//
// if (splitIndex < 0){
// splitIndex = (r - l) / 2;
//
// leftPlane = Float.NEGATIVE_INFINITY;
// rightPlane = Float.POSITIVE_INFINITY;
//
// for (int i = l; i < splitIndex; i++){
// float ex = tris[i].getExtreme(axis, false);
// if (ex > leftPlane){
// leftPlane = ex;
// }
// }
// for (int i = splitIndex; i <= r; i++){
// float ex = tris[i].getExtreme(axis, true);
// if (ex < rightPlane){
// rightPlane = ex;
// }
// }
// }
//
// BIHNode node = new BIHNode(axis);
// node.leftPlane = leftPlane;
// node.rightPlane = rightPlane;
//
// node.leftIndex = l;
// node.rightIndex = r;
//
// BoundingBox leftBbox = new BoundingBox(currentBox);
// setMinMax(leftBbox, false, axis, split);
// node.left = createNode2(l, splitIndex-1, leftBbox, depth+1);
//
// BoundingBox rightBbox = new BoundingBox(currentBox);
// setMinMax(rightBbox, true, axis, split);
// node.right = createNode2(splitIndex, r, rightBbox, depth+1);
//
// return node;
// }
private BIHNode createNode(int l, int r, BoundingBox nodeBbox, int depth) {
if ((r - l) < maxTrisPerNode || depth > MAX_TREE_DEPTH) {
return new BIHNode(l, r);
}
BoundingBox currentBox = createBox(l, r);
Vector3f exteriorExt = nodeBbox.getExtent(null);
Vector3f interiorExt = currentBox.getExtent(null);
exteriorExt.subtractLocal(interiorExt);
int axis = 0;
if (exteriorExt.x > exteriorExt.y) {
if (exteriorExt.x > exteriorExt.z) {
axis = 0;
} else {
axis = 2;
}
} else {
if (exteriorExt.y > exteriorExt.z) {
axis = 1;
} else {
axis = 2;
}
}
if (exteriorExt.equals(Vector3f.ZERO)) {
axis = 0;
}
// Arrays.sort(tris, l, r, comparators[axis]);
float split = currentBox.getCenter().get(axis);
int pivot = sortTriangles(l, r, split, axis);
if (pivot == l || pivot == r) {
pivot = (r + l) / 2;
}
//If one of the partitions is empty, continue with recursion: same level but different bbox
if (pivot < l) {
//Only right
BoundingBox rbbox = new BoundingBox(currentBox);
setMinMax(rbbox, true, axis, split);
return createNode(l, r, rbbox, depth + 1);
} else if (pivot > r) {
//Only left
BoundingBox lbbox = new BoundingBox(currentBox);
setMinMax(lbbox, false, axis, split);
return createNode(l, r, lbbox, depth + 1);
} else {
//Build the node
BIHNode node = new BIHNode(axis);
//Left child
BoundingBox lbbox = new BoundingBox(currentBox);
setMinMax(lbbox, false, axis, split);
//The left node right border is the plane most right
node.setLeftPlane(getMinMax(createBox(l, max(l, pivot - 1)), false, axis));
node.setLeftChild(createNode(l, max(l, pivot - 1), lbbox, depth + 1)); //Recursive call
//Right Child
BoundingBox rbbox = new BoundingBox(currentBox);
setMinMax(rbbox, true, axis, split);
//The right node left border is the plane most left
node.setRightPlane(getMinMax(createBox(pivot, r), true, axis));
node.setRightChild(createNode(pivot, r, rbbox, depth + 1)); //Recursive call
return node;
}
}
public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3) {
int pointIndex = index * 9;
v1.x = pointData[pointIndex++];
v1.y = pointData[pointIndex++];
v1.z = pointData[pointIndex++];
v2.x = pointData[pointIndex++];
v2.y = pointData[pointIndex++];
v2.z = pointData[pointIndex++];
v3.x = pointData[pointIndex++];
v3.y = pointData[pointIndex++];
v3.z = pointData[pointIndex++];
}
public void swapTriangles(int index1, int index2) {
int p1 = index1 * 9;
int p2 = index2 * 9;
// store p1 in tmp
System.arraycopy(pointData, p1, bihSwapTmp, 0, 9);
// copy p2 to p1
System.arraycopy(pointData, p2, pointData, p1, 9);
// copy tmp to p2
System.arraycopy(bihSwapTmp, 0, pointData, p2, 9);
// swap indices
int tmp2 = triIndices[index1];
triIndices[index1] = triIndices[index2];
triIndices[index2] = tmp2;
}
private int collideWithRay(Ray r,
Matrix4f worldMatrix,
BoundingVolume worldBound,
CollisionResults results) {
boundResults.clear();
worldBound.collideWith(r, boundResults);
if (boundResults.size() > 0) {
float tMin = boundResults.getClosestCollision().getDistance();
float tMax = boundResults.getFarthestCollision().getDistance();
if (tMax <= 0) {
tMax = Float.POSITIVE_INFINITY;
} else if (tMin == tMax) {
tMin = 0;
}
if (tMin <= 0) {
tMin = 0;
}
if (r.getLimit() < Float.POSITIVE_INFINITY) {
tMax = Math.min(tMax, r.getLimit());
if (tMin > tMax){
return 0;
}
}
// return root.intersectBrute(r, worldMatrix, this, tMin, tMax, results);
return root.intersectWhere(r, worldMatrix, this, tMin, tMax, results);
}
return 0;
}
private int collideWithBoundingVolume(BoundingVolume bv,
Matrix4f worldMatrix,
CollisionResults results) {
BoundingBox bbox;
if (bv instanceof BoundingSphere) {
BoundingSphere sphere = (BoundingSphere) bv;
bbox = new BoundingBox(bv.getCenter().clone(), sphere.getRadius(),
sphere.getRadius(),
sphere.getRadius());
} else if (bv instanceof BoundingBox) {
bbox = new BoundingBox((BoundingBox) bv);
} else {
throw new UnsupportedCollisionException();
}
bbox.transform(worldMatrix.invert(), bbox);
return root.intersectWhere(bv, bbox, worldMatrix, this, results);
}
public int collideWith(Collidable other,
Matrix4f worldMatrix,
BoundingVolume worldBound,
CollisionResults results) {
if (other instanceof Ray) {
Ray ray = (Ray) other;
return collideWithRay(ray, worldMatrix, worldBound, results);
} else if (other instanceof BoundingVolume) {
BoundingVolume bv = (BoundingVolume) other;
return collideWithBoundingVolume(bv, worldMatrix, results);
} else {
throw new UnsupportedCollisionException();
}
}
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(mesh, "mesh", null);
oc.write(root, "root", null);
oc.write(maxTrisPerNode, "tris_per_node", 0);
oc.write(pointData, "points", null);
oc.write(triIndices, "indices", null);
}
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
mesh = (Mesh) ic.readSavable("mesh", null);
root = (BIHNode) ic.readSavable("root", null);
maxTrisPerNode = ic.readInt("tris_per_node", 0);
pointData = ic.readFloatArray("points", null);
triIndices = ic.readIntArray("indices", null);
}
}