Skip to content

Commit

Permalink
Implement/test new OrientedBoundingBox
Browse files Browse the repository at this point in the history
  • Loading branch information
kainino0x committed Jun 9, 2015
1 parent 5483d09 commit a4588b9
Show file tree
Hide file tree
Showing 2 changed files with 767 additions and 0 deletions.
331 changes: 331 additions & 0 deletions Source/Core/OrientedBoundingBox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
/*global define*/
define([
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Intersect',
'./Matrix3'
], function(
Cartesian3,
defaultValue,
defined,
DeveloperError,
Intersect,
Matrix3) {
"use strict";

/**
* Creates an instance of an OrientedBoundingBox.
* An OrientedBoundingBox model of an object or set of objects, is a closed volume (a cuboid), which completely contains the object or the set of objects.
* It is oriented, so it can provide an optimum fit, it can bound more tightly.
* @alias OrientedBoundingBox
* @constructor
*
* @param {Cartesian3} [center=Cartesian3.ZERO] The center of the box.
* @param {Matrix3} [halfAxes=Matrix3.ZERO] The three orthogonal half-axes of the bounding box.
* Equivalently, the transformation matrix, to rotate and scale a 2x2x2
* cube centered at the origin.
*
* @see OrientedBoundingBox.fromPoints
* @see OrientedBoundingBox.fromBoundingRectangle
* @see BoundingSphere
* @see BoundingRectangle
* @see ObjectOrientedBoundingBox
*
* @example
* // Create an OrientedBoundingBox using a transformation matrix, a position where the box will be translated, and a scale.
* var center = new Cesium.Cartesian3(1,0,0);
* var halfAxes = Cesium.Matrix3.clone(Cesium.Matrix3.fromScale(new Cartesian3(1.0, 3.0, 2.0)));
*
* var obb = new Cesium.OrientedBoundingBox(center, halfAxes);
*/
var OrientedBoundingBox = function(center, halfAxes) {
/**
* The center of the box.
* @type {Cartesian3}
* @default {@link Cartesian3.ZERO}
*/
this.center = Cartesian3.clone(defaultValue(center, Cartesian3.ZERO));
/**
* The transformation matrix, to rotate the box to the right position.
* @type {Matrix3}
* @default {@link Matrix3.IDENTITY}
*/
this.halfAxes = Matrix3.clone(defaultValue(halfAxes, Matrix3.ZERO));
};

var scratchCartesian1 = new Cartesian3();
var scratchCartesian2 = new Cartesian3();
var scratchCartesian3 = new Cartesian3();
var scratchCartesian4 = new Cartesian3();
var scratchCartesian5 = new Cartesian3();
var scratchMatrix1 = new Matrix3();
var scratchRotation = new Matrix3();
var scratchCovarianceResult = new Matrix3();
var scratchEigenResult = {
unitary : new Matrix3(),
diagonal : new Matrix3()
};

/**
* Computes an instance of an OrientedBoundingBox of the given positions.
* This is an implementation of Stefan Gottschalk's Collision Queries using Oriented Bounding Boxes solution (PHD thesis).
* Reference: http://gamma.cs.unc.edu/users/gottschalk/main.pdf
*
* @param {Cartesian3[]} positions List of {@link Cartesian3} points that the bounding box will enclose.
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
*
* @example
* // Compute an object oriented bounding box enclosing two points.
* var box = Cesium.OrientedBoundingBox.fromPoints([new Cesium.Cartesian3(2, 0, 0), new Cesium.Cartesian3(-2, 0, 0)]);
*/
OrientedBoundingBox.fromPoints = function(positions, result) {
if (!defined(result)) {
result = new OrientedBoundingBox();
}

if (!defined(positions) || positions.length === 0) {
Cartesian3.clone(Cartesian3.ZERO, result.center);
Matrix3.clone(Matrix3.ZERO, result.halfAxes);
return result;
}

var i;
var length = positions.length;

var meanPoint = Cartesian3.clone(positions[0], scratchCartesian1);
for (i = 1; i < length; i++) {
Cartesian3.add(meanPoint, positions[i], meanPoint);
}
var invLength = 1.0 / length;
Cartesian3.multiplyByScalar(meanPoint, invLength, meanPoint);

var exx = 0.0;
var exy = 0.0;
var exz = 0.0;
var eyy = 0.0;
var eyz = 0.0;
var ezz = 0.0;
var p;

for (i = 0; i < length; i++) {
p = Cartesian3.subtract(positions[i], meanPoint, scratchCartesian2);
exx += p.x * p.x;
exy += p.x * p.y;
exz += p.x * p.z;
eyy += p.y * p.y;
eyz += p.y * p.z;
ezz += p.z * p.z;
}

exx *= invLength;
exy *= invLength;
exz *= invLength;
eyy *= invLength;
eyz *= invLength;
ezz *= invLength;

var covarianceMatrix = scratchCovarianceResult;
covarianceMatrix[0] = exx;
covarianceMatrix[1] = exy;
covarianceMatrix[2] = exz;
covarianceMatrix[3] = exy;
covarianceMatrix[4] = eyy;
covarianceMatrix[5] = eyz;
covarianceMatrix[6] = exz;
covarianceMatrix[7] = eyz;
covarianceMatrix[8] = ezz;

var eigenDecomposition = Matrix3.computeEigenDecomposition(covarianceMatrix, scratchEigenResult);
var rotation = Matrix3.transpose(eigenDecomposition.unitary, scratchRotation);

p = Cartesian3.subtract(positions[0], meanPoint, scratchCartesian2);
var tempPoint = Matrix3.multiplyByVector(rotation, p, scratchCartesian3);
var maxPoint = Cartesian3.clone(tempPoint, scratchCartesian4);
var minPoint = Cartesian3.clone(tempPoint, scratchCartesian5);

for (i = 1; i < length; i++) {
p = Cartesian3.subtract(positions[i], meanPoint, p);
Matrix3.multiplyByVector(rotation, p, tempPoint);
Cartesian3.minimumByComponent(minPoint, tempPoint, minPoint);
Cartesian3.maximumByComponent(maxPoint, tempPoint, maxPoint);
}

var center = Cartesian3.add(minPoint, maxPoint, scratchCartesian3);
Cartesian3.multiplyByScalar(center, 0.5, center);
Matrix3.multiplyByVector(rotation, center, center);
Cartesian3.add(meanPoint, center, result.center);

var scale = Cartesian3.subtract(maxPoint, minPoint, scratchCartesian3);
Cartesian3.multiplyByScalar(scale, 0.5, scale);
var scaleMat = Matrix3.fromScale(scale, scratchMatrix1);

Matrix3.multiply(rotation, scaleMat, result.halfAxes);

return result;
};

/**
* Computes an OrientedBoundingBox from a BoundingRectangle.
* The BoundingRectangle is placed on the XY plane.
*
* @param {BoundingRectangle} boundingRectangle A bounding rectangle.
* @param {Number} [rotation=0.0] The rotation of the bounding box in radians.
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
*
* @example
* // Compute an object oriented bounding box enclosing two points.
* var box = Cesium.OrientedBoundingBox.fromBoundingRectangle(boundingRectangle, 0.0);
*/
OrientedBoundingBox.fromBoundingRectangle = function(boundingRectangle, rotation, result) {
//>>includeStart('debug', pragmas.debug);
if (!defined(boundingRectangle)) {
throw new DeveloperError('boundingRectangle is required');
}
//>>includeEnd('debug');

if (!defined(result)) {
result = new OrientedBoundingBox();
}

var rotMat;
if (defined(rotation)) {
rotMat = Matrix3.fromRotationZ(rotation, scratchRotation);
} else {
rotMat = Matrix3.clone(Matrix3.IDENTITY, scratchRotation);
}

var scale = scratchCartesian1;
scale.x = boundingRectangle.width * 0.5;
scale.y = boundingRectangle.height * 0.5;
scale.z = 0.0;
var scaleMat = Matrix3.fromScale(scale, scratchMatrix1);
Matrix3.multiply(rotMat, scaleMat, result.halfAxes);

var translation = Matrix3.multiplyByVector(rotMat, scale, result.center);
translation.x += boundingRectangle.x;
translation.y += boundingRectangle.y;

return result;
};

/**
* Duplicates a OrientedBoundingBox instance.
*
* @param {OrientedBoundingBox} box The bounding box to duplicate.
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if none was provided. (Returns undefined if box is undefined)
*/
OrientedBoundingBox.clone = function(box, result) {
if (!defined(box)) {
return undefined;
}

if (!defined(result)) {
return new OrientedBoundingBox(box.center, box.halfAxes);
}

Cartesian3.clone(box.center, result.center);
Matrix3.clone(box.halfAxes, result.halfAxes);

return result;
};

/**
* Determines which side of a plane the oriented bounding box is located.
*
* @param {OrientedBoundingBox} box The oriented bounding box to test.
* @param {Cartesian4} plane The coefficients of the plane in Hessian Normal Form, as `ax + by + cz + d = 0`,
* where (a, b, c) must be a unit normal vector.
* The coefficients a, b, c, and d are the components x, y, z,
* and w of the {@link Cartesian4}, respectively.
* @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
* on the opposite side, and {@link Intersect.INTERSECTING} if the box
* intersects the plane.
*/
OrientedBoundingBox.intersectPlane = function(box, plane) {
//>>includeStart('debug', pragmas.debug);
if (!defined(box)) {
throw new DeveloperError('box is required.');
}

if (!defined(plane)) {
throw new DeveloperError('plane is required.');
}
//>>includeEnd('debug');

var center = box.center;
// plane is used as if it is its normal; the first three components are assumed to be normalized
var radEffective = Math.abs(Cartesian3.dot(plane, Matrix3.getColumn(box.halfAxes, 0, scratchCartesian1))) +
Math.abs(Cartesian3.dot(plane, Matrix3.getColumn(box.halfAxes, 1, scratchCartesian2))) +
Math.abs(Cartesian3.dot(plane, Matrix3.getColumn(box.halfAxes, 2, scratchCartesian3)));
var distanceToPlane = Cartesian3.dot(plane, center) + plane.w;

if (distanceToPlane <= -radEffective) {
// The entire box is on the negative side of the plane normal
return Intersect.OUTSIDE;
} else if (distanceToPlane >= radEffective) {
// The entire box is on the positive side of the plane normal
return Intersect.INSIDE;
}
return Intersect.INTERSECTING;
};

/**
* Determines which side of a plane the oriented bounding box is located.
*
* @param {Cartesian4} plane The coefficients of the plane in Hessian Normal Form, as `ax + by + cz + d = 0`,
* where (a, b, c) must be a unit normal vector.
* The coefficients a, b, c, and d are the components x, y, z,
* and w of the {@link Cartesian4}, respectively.
* @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
* on the opposite side, and {@link Intersect.INTERSECTING} if the box
* intersects the plane.
*/
OrientedBoundingBox.prototype.intersectPlane = function(plane) {
return OrientedBoundingBox.intersectPlane(this, plane);
};

/**
* Compares the provided OrientedBoundingBox componentwise and returns
* <code>true</code> if they are equal, <code>false</code> otherwise.
*
* @param {OrientedBoundingBox} left The first OrientedBoundingBox.
* @param {OrientedBoundingBox} right The second OrientedBoundingBox.
* @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
*/
OrientedBoundingBox.equals = function(left, right) {
return (left === right) ||
((defined(left)) &&
(defined(right)) &&
Cartesian3.equals(left.center, right.center) &&
Matrix3.equals(left.halfAxes, right.halfAxes));
};

/**
* Duplicates this OrientedBoundingBox instance.
*
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
*/
OrientedBoundingBox.prototype.clone = function(result) {
return OrientedBoundingBox.clone(this, result);
};

/**
* Compares this OrientedBoundingBox against the provided OrientedBoundingBox componentwise and returns
* <code>true</code> if they are equal, <code>false</code> otherwise.
*
* @param {OrientedBoundingBox} [right] The right hand side OrientedBoundingBox.
* @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
*/
OrientedBoundingBox.prototype.equals = function(right) {
return OrientedBoundingBox.equals(this, right);
};

return OrientedBoundingBox;
});
Loading

0 comments on commit a4588b9

Please sign in to comment.