| /* |
| * 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; |
| } |
| } |