/*
* (C) Copyright 2015-2017 Kai Burjack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package org.joml;
/**
* This is an implementation of the <a
* href="http://www.cg.cs.tu-bs.de/media/publications/fast-rayaxis-aligned-bounding-box-overlap-tests-using-ray-slopes.pdf">Fast Ray/Axis-Aligned Bounding Box
* Overlap Tests using Ray Slopes</a> paper.
* <p>
* It is an efficient implementation when testing many axis-aligned boxes against the same ray.
* <p>
* This class is thread-safe and can be used in a multithreaded environment when testing many axis-aligned boxes against the same ray concurrently.
*
* @author Kai Burjack
*/
public class RayAabIntersection {
private float originX, originY, originZ;
private float dirX, dirY, dirZ;
/* Needed for ray slope intersection method */
private float c_xy, c_yx, c_zy, c_yz, c_xz, c_zx;
private float s_xy, s_yx, s_zy, s_yz, s_xz, s_zx;
private byte classification;
/**
* Create a new {@link RayAabIntersection} without initializing a ray.
* <p>
* Before using the {@link #test(float, float, float, float, float, float) intersect()} method,
* the method {@link #set(float, float, float, float, float, float) set()} must be called in order to
* initialize the created RayAabIntersection instance with a ray.
*
* @see #set(float, float, float, float, float, float)
*/
public RayAabIntersection() {
}
/**
* Create a new {@link RayAabIntersection} and initialize it with a ray with origin <tt>(originX, originY, originZ)</tt>
* and direction <tt>(dirX, dirY, dirZ)</tt>.
* <p>
* In order to change the direction and/or origin of the ray later, use {@link #set(float, float, float, float, float, float) set()}.
*
* @see #set(float, float, float, float, float, float)
*
* @param originX
* the x coordinate of the origin
* @param originY
* the y coordinate of the origin
* @param originZ
* the z coordinate of the origin
* @param dirX
* the x coordinate of the direction
* @param dirY
* the y coordinate of the direction
* @param dirZ
* the z coordinate of the direction
*/
public RayAabIntersection(float originX, float originY, float originZ, float dirX, float dirY, float dirZ) {
set(originX, originY, originZ, dirX, dirY, dirZ);
}
/**
* Update the ray stored by this {@link RayAabIntersection} with the new origin <tt>(originX, originY, originZ)</tt>
* and direction <tt>(dirX, dirY, dirZ)</tt>.
*
* @param originX
* the x coordinate of the ray origin
* @param originY
* the y coordinate of the ray origin
* @param originZ
* the z coordinate of the ray origin
* @param dirX
* the x coordinate of the ray direction
* @param dirY
* the y coordinate of the ray direction
* @param dirZ
* the z coordinate of the ray direction
*/
public void set(float originX, float originY, float originZ, float dirX, float dirY, float dirZ) {
this.originX = originX;
this.originY = originY;
this.originZ = originZ;
this.dirX = dirX;
this.dirY = dirY;
this.dirZ = dirZ;
precomputeSlope();
}
private static int signum(float f) {
return (f == 0.0f || Float.isNaN(f)) ? 0 : ((1 - Float.floatToIntBits(f) >>> 31) << 1) - 1;
}
/**
* Precompute the values necessary for the ray slope algorithm.
*/
private void precomputeSlope() {
float invDirX = 1.0f / dirX;
float invDirY = 1.0f / dirY;
float invDirZ = 1.0f / dirZ;
s_yx = dirX * invDirY;
s_xy = dirY * invDirX;
s_zy = dirY * invDirZ;
s_yz = dirZ * invDirY;
s_xz = dirZ * invDirX;
s_zx = dirX * invDirZ;
c_xy = originY - s_xy * originX;
c_yx = originX - s_yx * originY;
c_zy = originY - s_zy * originZ;
c_yz = originZ - s_yz * originY;
c_xz = originZ - s_xz * originX; // <- original paper had a bug here. It switched originZ/originX
c_zx = originX - s_zx * originZ; // <- original paper had a bug here. It switched originZ/originX
int sgnX = signum(dirX);
int sgnY = signum(dirY);
int sgnZ = signum(dirZ);
classification = (byte) ((sgnZ+1) << 4 | (sgnY+1) << 2 | (sgnX+1));
}
/**
* Test whether the ray stored in this {@link RayAabIntersection} intersect the axis-aligned box
* given via its minimum corner <tt>(minX, minY, minZ)</tt> and its maximum corner <tt>(maxX, maxY, maxZ)</tt>.
* <p>
* This implementation uses a tableswitch to dispatch to the correct intersection method.
* <p>
* This method is thread-safe and can be used to test many axis-aligned boxes concurrently.
*
* @param minX
* the x coordinate of the minimum corner
* @param minY
* the y coordinate of the minimum corner
* @param minZ
* the z coordinate of the minimum corner
* @param maxX
* the x coordinate of the maximum corner
* @param maxY
* the y coordinate of the maximum corner
* @param maxZ
* the z coordinate of the maximum corner
* @return <code>true</code> iff the ray intersects the given axis-aligned box; <code>false</code> otherwise
*/
public boolean test(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
// tableswitch with dense and consecutive cases (will be a simple jump based on the switch argument)
switch (classification) {
case 0: // 0b000000: // MMM
return MMM(minX, minY, minZ, maxX, maxY, maxZ);
case 1: // 0b000001: // OMM
return OMM(minX, minY, minZ, maxX, maxY, maxZ);
case 2: // 0b000010: // PMM
return PMM(minX, minY, minZ, maxX, maxY, maxZ);
case 3: // 0b000011: // not used
return false;
case 4: // 0b000100: // MOM
return MOM(minX, minY, minZ, maxX, maxY, maxZ);
case 5: // 0b000101: // OOM
return OOM(minX, minY, minZ, maxX, maxY);
case 6: // 0b000110: // POM
return POM(minX, minY, minZ, maxX, maxY, maxZ);
case 7: // 0b000111: // not used
return false;
case 8: // 0b001000: // MPM
return MPM(minX, minY, minZ, maxX, maxY, maxZ);
case 9: // 0b001001: // OPM
return OPM(minX, minY, minZ, maxX, maxY, maxZ);
case 10: // 0b001010: // PPM
return PPM(minX, minY, minZ, maxX, maxY, maxZ);
case 11: // 0b001011: // not used
case 12: // 0b001100: // not used
case 13: // 0b001101: // not used
case 14: // 0b001110: // not used
case 15: // 0b001111: // not used
return false;
case 16: // 0b010000: // MMO
return MMO(minX, minY, minZ, maxX, maxY, maxZ);
case 17: // 0b010001: // OMO
return OMO(minX, minY, minZ, maxX, maxZ);
case 18: // 0b010010: // PMO
return PMO(minX, minY, minZ, maxX, maxY, maxZ);
case 19: // 0b010011: // not used
return false;
case 20: // 0b010100: // MOO
return MOO(minX, minY, minZ, maxY, maxZ);
case 21: // 0b010101: // OOO
return false; // <- degenerate case
case 22: // 0b010110: // POO
return POO(minY, minZ, maxX, maxY, maxZ);
case 23: // 0b010111: // not used
return false;
case 24: // 0b011000: // MPO
return MPO(minX, minY, minZ, maxX, maxY, maxZ);
case 25: // 0b011001: // OPO
return OPO(minX, minZ, maxX, maxY, maxZ);
case 26: // 0b011010: // PPO
return PPO(minX, minY, minZ, maxX, maxY, maxZ);
case 27: // 0b011011: // not used
case 28: // 0b011100: // not used
case 29: // 0b011101: // not used
case 30: // 0b011110: // not used
case 31: // 0b011111: // not used
return false;
case 32: // 0b100000: // MMP
return MMP(minX, minY, minZ, maxX, maxY, maxZ);
case 33: // 0b100001: // OMP
return OMP(minX, minY, minZ, maxX, maxY, maxZ);
case 34: // 0b100010: // PMP
return PMP(minX, minY, minZ, maxX, maxY, maxZ);
case 35: // 0b100011: // not used
return false;
case 36: // 0b100100: // MOP
return MOP(minX, minY, minZ, maxX, maxY, maxZ);
case 37: // 0b100101: // OOP
return OOP(minX, minY, maxX, maxY, maxZ);
case 38: // 0b100110: // POP
return POP(minX, minY, minZ, maxX, maxY, maxZ);
case 39: // 0b100111: // not used
return false;
case 40: // 0b101000: // MPP
return MPP(minX, minY, minZ, maxX, maxY, maxZ);
case 41: // 0b101001: // OPP
return OPP(minX, minY, minZ, maxX, maxY, maxZ);
case 42: // 0b101010: // PPP
return PPP(minX, minY, minZ, maxX, maxY, maxZ);
default:
return false;
}
}
/* Intersection tests for all possible ray direction cases */
private boolean MMM(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX >= minX && originY >= minY && originZ >= minZ
&& s_xy * minX - maxY + c_xy <= 0.0f
&& s_yx * minY - maxX + c_yx <= 0.0f
&& s_zy * minZ - maxY + c_zy <= 0.0f
&& s_yz * minY - maxZ + c_yz <= 0.0f
&& s_xz * minX - maxZ + c_xz <= 0.0f
&& s_zx * minZ - maxX + c_zx <= 0.0f;
}
private boolean OMM(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX >= minX && originX <= maxX && originY >= minY && originZ >= minZ
&& s_zy * minZ - maxY + c_zy <= 0.0f
&& s_yz * minY - maxZ + c_yz <= 0.0f;
}
private boolean PMM(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX <= maxX && originY >= minY && originZ >= minZ
&& s_xy * maxX - maxY + c_xy <= 0.0f
&& s_yx * minY - minX + c_yx >= 0.0f
&& s_zy * minZ - maxY + c_zy <= 0.0f
&& s_yz * minY - maxZ + c_yz <= 0.0f
&& s_xz * maxX - maxZ + c_xz <= 0.0f
&& s_zx * minZ - minX + c_zx >= 0.0f;
}
private boolean MOM(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originY >= minY && originY <= maxY && originX >= minX && originZ >= minZ
&& s_xz * minX - maxZ + c_xz <= 0.0f
&& s_zx * minZ - maxX + c_zx <= 0.0f;
}
private boolean OOM(float minX, float minY, float minZ, float maxX, float maxY) {
return originZ >= minZ && originX >= minX && originX <= maxX && originY >= minY && originY <= maxY;
}
private boolean POM(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originY >= minY && originY <= maxY && originX <= maxX && originZ >= minZ
&& s_xz * maxX - maxZ + c_xz <= 0.0f
&& s_zx * minZ - minX + c_zx >= 0.0f;
}
private boolean MPM(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX >= minX && originY <= maxY && originZ >= minZ
&& s_xy * minX - minY + c_xy >= 0.0f
&& s_yx * maxY - maxX + c_yx <= 0.0f
&& s_zy * minZ - minY + c_zy >= 0.0f
&& s_yz * maxY - maxZ + c_yz <= 0.0f
&& s_xz * minX - maxZ + c_xz <= 0.0f
&& s_zx * minZ - maxX + c_zx <= 0.0f;
}
private boolean OPM(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX >= minX && originX <= maxX && originY <= maxY && originZ >= minZ
&& s_zy * minZ - minY + c_zy >= 0.0f
&& s_yz * maxY - maxZ + c_yz <= 0.0f;
}
private boolean PPM(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX <= maxX && originY <= maxY && originZ >= minZ
&& s_xy * maxX - minY + c_xy >= 0.0f
&& s_yx * maxY - minX + c_yx >= 0.0f
&& s_zy * minZ - minY + c_zy >= 0.0f
&& s_yz * maxY - maxZ + c_yz <= 0.0f
&& s_xz * maxX - maxZ + c_xz <= 0.0f
&& s_zx * minZ - minX + c_zx >= 0.0f;
}
private boolean MMO(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originZ >= minZ && originZ <= maxZ && originX >= minX && originY >= minY
&& s_xy * minX - maxY + c_xy <= 0.0f
&& s_yx * minY - maxX + c_yx <= 0.0f;
}
private boolean OMO(float minX, float minY, float minZ, float maxX, float maxZ) {
return originY >= minY && originX >= minX && originX <= maxX && originZ >= minZ && originZ <= maxZ;
}
private boolean PMO(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originZ >= minZ && originZ <= maxZ && originX <= maxX && originY >= minY
&& s_xy * maxX - maxY + c_xy <= 0.0f
&& s_yx * minY - minX + c_yx >= 0.0f;
}
private boolean MOO(float minX, float minY, float minZ, float maxY, float maxZ) {
return originX >= minX && originY >= minY && originY <= maxY && originZ >= minZ && originZ <= maxZ;
}
private boolean POO(float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX <= maxX && originY >= minY && originY <= maxY && originZ >= minZ && originZ <= maxZ;
}
private boolean MPO(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originZ >= minZ && originZ <= maxZ && originX >= minX && originY <= maxY
&& s_xy * minX - minY + c_xy >= 0.0f
&& s_yx * maxY - maxX + c_yx <= 0.0f;
}
private boolean OPO(float minX, float minZ, float maxX, float maxY, float maxZ) {
return originY <= maxY && originX >= minX && originX <= maxX && originZ >= minZ && originZ <= maxZ;
}
private boolean PPO(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originZ >= minZ && originZ <= maxZ && originX <= maxX && originY <= maxY
&& s_xy * maxX - minY + c_xy >= 0.0f
&& s_yx * maxY - minX + c_yx >= 0.0f;
}
private boolean MMP(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX >= minX && originY >= minY && originZ <= maxZ
&& s_xy * minX - maxY + c_xy <= 0.0f
&& s_yx * minY - maxX + c_yx <= 0.0f
&& s_zy * maxZ - maxY + c_zy <= 0.0f
&& s_yz * minY - minZ + c_yz >= 0.0f
&& s_xz * minX - minZ + c_xz >= 0.0f
&& s_zx * maxZ - maxX + c_zx <= 0.0f;
}
private boolean OMP(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX >= minX && originX <= maxX && originY >= minY && originZ <= maxZ
&& s_zy * maxZ - maxY + c_zy <= 0.0f
&& s_yz * minY - minZ + c_yz >= 0.0f;
}
private boolean PMP(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX <= maxX && originY >= minY && originZ <= maxZ
&& s_xy * maxX - maxY + c_xy <= 0.0f
&& s_yx * minY - minX + c_yx >= 0.0f
&& s_zy * maxZ - maxY + c_zy <= 0.0f
&& s_yz * minY - minZ + c_yz >= 0.0f
&& s_xz * maxX - minZ + c_xz >= 0.0f
&& s_zx * maxZ - minX + c_zx >= 0.0f;
}
private boolean MOP(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originY >= minY && originY <= maxY && originX >= minX && originZ <= maxZ
&& s_xz * minX - minZ + c_xz >= 0.0f
&& s_zx * maxZ - maxX + c_zx <= 0.0f;
}
private boolean OOP(float minX, float minY, float maxX, float maxY, float maxZ) {
return originZ <= maxZ && originX >= minX && originX <= maxX && originY >= minY && originY <= maxY;
}
private boolean POP(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originY >= minY && originY <= maxY && originX <= maxX && originZ <= maxZ
&& s_xz * maxX - minZ + c_xz >= 0.0f
&& s_zx * maxZ - minX + c_zx <= 0.0f;
}
private boolean MPP(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX >= minX && originY <= maxY && originZ <= maxZ
&& s_xy * minX - minY + c_xy >= 0.0f
&& s_yx * maxY - maxX + c_yx <= 0.0f
&& s_zy * maxZ - minY + c_zy >= 0.0f
&& s_yz * maxY - minZ + c_yz >= 0.0f
&& s_xz * minX - minZ + c_xz >= 0.0f
&& s_zx * maxZ - maxX + c_zx <= 0.0f;
}
private boolean OPP(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX >= minX && originX <= maxX && originY <= maxY && originZ <= maxZ
&& s_zy * maxZ - minY + c_zy <= 0.0f
&& s_yz * maxY - minZ + c_yz <= 0.0f;
}
private boolean PPP(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) {
return originX <= maxX && originY <= maxY && originZ <= maxZ
&& s_xy * maxX - minY + c_xy >= 0.0f
&& s_yx * maxY - minX + c_yx >= 0.0f
&& s_zy * maxZ - minY + c_zy >= 0.0f
&& s_yz * maxY - minZ + c_yz >= 0.0f
&& s_xz * maxX - minZ + c_xz >= 0.0f
&& s_zx * maxZ - minX + c_zx >= 0.0f;
}
}