blob: 0f5c1c820022436d0649b8b8cb3cdd1226c5c1f5 [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.collision.Collidable;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.export.*;
import com.jme3.math.Matrix4f;
import com.jme3.math.Ray;
import com.jme3.math.Triangle;
import com.jme3.math.Vector3f;
import com.jme3.util.TempVars;
import java.io.IOException;
import static java.lang.Math.max;
import static java.lang.Math.min;
import java.util.ArrayList;
/**
* Bounding Interval Hierarchy.
* Based on:
*
* Instant Ray Tracing: The Bounding Interval Hierarchy
* By Carsten Wächter and Alexander Keller
*/
public final class BIHNode implements Savable {
private int leftIndex, rightIndex;
private BIHNode left;
private BIHNode right;
private float leftPlane;
private float rightPlane;
private int axis;
//Do not do this: It increases memory usage of each BIHNode by at least 56 bytes!
//
//private Triangle tmpTriangle = new Triangle();
public BIHNode(int l, int r) {
leftIndex = l;
rightIndex = r;
axis = 3; // indicates leaf
}
public BIHNode(int axis) {
this.axis = axis;
}
public BIHNode() {
}
public BIHNode getLeftChild() {
return left;
}
public void setLeftChild(BIHNode left) {
this.left = left;
}
public float getLeftPlane() {
return leftPlane;
}
public void setLeftPlane(float leftPlane) {
this.leftPlane = leftPlane;
}
public BIHNode getRightChild() {
return right;
}
public void setRightChild(BIHNode right) {
this.right = right;
}
public float getRightPlane() {
return rightPlane;
}
public void setRightPlane(float rightPlane) {
this.rightPlane = rightPlane;
}
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(leftIndex, "left_index", 0);
oc.write(rightIndex, "right_index", 0);
oc.write(leftPlane, "left_plane", 0);
oc.write(rightPlane, "right_plane", 0);
oc.write(axis, "axis", 0);
oc.write(left, "left_node", null);
oc.write(right, "right_node", null);
}
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
leftIndex = ic.readInt("left_index", 0);
rightIndex = ic.readInt("right_index", 0);
leftPlane = ic.readFloat("left_plane", 0);
rightPlane = ic.readFloat("right_plane", 0);
axis = ic.readInt("axis", 0);
left = (BIHNode) ic.readSavable("left_node", null);
right = (BIHNode) ic.readSavable("right_node", null);
}
public static final class BIHStackData {
private final BIHNode node;
private final float min, max;
BIHStackData(BIHNode node, float min, float max) {
this.node = node;
this.min = min;
this.max = max;
}
}
public final int intersectWhere(Collidable col,
BoundingBox box,
Matrix4f worldMatrix,
BIHTree tree,
CollisionResults results) {
TempVars vars = TempVars.get();
ArrayList<BIHStackData> stack = vars.bihStack;
stack.clear();
float[] minExts = {box.getCenter().x - box.getXExtent(),
box.getCenter().y - box.getYExtent(),
box.getCenter().z - box.getZExtent()};
float[] maxExts = {box.getCenter().x + box.getXExtent(),
box.getCenter().y + box.getYExtent(),
box.getCenter().z + box.getZExtent()};
stack.add(new BIHStackData(this, 0, 0));
Triangle t = new Triangle();
int cols = 0;
stackloop:
while (stack.size() > 0) {
BIHNode node = stack.remove(stack.size() - 1).node;
while (node.axis != 3) {
int a = node.axis;
float maxExt = maxExts[a];
float minExt = minExts[a];
if (node.leftPlane < node.rightPlane) {
// means there's a gap in the middle
// if the box is in that gap, we stop there
if (minExt > node.leftPlane
&& maxExt < node.rightPlane) {
continue stackloop;
}
}
if (maxExt < node.rightPlane) {
node = node.left;
} else if (minExt > node.leftPlane) {
node = node.right;
} else {
stack.add(new BIHStackData(node.right, 0, 0));
node = node.left;
}
// if (maxExt < node.leftPlane
// && maxExt < node.rightPlane){
// node = node.left;
// }else if (minExt > node.leftPlane
// && minExt > node.rightPlane){
// node = node.right;
// }else{
// }
}
for (int i = node.leftIndex; i <= node.rightIndex; i++) {
tree.getTriangle(i, t.get1(), t.get2(), t.get3());
if (worldMatrix != null) {
worldMatrix.mult(t.get1(), t.get1());
worldMatrix.mult(t.get2(), t.get2());
worldMatrix.mult(t.get3(), t.get3());
}
int added = col.collideWith(t, results);
if (added > 0) {
int index = tree.getTriangleIndex(i);
int start = results.size() - added;
for (int j = start; j < results.size(); j++) {
CollisionResult cr = results.getCollisionDirect(j);
cr.setTriangleIndex(index);
}
cols += added;
}
}
}
vars.release();
return cols;
}
public final int intersectBrute(Ray r,
Matrix4f worldMatrix,
BIHTree tree,
float sceneMin,
float sceneMax,
CollisionResults results) {
float tHit = Float.POSITIVE_INFINITY;
TempVars vars = TempVars.get();
Vector3f v1 = vars.vect1,
v2 = vars.vect2,
v3 = vars.vect3;
int cols = 0;
ArrayList<BIHStackData> stack = vars.bihStack;
stack.clear();
stack.add(new BIHStackData(this, 0, 0));
stackloop:
while (stack.size() > 0) {
BIHStackData data = stack.remove(stack.size() - 1);
BIHNode node = data.node;
leafloop:
while (node.axis != 3) { // while node is not a leaf
BIHNode nearNode, farNode;
nearNode = node.left;
farNode = node.right;
stack.add(new BIHStackData(farNode, 0, 0));
node = nearNode;
}
// a leaf
for (int i = node.leftIndex; i <= node.rightIndex; i++) {
tree.getTriangle(i, v1, v2, v3);
if (worldMatrix != null) {
worldMatrix.mult(v1, v1);
worldMatrix.mult(v2, v2);
worldMatrix.mult(v3, v3);
}
float t = r.intersects(v1, v2, v3);
if (t < tHit) {
tHit = t;
Vector3f contactPoint = new Vector3f(r.direction).multLocal(tHit).addLocal(r.origin);
CollisionResult cr = new CollisionResult(contactPoint, tHit);
cr.setTriangleIndex(tree.getTriangleIndex(i));
results.addCollision(cr);
cols++;
}
}
}
vars.release();
return cols;
}
public final int intersectWhere(Ray r,
Matrix4f worldMatrix,
BIHTree tree,
float sceneMin,
float sceneMax,
CollisionResults results) {
TempVars vars = TempVars.get();
ArrayList<BIHStackData> stack = vars.bihStack;
stack.clear();
// float tHit = Float.POSITIVE_INFINITY;
Vector3f o = vars.vect1.set(r.getOrigin());
Vector3f d = vars.vect2.set(r.getDirection());
Matrix4f inv =vars.tempMat4.set(worldMatrix).invertLocal();
inv.mult(r.getOrigin(), r.getOrigin());
// Fixes rotation collision bug
inv.multNormal(r.getDirection(), r.getDirection());
// inv.multNormalAcross(r.getDirection(), r.getDirection());
float[] origins = {r.getOrigin().x,
r.getOrigin().y,
r.getOrigin().z};
float[] invDirections = {1f / r.getDirection().x,
1f / r.getDirection().y,
1f / r.getDirection().z};
r.getDirection().normalizeLocal();
Vector3f v1 = vars.vect3,
v2 = vars.vect4,
v3 = vars.vect5;
int cols = 0;
stack.add(new BIHStackData(this, sceneMin, sceneMax));
stackloop:
while (stack.size() > 0) {
BIHStackData data = stack.remove(stack.size() - 1);
BIHNode node = data.node;
float tMin = data.min,
tMax = data.max;
if (tMax < tMin) {
continue;
}
leafloop:
while (node.axis != 3) { // while node is not a leaf
int a = node.axis;
// find the origin and direction value for the given axis
float origin = origins[a];
float invDirection = invDirections[a];
float tNearSplit, tFarSplit;
BIHNode nearNode, farNode;
tNearSplit = (node.leftPlane - origin) * invDirection;
tFarSplit = (node.rightPlane - origin) * invDirection;
nearNode = node.left;
farNode = node.right;
if (invDirection < 0) {
float tmpSplit = tNearSplit;
tNearSplit = tFarSplit;
tFarSplit = tmpSplit;
BIHNode tmpNode = nearNode;
nearNode = farNode;
farNode = tmpNode;
}
if (tMin > tNearSplit && tMax < tFarSplit) {
continue stackloop;
}
if (tMin > tNearSplit) {
tMin = max(tMin, tFarSplit);
node = farNode;
} else if (tMax < tFarSplit) {
tMax = min(tMax, tNearSplit);
node = nearNode;
} else {
stack.add(new BIHStackData(farNode, max(tMin, tFarSplit), tMax));
tMax = min(tMax, tNearSplit);
node = nearNode;
}
}
// if ( (node.rightIndex - node.leftIndex) > minTrisPerNode){
// // on demand subdivision
// node.subdivide();
// stack.add(new BIHStackData(node, tMin, tMax));
// continue stackloop;
// }
// a leaf
for (int i = node.leftIndex; i <= node.rightIndex; i++) {
tree.getTriangle(i, v1, v2, v3);
float t = r.intersects(v1, v2, v3);
if (!Float.isInfinite(t)) {
if (worldMatrix != null) {
worldMatrix.mult(v1, v1);
worldMatrix.mult(v2, v2);
worldMatrix.mult(v3, v3);
float t_world = new Ray(o, d).intersects(v1, v2, v3);
t = t_world;
}
Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null);
Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o);
float worldSpaceDist = o.distance(contactPoint);
CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist);
cr.setContactNormal(contactNormal);
cr.setTriangleIndex(tree.getTriangleIndex(i));
results.addCollision(cr);
cols++;
}
}
}
vars.release();
r.setOrigin(o);
r.setDirection(d);
return cols;
}
}