/*
* __ .__ .__ ._____.
* _/ |_ _______ __|__| ____ | | |__\_ |__ ______
* \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/
* | | ( <_> > <| \ \___| |_| || \_\ \\___ \
* |__| \____/__/\_ \__|\___ >____/__||___ /____ >
* \/ \/ \/ \/
*
* Copyright (c) 2006-2011 Karsten Schmidt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* http://creativecommons.org/licenses/LGPL/2.1/
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
package spimedb.util.geom;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import spimedb.util.math.MathUtils;
/**
* Axis-aligned bounding box with basic intersection features for Ray, AABB and
* Sphere classes.
*/
//@XmlAccessorType(XmlAccessType.FIELD)
@JsonSerialize
public class AABB extends Vec3D implements BB {
// TODO extent as array
protected final Vec3D extent;
public AABB(float x, float y, float z) {
super(x, y, z);
extent = new Vec3D();
}
public AABB() {
this(Float.NaN, Float.NaN, Float.NaN);
}
@Override
public Vec3D getExtents() {
return extent;
}
/**
* Creates an independent copy of the passed in box
*
* @param box
*/
public AABB(BB box) {
this(box, box.getExtents());
}
public float minX() { return x - extent.x(); }
public float maxX() { return x + extent.x(); }
public float minY() { return y - extent.y(); }
public float maxY() { return y + extent.y(); }
public float minZ() { return z - extent.z(); }
public float maxZ() { return z + extent.z(); }
/**
* Creates a new box of the given size at the world origin.
*
* @param extent
*/
public AABB(float extent) {
this(new Vec3D(), new Vec3D(extent,extent,extent));
}
public AABB(float x, float y, float z, float extent) {
this();
set(x, y, z);
size(extent,extent,extent);
}
// /**
// * Creates a new instance from centre point and uniform extent in all
// * directions.
// *
// * @param center
// * @param extent half size, radius
// */
// public AABB(roVec3D center, float extent) {
// this(center, extent);
// }
/**
* Creates a new instance from centre point and extent
*
* @param center
* @param extent
* box dimensions (the box will be double the size in each
* direction)
*/
public AABB(XYZ center, XYZ extent) {
this();
set(center);
size(extent);
}
public final void size(XYZ extent) {
this.extent.set(extent);
}
public final void size(float x, float y, float z) {
this.extent.set(x, y, z);
}
public AABB copy() {
return new AABB(this);
}
// public final Vec3D getMax() {
// // return this.add(extent);
// return max.copy();
// }
//
// public final Vec3D getMin() {
// return min.copy();
// }
public XYZ getNormalForPoint(roVec3D p) {
p = p.sub(this);
Vec3D pabs = extent.sub(p.getAbs());
Vec3D psign = p.getSignum();
XYZ normal = X_AXIS.scale(psign.x);
float minDist = pabs.x;
if (pabs.y < minDist) {
minDist = pabs.y;
normal = Y_AXIS.scale(psign.y);
}
if (pabs.z < minDist) {
normal = Z_AXIS.scale(psign.z);
}
return normal;
}
// /**
// * Adjusts the box size and position such that it includes the given point.
// *
// * @param p
// * point to include
// * @return itself
// */
// public BB growToContainPoint(roVec3D p) {
// min.minSelf(p);
// max.maxSelf(p);
// set(min.interpolateTo(max, 0.5f));
// extent.set(max.sub(min).scaleSelf(0.5f));
// return this;
// }
// /**
// * Calculates intersection with the given ray between a certain distance
// * interval.
// *
// * Ray-box intersection is using IEEE numerical properties to ensure the
// * test is both robust and efficient, as described in:
// *
// * Amy Williams, Steve Barrus, R. Keith Morley, and Peter Shirley: "An
// * Efficient and Robust Ray-Box Intersection Algorithm" Journal of graphics
// * tools, 10(1):49-54, 2005
// *
// * @param ray
// * incident ray
// * @param minDist
// * @param maxDist
// * @return intersection point on the bounding box (only the first is
// * returned) or null if no intersection
// */
// public Vec3D intersectsRay(Ray3D ray, float minDist, float maxDist) {
// Vec3D invDir = ray.getDirection().reciprocal();
// boolean signDirX = invDir.x < 0;
// boolean signDirY = invDir.y < 0;
// boolean signDirZ = invDir.z < 0;
// Vec3D bbox = signDirX ? max : min;
// float tmin = (bbox.x - ray.x) * invDir.x;
// bbox = signDirX ? min : max;
// float tmax = (bbox.x - ray.x) * invDir.x;
// bbox = signDirY ? max : min;
// float tymin = (bbox.y - ray.y) * invDir.y;
// bbox = signDirY ? min : max;
// float tymax = (bbox.y - ray.y) * invDir.y;
// if ((tmin > tymax) || (tymin > tmax)) {
// return null;
// }
// if (tymin > tmin) {
// tmin = tymin;
// }
// if (tymax < tmax) {
// tmax = tymax;
// }
// bbox = signDirZ ? max : min;
// float tzmin = (bbox.z - ray.z) * invDir.z;
// bbox = signDirZ ? min : max;
// float tzmax = (bbox.z - ray.z) * invDir.z;
// if ((tmin > tzmax) || (tzmin > tmax)) {
// return null;
// }
// if (tzmin > tmin) {
// tmin = tzmin;
// }
// if (tzmax < tmax) {
// tmax = tzmax;
// }
// if ((tmin < maxDist) && (tmax > minDist)) {
// return ray.getPointAtDistance(tmin);
// }
// return null;
// }
public boolean intersectsTriangle(Triangle3D tri) {
// use separating axis theorem to test overlap between triangle and box
// need to test for overlap in these directions:
//
// 1) the {x,y,z}-directions (actually, since we use the AABB of the
// triangle
// we do not even need to test these)
// 2) normal of the triangle
// 3) crossproduct(edge from tri, {x,y,z}-directin)
// this gives 3x3=9 more tests
Vec3D v0, v1, v2;
Vec3D normal, e0, e1, e2, f;
// move everything so that the boxcenter is in (0,0,0)
v0 = tri.a.sub(this);
v1 = tri.b.sub(this);
v2 = tri.c.sub(this);
// compute triangle edges
e0 = v1.sub(v0);
e1 = v2.sub(v1);
e2 = v0.sub(v2);
// test the 9 tests first (this was faster)
f = e0.getAbs();
if (testAxis(e0.z, -e0.y, f.z, f.y, v0.y, v0.z, v2.y, v2.z, extent.y,
extent.z)) {
return false;
}
if (testAxis(-e0.z, e0.x, f.z, f.x, v0.x, v0.z, v2.x, v2.z, extent.x,
extent.z)) {
return false;
}
if (testAxis(e0.y, -e0.x, f.y, f.x, v1.x, v1.y, v2.x, v2.y, extent.x,
extent.y)) {
return false;
}
f = e1.getAbs();
if (testAxis(e1.z, -e1.y, f.z, f.y, v0.y, v0.z, v2.y, v2.z, extent.y,
extent.z)) {
return false;
}
if (testAxis(-e1.z, e1.x, f.z, f.x, v0.x, v0.z, v2.x, v2.z, extent.x,
extent.z)) {
return false;
}
if (testAxis(e1.y, -e1.x, f.y, f.x, v0.x, v0.y, v1.x, v1.y, extent.x,
extent.y)) {
return false;
}
f = e2.getAbs();
if (testAxis(e2.z, -e2.y, f.z, f.y, v0.y, v0.z, v1.y, v1.z, extent.y,
extent.z)) {
return false;
}
if (testAxis(-e2.z, e2.x, f.z, f.x, v0.x, v0.z, v1.x, v1.z, extent.x,
extent.z)) {
return false;
}
if (testAxis(e2.y, -e2.x, f.y, f.x, v1.x, v1.y, v2.x, v2.y, extent.x,
extent.y)) {
return false;
}
// first test overlap in the {x,y,z}-directions
// find min, max of the triangle each direction, and test for overlap in
// that direction -- this is equivalent to testing a minimal AABB around
// the triangle against the AABB
// test in X-direction
if (MathUtils.min(v0.x, v1.x, v2.x) > extent.x
|| MathUtils.max(v0.x, v1.x, v2.x) < -extent.x) {
return false;
}
// test in Y-direction
if (MathUtils.min(v0.y, v1.y, v2.y) > extent.y
|| MathUtils.max(v0.y, v1.y, v2.y) < -extent.y) {
return false;
}
// test in Z-direction
if (MathUtils.min(v0.z, v1.z, v2.z) > extent.z
|| MathUtils.max(v0.z, v1.z, v2.z) < -extent.z) {
return false;
}
// test if the box intersects the plane of the triangle
// compute plane equation of triangle: normal*x+d=0
normal = e0.cross(e1);
float d = -normal.dot(v0);
return planeBoxOverlap(normal, d, extent);
}
private static boolean planeBoxOverlap(Vec3D normal, float d, Vec3D maxbox) {
Vec3D vmin = new Vec3D();
Vec3D vmax = new Vec3D();
if (normal.x > 0.0f) {
vmin.x = -maxbox.x;
vmax.x = maxbox.x;
} else {
vmin.x = maxbox.x;
vmax.x = -maxbox.x;
}
if (normal.y > 0.0f) {
vmin.y = -maxbox.y;
vmax.y = maxbox.y;
} else {
vmin.y = maxbox.y;
vmax.y = -maxbox.y;
}
if (normal.z > 0.0f) {
vmin.z = -maxbox.z;
vmax.z = maxbox.z;
} else {
vmin.z = maxbox.z;
vmax.z = -maxbox.z;
}
if (normal.dot(vmin) + d > 0.0f) {
return false;
}
return normal.dot(vmax) + d >= 0.0f;
}
public AABB set(BB box) {
super.set(box);
extent.set(box.getExtents());
return this;
}
/**
* Updates the position of the box in space
* @see Vec3D#set(float, float, float)
*/
public AABB set(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
//updateBounds();
return this;
}
public final AABB set(XYZ v) {
return set(v.x(), v.y(), v.z());
}
private static boolean testAxis(float a, float b, float fa, float fb, float va,
float vb, float wa, float wb, float ea, float eb) {
float p0 = a * va + b * vb;
float p2 = a * wa + b * wb;
float min, max;
if (p0 < p2) {
min = p0;
max = p2;
} else {
min = p2;
max = p0;
}
float rad = fa * ea + fb * eb;
return (min > rad || max < -rad);
}
// public Mesh3D toMesh() {
// return toMesh(null);
// }
//
// public Mesh3D toMesh(Mesh3D mesh) {
// if (mesh == null) {
// mesh = new TriangleMesh("aabb", 8, 12);
// }
// Vec3D a = min;
// Vec3D g = max;
// Vec3D b = new Vec3D(a.x, a.y, g.z);
// Vec3D c = new Vec3D(g.x, a.y, g.z);
// Vec3D d = new Vec3D(g.x, a.y, a.z);
// Vec3D e = new Vec3D(a.x, g.y, a.z);
// Vec3D f = new Vec3D(a.x, g.y, g.z);
// Vec3D h = new Vec3D(g.x, g.y, a.z);
// Vec2D ua = new Vec2D(0, 0);
// Vec2D ub = new Vec2D(1, 0);
// Vec2D uc = new Vec2D(1, 1);
// Vec2D ud = new Vec2D(0, 1);
// // left
// mesh.addFace(a, b, f, ud, uc, ub);
// mesh.addFace(a, f, e, ud, ub, ua);
// // front
// mesh.addFace(b, c, g, ud, uc, ub);
// mesh.addFace(b, g, f, ud, ub, ua);
// // right
// mesh.addFace(c, d, h, ud, uc, ub);
// mesh.addFace(c, h, g, ud, ub, ua);
// // back
// mesh.addFace(d, a, e, ud, uc, ub);
// mesh.addFace(d, e, h, ud, ub, ua);
// // top
// mesh.addFace(e, f, h, ua, ud, ub);
// mesh.addFace(f, g, h, ud, uc, ub);
// // bottom
// mesh.addFace(a, d, b, ud, uc, ua);
// mesh.addFace(b, d, c, ua, uc, ub);
// return mesh;
// }
/*
* (non-Javadoc)
*
* @see toxi.geom.Vec3D#toString()
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("<aabb@").append(super.toString()).append('x')
.append(extent);
return sb.toString();
}
public final void setRangeZ(float min, float max) {
float mid = (min+max)/2f;
setZ(mid);
getExtents().setZ(Math.abs(max-min));
}
// public BB union(AABB box) {
// min.minSelf(box.getMin());
// max.maxSelf(box.getMax());
// set(min.interpolateTo(max, 0.5f));
// extent.set(max.sub(min).scaleSelf(0.5f));
// return this;
// }
// /**
// * Updates the min/max corner points of the box. MUST be called after moving
// * the box in space by manipulating the public x,y,z coordinates directly.
// *
// * @return itself
// */
// public final BB updateBounds() {
// // this is check is necessary for the constructor
// if (extent != null) {
// this.min = this.sub(extent);
// this.max = this.plus(extent);
// }
// return this;
// }
}