/* * Copyright (c) 2009-2013 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.bounding; import com.jme3.collision.Collidable; import com.jme3.collision.CollisionResult; 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.*; import com.jme3.scene.Mesh; import com.jme3.scene.Spatial; import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; //import com.jme.scene.TriMesh; /** * <code>BoundingBox</code> describes a bounding volume as an axis-aligned box. * <br> * Instances may be initialized by invoking the <code>containAABB</code> method. * * @author Joshua Slack * @version $Id: BoundingBox.java,v 1.50 2007/09/22 16:46:35 irrisor Exp $ */ public class BoundingBox extends BoundingVolume { /** * the X-extent of the box (>=0, may be +Infinity) */ float xExtent; /** * the Y-extent of the box (>=0, may be +Infinity) */ float yExtent; /** * the Z-extent of the box (>=0, may be +Infinity) */ float zExtent; /** * Instantiate a <code>BoundingBox</code> without initializing it. */ public BoundingBox() { } /** * Instantiate a <code>BoundingBox</code> with given center and extents. * * @param c the coordinates of the center of the box (not null, not altered) * @param x the X-extent of the box (>=0, may be +Infinity) * @param y the Y-extent of the box (>=0, may be +Infinity) * @param z the Z-extent of the box (>=0, may be +Infinity) */ public BoundingBox(Vector3f c, float x, float y, float z) { this.center.set(c); this.xExtent = x; this.yExtent = y; this.zExtent = z; } /** * Instantiate a <code>BoundingBox</code> equivalent to an existing box. * * @param source the existing box (not null, not altered) */ public BoundingBox(BoundingBox source) { this.center.set(source.center); this.xExtent = source.xExtent; this.yExtent = source.yExtent; this.zExtent = source.zExtent; } public BoundingBox(Vector3f min, Vector3f max) { setMinMax(min, max); } public Type getType() { return Type.AABB; } /** * <code>computeFromPoints</code> creates a new Bounding Box from a given * set of points. It uses the <code>containAABB</code> method as default. * * @param points * the points to contain. */ public void computeFromPoints(FloatBuffer points) { containAABB(points); } /** * <code>computeFromTris</code> creates a new Bounding Box from a given * set of triangles. It is used in OBBTree calculations. * * @param tris * @param start * @param end */ public void computeFromTris(Triangle[] tris, int start, int end) { if (end - start <= 0) { return; } 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 point; for (int i = start; i < end; i++) { point = tris[i].get(0); checkMinMax(min, max, point); point = tris[i].get(1); checkMinMax(min, max, point); point = tris[i].get(2); checkMinMax(min, max, point); } center.set(min.addLocal(max)); center.multLocal(0.5f); xExtent = max.x - center.x; yExtent = max.y - center.y; zExtent = max.z - center.z; vars.release(); } public void computeFromTris(int[] indices, Mesh mesh, int start, int end) { if (end - start <= 0) { return; } TempVars vars = TempVars.get(); Vector3f vect1 = vars.vect1; Vector3f vect2 = vars.vect2; Triangle triangle = vars.triangle; Vector3f min = vect1.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); Vector3f max = vect2.set(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); Vector3f point; for (int i = start; i < end; i++) { mesh.getTriangle(indices[i], triangle); point = triangle.get(0); checkMinMax(min, max, point); point = triangle.get(1); checkMinMax(min, max, point); point = triangle.get(2); checkMinMax(min, max, point); } center.set(min.addLocal(max)); center.multLocal(0.5f); xExtent = max.x - center.x; yExtent = max.y - center.y; zExtent = max.z - center.z; vars.release(); } public static void checkMinMax(Vector3f min, Vector3f max, Vector3f point) { if (point.x < min.x) { min.x = point.x; } if (point.x > max.x) { max.x = point.x; } if (point.y < min.y) { min.y = point.y; } if (point.y > max.y) { max.y = point.y; } if (point.z < min.z) { min.z = point.z; } if (point.z > max.z) { max.z = point.z; } } /** * <code>containAABB</code> creates a minimum-volume axis-aligned bounding * box of the points, then selects the smallest enclosing sphere of the box * with the sphere centered at the boxes center. * * @param points * the list of points. */ public void containAABB(FloatBuffer points) { if (points == null) { return; } points.rewind(); if (points.remaining() <= 2) // we need at least a 3 float vector { return; } TempVars vars = TempVars.get(); float[] tmpArray = vars.skinPositions; float minX = Float.POSITIVE_INFINITY, minY = Float.POSITIVE_INFINITY, minZ = Float.POSITIVE_INFINITY; float maxX = Float.NEGATIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY, maxZ = Float.NEGATIVE_INFINITY; int iterations = (int) FastMath.ceil(points.limit() / ((float) tmpArray.length)); for (int i = iterations - 1; i >= 0; i--) { int bufLength = Math.min(tmpArray.length, points.remaining()); points.get(tmpArray, 0, bufLength); for (int j = 0; j < bufLength; j += 3) { vars.vect1.x = tmpArray[j]; vars.vect1.y = tmpArray[j+1]; vars.vect1.z = tmpArray[j+2]; if (vars.vect1.x < minX) { minX = vars.vect1.x; } if (vars.vect1.x > maxX) { maxX = vars.vect1.x; } if (vars.vect1.y < minY) { minY = vars.vect1.y; } if (vars.vect1.y > maxY) { maxY = vars.vect1.y; } if (vars.vect1.z < minZ) { minZ = vars.vect1.z; } if (vars.vect1.z > maxZ) { maxZ = vars.vect1.z; } } } vars.release(); center.set(minX + maxX, minY + maxY, minZ + maxZ); center.multLocal(0.5f); xExtent = maxX - center.x; yExtent = maxY - center.y; zExtent = maxZ - center.z; } /** * <code>transform</code> modifies the center of the box to reflect the * change made via a rotation, translation and scale. * * @param trans * the transform to apply * @param store * box to store result in */ public BoundingVolume transform(Transform trans, BoundingVolume store) { BoundingBox box; if (store == null || store.getType() != Type.AABB) { box = new BoundingBox(); } else { box = (BoundingBox) store; } center.mult(trans.getScale(), box.center); trans.getRotation().mult(box.center, box.center); box.center.addLocal(trans.getTranslation()); TempVars vars = TempVars.get(); Matrix3f transMatrix = vars.tempMat3; transMatrix.set(trans.getRotation()); // Make the rotation matrix all positive to get the maximum x/y/z extent transMatrix.absoluteLocal(); Vector3f scale = trans.getScale(); vars.vect1.set(xExtent * FastMath.abs(scale.x), yExtent * FastMath.abs(scale.y), zExtent * FastMath.abs(scale.z)); transMatrix.mult(vars.vect1, vars.vect2); // Assign the biggest rotations after scales. box.xExtent = FastMath.abs(vars.vect2.getX()); box.yExtent = FastMath.abs(vars.vect2.getY()); box.zExtent = FastMath.abs(vars.vect2.getZ()); vars.release(); return box; } public BoundingVolume transform(Matrix4f trans, BoundingVolume store) { BoundingBox box; if (store == null || store.getType() != Type.AABB) { box = new BoundingBox(); } else { box = (BoundingBox) store; } TempVars vars = TempVars.get(); float w = trans.multProj(center, box.center); box.center.divideLocal(w); Matrix3f transMatrix = vars.tempMat3; trans.toRotationMatrix(transMatrix); // Make the rotation matrix all positive to get the maximum x/y/z extent transMatrix.absoluteLocal(); vars.vect1.set(xExtent, yExtent, zExtent); transMatrix.mult(vars.vect1, vars.vect1); // Assign the biggest rotations after scales. box.xExtent = FastMath.abs(vars.vect1.getX()); box.yExtent = FastMath.abs(vars.vect1.getY()); box.zExtent = FastMath.abs(vars.vect1.getZ()); vars.release(); return box; } /** * <code>whichSide</code> takes a plane (typically provided by a view * frustum) to determine which side this bound is on. * * @param plane * the plane to check against. */ public Plane.Side whichSide(Plane plane) { float radius = FastMath.abs(xExtent * plane.getNormal().getX()) + FastMath.abs(yExtent * plane.getNormal().getY()) + FastMath.abs(zExtent * plane.getNormal().getZ()); float distance = plane.pseudoDistance(center); //changed to < and > to prevent floating point precision problems if (distance < -radius) { return Plane.Side.Negative; } else if (distance > radius) { return Plane.Side.Positive; } else { return Plane.Side.None; } } /** * <code>merge</code> combines this bounding box locally with a second * bounding volume. The result contains both the original box and the second * volume. * * @param volume the bounding volume to combine with this box (or null) (not * altered) * @return this box (with its components modified) or null if the second * volume is of some type other than AABB or Sphere */ public BoundingVolume merge(BoundingVolume volume) { return mergeLocal(volume); } /** * <code>mergeLocal</code> combines this bounding box locally with a second * bounding volume. The result contains both the original box and the second * volume. * * @param volume the bounding volume to combine with this box (or null) (not * altered) * @return this box (with its components modified) or null if the second * volume is of some type other than AABB or Sphere */ public BoundingVolume mergeLocal(BoundingVolume volume) { if (volume == null) { return this; } switch (volume.getType()) { case AABB: BoundingBox vBox = (BoundingBox) volume; return mergeLocal(vBox.center, vBox.xExtent, vBox.yExtent, vBox.zExtent); case Sphere: BoundingSphere vSphere = (BoundingSphere) volume; return mergeLocal(vSphere.center, vSphere.radius, vSphere.radius, vSphere.radius); // case OBB: { // return mergeOBB((OrientedBoundingBox) volume); // } default: return null; } } /** * Merges this AABB with the given OBB. * * @param volume * the OBB to merge this AABB with. * @return This AABB extended to fit the given OBB. */ // private BoundingBox mergeOBB(OrientedBoundingBox volume) { // if (!volume.correctCorners) // volume.computeCorners(); // // TempVars vars = TempVars.get(); // Vector3f min = vars.compVect1.set(center.x - xExtent, center.y - yExtent, // center.z - zExtent); // Vector3f max = vars.compVect2.set(center.x + xExtent, center.y + yExtent, // center.z + zExtent); // // for (int i = 1; i < volume.vectorStore.length; i++) { // Vector3f temp = volume.vectorStore[i]; // if (temp.x < min.x) // min.x = temp.x; // else if (temp.x > max.x) // max.x = temp.x; // // if (temp.y < min.y) // min.y = temp.y; // else if (temp.y > max.y) // max.y = temp.y; // // if (temp.z < min.z) // min.z = temp.z; // else if (temp.z > max.z) // max.z = temp.z; // } // // center.set(min.addLocal(max)); // center.multLocal(0.5f); // // xExtent = max.x - center.x; // yExtent = max.y - center.y; // zExtent = max.z - center.z; // return this; // } /** * <code>mergeLocal</code> combines this bounding box locally with a second * bounding box described by its center and extents. * * @param c the center of the second box (not null, not altered) * @param x the X-extent of the second box * @param y the Y-extent of the second box * @param z the Z-extent of the second box * @return the resulting merged box. */ private BoundingBox mergeLocal(Vector3f c, float x, float y, float z) { if (xExtent == Float.POSITIVE_INFINITY || x == Float.POSITIVE_INFINITY) { center.x = 0; xExtent = Float.POSITIVE_INFINITY; } else { float low = center.x - xExtent; if (low > c.x - x) { low = c.x - x; } float high = center.x + xExtent; if (high < c.x + x) { high = c.x + x; } center.x = (low + high) / 2; xExtent = high - center.x; } if (yExtent == Float.POSITIVE_INFINITY || y == Float.POSITIVE_INFINITY) { center.y = 0; yExtent = Float.POSITIVE_INFINITY; } else { float low = center.y - yExtent; if (low > c.y - y) { low = c.y - y; } float high = center.y + yExtent; if (high < c.y + y) { high = c.y + y; } center.y = (low + high) / 2; yExtent = high - center.y; } if (zExtent == Float.POSITIVE_INFINITY || z == Float.POSITIVE_INFINITY) { center.z = 0; zExtent = Float.POSITIVE_INFINITY; } else { float low = center.z - zExtent; if (low > c.z - z) { low = c.z - z; } float high = center.z + zExtent; if (high < c.z + z) { high = c.z + z; } center.z = (low + high) / 2; zExtent = high - center.z; } return this; } /** * <code>clone</code> creates a new BoundingBox object containing the same * data as this one. * * @param store * where to store the cloned information. if null or wrong class, * a new store is created. * @return the new BoundingBox */ public BoundingVolume clone(BoundingVolume store) { if (store != null && store.getType() == Type.AABB) { BoundingBox rVal = (BoundingBox) store; rVal.center.set(center); rVal.xExtent = xExtent; rVal.yExtent = yExtent; rVal.zExtent = zExtent; rVal.checkPlane = checkPlane; return rVal; } BoundingBox rVal = new BoundingBox(center.clone(), xExtent, yExtent, zExtent); return rVal; } /** * <code>toString</code> returns the string representation of this object. * The form is: "[Center: <Vector> xExtent: X.XX yExtent: Y.YY zExtent: * Z.ZZ]". * * @return the string representation of this. */ @Override public String toString() { return getClass().getSimpleName() + " [Center: " + center + " xExtent: " + xExtent + " yExtent: " + yExtent + " zExtent: " + zExtent + "]"; } /** * intersects determines if this Bounding Box intersects with another given * bounding volume. If so, true is returned, otherwise, false is returned. * * @see BoundingVolume#intersects(com.jme3.bounding.BoundingVolume) */ public boolean intersects(BoundingVolume bv) { return bv.intersectsBoundingBox(this); } /** * determines if this bounding box intersects a given bounding sphere. * * @see BoundingVolume#intersectsSphere(com.jme3.bounding.BoundingSphere) */ public boolean intersectsSphere(BoundingSphere bs) { return bs.intersectsBoundingBox(this); } /** * determines if this bounding box intersects a given bounding box. If the * two boxes intersect in any way, true is returned. Otherwise, false is * returned. * * @see BoundingVolume#intersectsBoundingBox(com.jme3.bounding.BoundingBox) */ public boolean intersectsBoundingBox(BoundingBox bb) { assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bb.center); if (center.x + xExtent < bb.center.x - bb.xExtent || center.x - xExtent > bb.center.x + bb.xExtent) { return false; } else if (center.y + yExtent < bb.center.y - bb.yExtent || center.y - yExtent > bb.center.y + bb.yExtent) { return false; } else if (center.z + zExtent < bb.center.z - bb.zExtent || center.z - zExtent > bb.center.z + bb.zExtent) { return false; } else { return true; } } /** * determines if this bounding box intersects with a given oriented bounding * box. * * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox) */ // public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) { // return obb.intersectsBoundingBox(this); // } /** * determines if this bounding box intersects with a given ray object. If an * intersection has occurred, true is returned, otherwise false is returned. * * @see BoundingVolume#intersects(com.jme3.math.Ray) */ public boolean intersects(Ray ray) { assert Vector3f.isValidVector(center); float rhs; TempVars vars = TempVars.get(); Vector3f diff = ray.origin.subtract(getCenter(vars.vect2), vars.vect1); final float[] fWdU = vars.fWdU; final float[] fAWdU = vars.fAWdU; final float[] fDdU = vars.fDdU; final float[] fADdU = vars.fADdU; final float[] fAWxDdU = vars.fAWxDdU; fWdU[0] = ray.getDirection().dot(Vector3f.UNIT_X); fAWdU[0] = FastMath.abs(fWdU[0]); fDdU[0] = diff.dot(Vector3f.UNIT_X); fADdU[0] = FastMath.abs(fDdU[0]); if (fADdU[0] > xExtent && fDdU[0] * fWdU[0] >= 0.0) { vars.release(); return false; } fWdU[1] = ray.getDirection().dot(Vector3f.UNIT_Y); fAWdU[1] = FastMath.abs(fWdU[1]); fDdU[1] = diff.dot(Vector3f.UNIT_Y); fADdU[1] = FastMath.abs(fDdU[1]); if (fADdU[1] > yExtent && fDdU[1] * fWdU[1] >= 0.0) { vars.release(); return false; } fWdU[2] = ray.getDirection().dot(Vector3f.UNIT_Z); fAWdU[2] = FastMath.abs(fWdU[2]); fDdU[2] = diff.dot(Vector3f.UNIT_Z); fADdU[2] = FastMath.abs(fDdU[2]); if (fADdU[2] > zExtent && fDdU[2] * fWdU[2] >= 0.0) { vars.release(); return false; } Vector3f wCrossD = ray.getDirection().cross(diff, vars.vect2); fAWxDdU[0] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_X)); rhs = yExtent * fAWdU[2] + zExtent * fAWdU[1]; if (fAWxDdU[0] > rhs) { vars.release(); return false; } fAWxDdU[1] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Y)); rhs = xExtent * fAWdU[2] + zExtent * fAWdU[0]; if (fAWxDdU[1] > rhs) { vars.release(); return false; } fAWxDdU[2] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Z)); rhs = xExtent * fAWdU[1] + yExtent * fAWdU[0]; if (fAWxDdU[2] > rhs) { vars.release(); return false; } vars.release(); return true; } /** * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray) */ private int collideWithRay(Ray ray, CollisionResults results) { TempVars vars = TempVars.get(); try { Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center); Vector3f direction = vars.vect2.set(ray.direction); //float[] t = {0f, Float.POSITIVE_INFINITY}; float[] t = vars.fWdU; // use one of the tempvars arrays t[0] = 0; t[1] = Float.POSITIVE_INFINITY; float saveT0 = t[0], saveT1 = t[1]; boolean notEntirelyClipped = clip(+direction.x, -diff.x - xExtent, t) && clip(-direction.x, +diff.x - xExtent, t) && clip(+direction.y, -diff.y - yExtent, t) && clip(-direction.y, +diff.y - yExtent, t) && clip(+direction.z, -diff.z - zExtent, t) && clip(-direction.z, +diff.z - zExtent, t); if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) { if (t[1] > t[0]) { float[] distances = t; Vector3f point0 = new Vector3f(ray.direction).multLocal(distances[0]).addLocal(ray.origin); Vector3f point1 = new Vector3f(ray.direction).multLocal(distances[1]).addLocal(ray.origin); CollisionResult result = new CollisionResult(point0, distances[0]); results.addCollision(result); result = new CollisionResult(point1, distances[1]); results.addCollision(result); return 2; } Vector3f point = new Vector3f(ray.direction).multLocal(t[0]).addLocal(ray.origin); CollisionResult result = new CollisionResult(point, t[0]); results.addCollision(result); return 1; } return 0; } finally { vars.release(); } } private int collideWithRay(Ray ray) { TempVars vars = TempVars.get(); try { Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center); Vector3f direction = vars.vect2.set(ray.direction); //float[] t = {0f, Float.POSITIVE_INFINITY}; float[] t = vars.fWdU; // use one of the tempvars arrays t[0] = 0; t[1] = Float.POSITIVE_INFINITY; float saveT0 = t[0], saveT1 = t[1]; boolean notEntirelyClipped = clip(+direction.x, -diff.x - xExtent, t) && clip(-direction.x, +diff.x - xExtent, t) && clip(+direction.y, -diff.y - yExtent, t) && clip(-direction.y, +diff.y - yExtent, t) && clip(+direction.z, -diff.z - zExtent, t) && clip(-direction.z, +diff.z - zExtent, t); if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) { if (t[1] > t[0]) return 2; else return 1; } return 0; } finally { vars.release(); } } @Override public int collideWith(Collidable other, CollisionResults results) { if (other instanceof Ray) { Ray ray = (Ray) other; return collideWithRay(ray, results); } else if (other instanceof Triangle) { Triangle t = (Triangle) other; if (intersects(t.get1(), t.get2(), t.get3())) { CollisionResult r = new CollisionResult(); results.addCollision(r); return 1; } return 0; } else if (other instanceof BoundingVolume) { if (intersects((BoundingVolume) other)) { CollisionResult r = new CollisionResult(); results.addCollision(r); return 1; } return 0; } else if (other instanceof Spatial) { return ((Spatial)other).collideWith(this, results); } else { throw new UnsupportedCollisionException("With: " + other.getClass().getSimpleName()); } } @Override public int collideWith(Collidable other) { if (other instanceof Ray) { Ray ray = (Ray) other; return collideWithRay(ray); } else if (other instanceof Triangle) { Triangle t = (Triangle) other; if (intersects(t.get1(), t.get2(), t.get3())) { return 1; } return 0; } else if (other instanceof BoundingVolume) { return intersects((BoundingVolume) other) ? 1 : 0; } else { throw new UnsupportedCollisionException("With: " + other.getClass().getSimpleName()); } } /** * C code ported from <a href="http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/tribox3.txt"> * http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/tribox3.txt</a> * * @param v1 The first point in the triangle * @param v2 The second point in the triangle * @param v3 The third point in the triangle * @return True if the bounding box intersects the triangle, false * otherwise. */ public boolean intersects(Vector3f v1, Vector3f v2, Vector3f v3) { return Intersection.intersect(this, v1, v2, v3); } @Override public boolean contains(Vector3f point) { return FastMath.abs(center.x - point.x) < xExtent && FastMath.abs(center.y - point.y) < yExtent && FastMath.abs(center.z - point.z) < zExtent; } @Override public boolean intersects(Vector3f point) { return FastMath.abs(center.x - point.x) <= xExtent && FastMath.abs(center.y - point.y) <= yExtent && FastMath.abs(center.z - point.z) <= zExtent; } public float distanceToEdge(Vector3f point) { // compute coordinates of point in box coordinate system TempVars vars= TempVars.get(); Vector3f closest = vars.vect1; point.subtract(center,closest); // project test point onto box float sqrDistance = 0.0f; float delta; if (closest.x < -xExtent) { delta = closest.x + xExtent; sqrDistance += delta * delta; closest.x = -xExtent; } else if (closest.x > xExtent) { delta = closest.x - xExtent; sqrDistance += delta * delta; closest.x = xExtent; } if (closest.y < -yExtent) { delta = closest.y + yExtent; sqrDistance += delta * delta; closest.y = -yExtent; } else if (closest.y > yExtent) { delta = closest.y - yExtent; sqrDistance += delta * delta; closest.y = yExtent; } if (closest.z < -zExtent) { delta = closest.z + zExtent; sqrDistance += delta * delta; closest.z = -zExtent; } else if (closest.z > zExtent) { delta = closest.z - zExtent; sqrDistance += delta * delta; closest.z = zExtent; } vars.release(); return FastMath.sqrt(sqrDistance); } /** * <code>clip</code> determines if a line segment intersects the current * test plane. * * @param denom * the denominator of the line segment. * @param numer * the numerator of the line segment. * @param t * test values of the plane. * @return true if the line segment intersects the plane, false otherwise. */ private boolean clip(float denom, float numer, float[] t) { // Return value is 'true' if line segment intersects the current test // plane. Otherwise 'false' is returned in which case the line segment // is entirely clipped. if (denom > 0.0f) { // This is the old if statement... // if (numer > denom * t[1]) { // // The problem is that what is actually stored is // numer/denom. In non-floating point, this math should // work out the same but in floating point there can // be subtle math errors. The multiply will exaggerate // errors that may have been introduced when the value // was originally divided. // // This is especially true when the bounding box has zero // extents in some plane because the error rate is critical. // comparing a to b * c is not the same as comparing a/b to c // in this case. In fact, I tried converting this method to // double and the and the error was in the last decimal place. // // So, instead, we now compare the divided version to the divided // version. We lose some slight performance here as divide // will be more expensive than the divide. Some microbenchmarks // show divide to be 3x slower than multiple on Java 1.6. // BUT... we also saved a multiply in the non-clipped case because // we can reuse the divided version in both if checks. // I think it's better to be right in this case. // // Bug that I'm fixing: rays going right through quads at certain // angles and distances because they fail the bounding box test. // Many Bothans died bring you this fix. // -pspeed float newT = numer / denom; if (newT > t[1]) { return false; } if (newT > t[0]) { t[0] = newT; } return true; } else if (denom < 0.0f) { // Old if statement... see above // if (numer > denom * t[0]) { // // Note though that denom is always negative in this block. // When we move it over to the other side we have to flip // the comparison. Algebra for the win. float newT = numer / denom; if (newT < t[0]) { return false; } if (newT < t[1]) { t[1] = newT; } return true; } else { return numer <= 0.0; } } /** * Query extent. * * @param store * where extent gets stored - null to return a new vector * @return store / new vector */ public Vector3f getExtent(Vector3f store) { if (store == null) { store = new Vector3f(); } store.set(xExtent, yExtent, zExtent); return store; } public float getXExtent() { return xExtent; } public float getYExtent() { return yExtent; } public float getZExtent() { return zExtent; } public void setXExtent(float xExtent) { if (xExtent < 0) { throw new IllegalArgumentException(); } this.xExtent = xExtent; } public void setYExtent(float yExtent) { if (yExtent < 0) { throw new IllegalArgumentException(); } this.yExtent = yExtent; } public void setZExtent(float zExtent) { if (zExtent < 0) { throw new IllegalArgumentException(); } this.zExtent = zExtent; } public Vector3f getMin(Vector3f store) { if (store == null) { store = new Vector3f(); } store.set(center).subtractLocal(xExtent, yExtent, zExtent); return store; } public Vector3f getMax(Vector3f store) { if (store == null) { store = new Vector3f(); } store.set(center).addLocal(xExtent, yExtent, zExtent); return store; } public void setMinMax(Vector3f min, Vector3f max) { this.center.set(max).addLocal(min).multLocal(0.5f); xExtent = FastMath.abs(max.x - center.x); yExtent = FastMath.abs(max.y - center.y); zExtent = FastMath.abs(max.z - center.z); } @Override public void write(JmeExporter e) throws IOException { super.write(e); OutputCapsule capsule = e.getCapsule(this); capsule.write(xExtent, "xExtent", 0); capsule.write(yExtent, "yExtent", 0); capsule.write(zExtent, "zExtent", 0); } @Override public void read(JmeImporter e) throws IOException { super.read(e); InputCapsule capsule = e.getCapsule(this); xExtent = capsule.readFloat("xExtent", 0); yExtent = capsule.readFloat("yExtent", 0); zExtent = capsule.readFloat("zExtent", 0); } @Override public float getVolume() { return (8 * xExtent * yExtent * zExtent); } }