/**
* @class
* Axis-aligned bounding boxes with the constraint that they are cubes with equal sides.
* @property {Lore.Vector3f} center - The center of this axis-aligned bounding box.
* @property {number} radius - The radius of this axis-aligned bounding box.
* @property {number} locCode - The location code of this axis-aligned bounding box in the octree.
* @property {number} left - The distance of the left plane to the world ZY plane.
* @property {number} right - The distance of the right plane to the world ZY plane.
* @property {number} back - The distance of the back plane to the world XY plane.
* @property {number} front - The distance of the front plane to the world XY plane.
* @property {number} bottom - The distance of the bottom plane to the world XZ plane.
* @property {number} top - The distance of the top plane to the world XZ plane.
* @property {Array} neighbours - The neighbours of this axis-aligned bounding box in an an octree.
* @property {Float32Array} min - An array specifying the minimum corner point (x, y, z) of the axis-aligned bounding box.
* @property {Float32Array} max - An array specifying the maximum corner point (x, y, z) of the axis-aligned bounding box.
* @constructor
* @param {Lore.Vector3f} center - A radius for this axis-aligned bounding box.
* @param {number} radius - A radius for this axis-aligned bounding box.
*/
Lore.AABB = class AABB {
constructor(center, radius) {
this.center = center || new Lore.Vector3f();
this.radius = radius || 0;
this.locCode = 0;
this.left = 0;
this.right = 0;
this.back = 0;
this.front = 0;
this.bottom = 0;
this.top = 0;
this.neighbours = new Array(6);
this.min = new Float32Array(3);
this.max = new Float32Array(3);
this.updateDimensions();
}
/**
* Calculates the distance of the axis-aligned bounding box's planes to the world planes.
*/
updateDimensions() {
let cx = this.center.components[0];
let cy = this.center.components[1];
let cz = this.center.components[2];
this.min[0] = cx - this.radius;
this.min[1] = cy - this.radius;
this.min[2] = cz - this.radius;
this.max[0] = cx + this.radius;
this.max[1] = cy + this.radius;
this.max[2] = cz + this.radius;
// Precalculate to simplify ray test
this.left = cx - this.radius;
this.right = cx + this.radius;
this.back = cz - this.radius;
this.front = cz + this.radius;
this.bottom = cy - this.radius;
this.top = cy + this.radius;
return this;
}
/**
* Sets the location code of this axis-aligned bounding box.
*
* @param {number} locCode - The location code.
*/
setLocCode(locCode) {
this.locCode = locCode;
return this;
}
/**
* Gets the location code of this axis-aligned bounding box.
*
* @returns {number} The location code.
*/
getLocCode() {
return this.locCode;
}
/**
* Tests whether or not this axis-aligned bounding box is intersected by a ray.
*
* @param {Lore.Vector3f} source - The source of the ray.
* @param {Lore.Vector3f} dir - A normalized vector of the direction of the ray.
* @param {number} dist - The maximum distance from the source that still counts as an intersect (the far property of the Lore.Raycaster object).
* @returns {boolean} - Whether or not there is an intersect.
*/
rayTest(source, inverseDir, dist) {
// dir is the precomputed inverse of the direction of the ray,
// this means that the costly divisions can be omitted
let oc = source.components;
let ic = inverseDir.components;
let t0 = (this.left - oc[0]) * ic[0];
let t1 = (this.right - oc[0]) * ic[0];
let t2 = (this.bottom - oc[1]) * ic[1];
let t3 = (this.top - oc[1]) * ic[1];
let t4 = (this.back - oc[2]) * ic[2];
let t5 = (this.front - oc[2]) * ic[2];
let maxT = Math.min(Math.max(t0, t1), Math.max(t2, t3), Math.max(t4, t5));
// Ray intersects in reverse direction, which means
// that the box is behind the camera
if (maxT < 0) {
return false;
}
let minT = Math.max(Math.min(t0, t1), Math.min(t2, t3), Math.min(t4, t5));
if (minT > maxT || minT > dist) {
return false;
}
// Intersection happens when minT is larger or equal to maxT
// and minT is smaller than the distance (distance == radius == ray.far)
return true;
}
/**
* Tests whether or not this axis-aligned bounding box is intersected by a cylinder. CAUTION: If this runs multi-threaded, it might fail.
*
* @param {Lore.Vector3f} source - The source of the ray.
* @param {Lore.Vector3f} dir - A normalized vector of the direction of the ray.
* @param {number} dist - The maximum distance from the source that still counts as an intersect (the far property of the Lore.Raycaster object).
* @param {number} radius - The radius of the cylinder
* @returns {boolean} - Whether or not there is an intersect.
*/
cylinderTest(source, inverseDir, dist, radius) {
// Instead of testing an actual cylinder against this aabb, we simply
// expand the radius of the box temporarily.
this.radius += radius;
this.updateDimensions();
// Do the normal ray intersection test
let result = this.rayTest(source, inverseDir, dist);
this.radius -= radius;
this.updateDimensions();
return result;
}
/**
* Returns the square distance of this axis-aligned bounding box to the point supplied as an argument.
*
* @param {number} x - The x component of the point coordinate.
* @param {number} y - The y component of the point coordinate.
* @param {number} z - The z component of the point coordinate.
* @returns {number} The square distance of this axis-aligned bounding box to the input point.
*/
distanceToPointSq(x, y, z) {
// From book, real time collision detection
let sqDist = 0;
let p = [x, y, z];
// Add the distances for each axis
for (var i = 0; i < 3; i++) {
if (p[i] < this.min[i])
sqDist += Math.pow(this.min[i] - p[i], 2);
if (p[i] > this.max[i])
sqDist += Math.pow(p[i] - this.max[i], 2);
}
return sqDist;
}
/**
* Returns the box that is closest to the point (measured from center).
*
* @param {number} x - The x component of the point coordinate.
* @param {number} y - The y component of the point coordinate.
* @param {number} z - The z component of the point coordinate.
* @returns {number} The square distance of this axis-aligned bounding box to the input point.
*/
distanceFromCenterToPointSq(x, y, z) {
let center = this.center.components;
return Math.pow(center[0] - x, 2) + Math.pow(center[1] - y, 2) + Math.pow(center[2] - z, 2);
}
/**
* Tests whether or not this axis-aligned bounding box overlaps or shares an edge or a vertex with another axis-aligned bounding box.
* This method can also be used to assert whether or not two boxes are neighbours.
*
* @param {Lore.AABB} aabb - The axis-aligned bounding box to test against.
* @returns {boolean} - Whether or not there is an overlap.
*/
testAABB(aabb) {
for (var i = 0; i < 3; i++) {
if (this.max[i] < aabb.min[i] || this.min[i] > aabb.max[i]) {
return false;
}
}
return true;
}
/**
* Creates a axis-aligned bounding box surrounding a set of vertices.
*
* @param {Uint32Array} vertices - The vertices which will all be inside the axis-aligned bounding box.
* @returns {Lore.AABB} An axis-aligned bounding box surrounding the vertices.
*/
static fromPoints(vertices) {
let x = vertices[0];
let y = vertices[1];
let z = vertices[2];
let min = new Lore.Vector3f(x, y, z);
let max = new Lore.Vector3f(x, y, z);
let minc = min.components;
let maxc = max.components;
for (var i = 1; i < vertices.length / 3; i++) {
if (vertices[i * 3 + 0] < minc[0]) minc[0] = vertices[i * 3 + 0];
if (vertices[i * 3 + 1] < minc[1]) minc[1] = vertices[i * 3 + 1];
if (vertices[i * 3 + 2] < minc[2]) minc[2] = vertices[i * 3 + 2];
if (vertices[i * 3 + 0] > maxc[0]) maxc[0] = vertices[i * 3 + 0];
if (vertices[i * 3 + 1] > maxc[1]) maxc[1] = vertices[i * 3 + 1];
if (vertices[i * 3 + 2] > maxc[2]) maxc[2] = vertices[i * 3 + 2];
}
// Calculate the radius in each direction
let radii = new Lore.Vector3f.subtract(max, min);
radii.multiplyScalar(0.5);
let rx = radii.components[0];
let ry = radii.components[1];
let rz = radii.components[2];
let center = new Lore.Vector3f(rx, ry, rz);
center.add(min);
// Since the octree always stores cubes, there is of course only
// one radius - take the biggest one
let radius = Math.max(rx, ry, rz);
return new Lore.AABB(center, radius);
}
/**
* Returns an array representing the 8 corners of the axis-aligned bounding box.
*
* @param {Lore.AABB} aabb An axis-aligned bounding box.
* @returns {Array} An array containing the 8 corners of the axisa-aligned bunding box. E.g [[x, y, z], [x, y, z], ...]
*/
static getCorners(aabb) {
let c = aabb.center.components;
let x = c[0];
let y = c[1];
let z = c[2];
let r = aabb.radius;
return [
[x - r, y - r, z - r],
[x - r, y - r, z + r],
[x - r, y + r, z - r],
[x - r, y + r, z + r],
[x + r, y - r, z - r],
[x + r, y - r, z + r],
[x + r, y + r, z - r],
[x + r, y + r, z + r]
]
}
/**
* Clones an axis-aligned bounding box.
*
* @param {Lore.AABB} original - The axis-aligned bounding box to be cloned.
* @returns {Lore.AABB} The cloned axis-aligned bounding box.
*/
static clone(original) {
let clone = new Lore.AABB();
clone.back = original.back;
clone.bottom = original.bottom;
clone.center = new Lore.Vector3f(original.center.components[0],
original.center.components[1], original.center.components[2]);
clone.front = original.front;
clone.left = original.left;
clone.locCode = original.locCode;
clone.max = original.max;
clone.min = original.min;
clone.radius = original.radius;
clone.right = original.right;
clone.top = original.top;
return clone;
}
}