Package toxi.geom

Source Code of toxi.geom.AABB

/*
*   __               .__       .__  ._____.          
* _/  |_  _______  __|__| ____ |  | |__\_ |__   ______
* \   __\/  _ \  \/  /  |/ ___\|  | |  || __ \ /  ___/
*  |  | (  <_> >    <|  \  \___|  |_|  || \_\ \\___ \
*  |__|  \____/__/\_ \__|\___  >____/__||___  /____  >
*                   \/       \/             \/     \/
*
* 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 toxi.geom;

import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;

import toxi.geom.mesh.Mesh3D;
import toxi.geom.mesh.TriangleMesh;
import toxi.math.MathUtils;

/**
* Axis-aligned bounding box with basic intersection features for Ray, AABB and
* Sphere classes.
*/
@XmlAccessorType(XmlAccessType.FIELD)
public class AABB extends Vec3D implements Shape3D {

    /**
     * Creates a new instance from two vectors specifying opposite corners of
     * the box
     *
     * @param min
     *            first corner point
     * @param max
     *            second corner point
     * @return new AABB with centre at the half point between the 2 input
     *         vectors
     */
    public static final AABB fromMinMax(Vec3D min, Vec3D max) {
        Vec3D a = Vec3D.min(min, max);
        Vec3D b = Vec3D.max(min, max);
        return new AABB(a.interpolateTo(b, 0.5f), b.sub(a).scaleSelf(0.5f));
    }

    /**
     * Factory method, computes & returns the bounding box for the given list of
     * points.
     *
     * @param points
     * @return bounding rect
     */
    public static final AABB getBoundingBox(List<? extends Vec3D> points) {
        if (points == null || points.size() == 0) {
            return null;
        }
        Vec3D first = points.get(0);
        Vec3D min = first.copy();
        Vec3D max = first.copy();
        for (Vec3D p : points) {
            min.minSelf(p);
            max.maxSelf(p);
        }
        return fromMinMax(min, max);
    }

    @XmlElement(required = true)
    protected Vec3D extent;

    @XmlTransient
    protected Vec3D min, max;

    public AABB() {
        super();
        setExtent(new Vec3D());
    }

    /**
     * Creates an independent copy of the passed in box
     *
     * @param box
     */
    public AABB(AABB box) {
        this(box, box.getExtent());
    }

    /**
     * Creates a new box of the given size at the world origin.
     *
     * @param extent
     */
    public AABB(float extent) {
        this(new Vec3D(), extent);
    }

    /**
     * Creates a new instance from centre point and uniform extent in all
     * directions.
     *
     * @param pos
     * @param extent
     */
    public AABB(ReadonlyVec3D pos, float extent) {
        super(pos);
        setExtent(new Vec3D(extent, extent, extent));
    }

    /**
     * Creates a new instance from centre point and extent
     *
     * @param pos
     * @param extent
     *            box dimensions (the box will be double the size in each
     *            direction)
     */
    public AABB(ReadonlyVec3D pos, ReadonlyVec3D extent) {
        super(pos);
        setExtent(extent);
    }

    public boolean containsPoint(ReadonlyVec3D p) {
        return p.isInAABB(this);
    }

    public AABB copy() {
        return new AABB(this);
    }

    public Sphere getBoundingSphere() {
        return new Sphere(this, extent.magnitude());
    }

    /**
     * Returns the current box size as new Vec3D instance (updating this vector
     * will NOT update the box size! Use {@link #setExtent(ReadonlyVec3D)} for
     * those purposes)
     *
     * @return box size
     */
    public final Vec3D getExtent() {
        return extent.copy();
    }

    public final Vec3D getMax() {
        // return this.add(extent);
        return max.copy();
    }

    public final Vec3D getMin() {
        return min.copy();
    }

    public Vec3D getNormalForPoint(ReadonlyVec3D p) {
        p = p.sub(this);
        Vec3D pabs = extent.sub(p.getAbs());
        Vec3D psign = p.getSignum();
        Vec3D normal = Vec3D.X_AXIS.scale(psign.x);
        float minDist = pabs.x;
        if (pabs.y < minDist) {
            minDist = pabs.y;
            normal = Vec3D.Y_AXIS.scale(psign.y);
        }
        if (pabs.z < minDist) {
            normal = Vec3D.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 AABB growToContainPoint(ReadonlyVec3D p) {
        min.minSelf(p);
        max.maxSelf(p);
        set(min.interpolateTo(max, 0.5f));
        extent.set(max.sub(min).scaleSelf(0.5f));
        return this;
    }

    /**
     * Checks if the box intersects the passed in one.
     *
     * @param box
     *            box to check
     * @return true, if boxes overlap
     */
    public boolean intersectsBox(AABB box) {
        Vec3D t = box.sub(this);
        return MathUtils.abs(t.x) <= (extent.x + box.extent.x)
                && MathUtils.abs(t.y) <= (extent.y + box.extent.y)
                && MathUtils.abs(t.z) <= (extent.z + box.extent.z);
    }

    /**
     * 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 intersectsSphere(Sphere s) {
        return intersectsSphere(s, s.radius);
    }

    /**
     * @param c
     *            sphere centre
     * @param r
     *            sphere radius
     * @return true, if AABB intersects with sphere
     */
    public boolean intersectsSphere(Vec3D c, float r) {
        float s, d = 0;
        // find the square of the distance
        // from the sphere to the box
        if (c.x < min.x) {
            s = c.x - min.x;
            d = s * s;
        } else if (c.x > max.x) {
            s = c.x - max.x;
            d += s * s;
        }

        if (c.y < min.y) {
            s = c.y - min.y;
            d += s * s;
        } else if (c.y > max.y) {
            s = c.y - max.y;
            d += s * s;
        }

        if (c.z < min.z) {
            s = c.z - min.z;
            d += s * s;
        } else if (c.z > max.z) {
            s = c.z - max.z;
            d += s * s;
        }

        return d <= r * r;
    }

    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);
        if (!planeBoxOverlap(normal, d, extent)) {
            return false;
        }
        return true;
    }

    private 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;
        }
        if (normal.dot(vmax) + d >= 0.0f) {
            return true;
        }
        return false;
    }

    public AABB set(AABB box) {
        extent.set(box.extent);
        return set((ReadonlyVec3D) box);
    }

    /**
     * Updates the position of the box in space and calls
     * {@link #updateBounds()} immediately
     *
     * @see toxi.geom.Vec3D#set(float, float, float)
     */
    public Vec3D set(float x, float y, float z) {
        this.x = x;
        this.y = y;
        this.z = z;
        updateBounds();
        return this;
    }

    /**
     * Updates the position of the box in space and calls
     * {@link #updateBounds()} immediately
     *
     * @see toxi.geom.Vec3D#set(toxi.geom.Vec3D)
     */
    public AABB set(ReadonlyVec3D v) {
        x = v.x();
        y = v.y();
        z = v.z();
        updateBounds();
        return this;
    }

    /**
     * Updates the size of the box and calls {@link #updateBounds()} immediately
     *
     * @param extent
     *            new box size
     * @return itself, for method chaining
     */
    public AABB setExtent(ReadonlyVec3D extent) {
        this.extent = extent.copy();
        return updateBounds();
    }

    private 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() {
        StringBuffer sb = new StringBuffer();
        sb.append("<aabb> pos: ").append(super.toString()).append(" ext: ")
                .append(extent);
        return sb.toString();
    }

    public AABB 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 AABB updateBounds() {
        // this is check is necessary for the constructor
        if (extent != null) {
            this.min = this.sub(extent);
            this.max = this.add(extent);
        }
        return this;
    }
}
TOP

Related Classes of toxi.geom.AABB

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.