Skip to content

Commit 28c8c9d

Browse files
committed
Implement/test new OrientedBoundingBox
1 parent 98b35fb commit 28c8c9d

File tree

2 files changed

+767
-0
lines changed

2 files changed

+767
-0
lines changed

Source/Core/OrientedBoundingBox.js

+331
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/*global define*/
2+
define([
3+
'./Cartesian3',
4+
'./defaultValue',
5+
'./defined',
6+
'./DeveloperError',
7+
'./Intersect',
8+
'./Matrix3'
9+
], function(
10+
Cartesian3,
11+
defaultValue,
12+
defined,
13+
DeveloperError,
14+
Intersect,
15+
Matrix3) {
16+
"use strict";
17+
18+
/**
19+
* Creates an instance of an OrientedBoundingBox.
20+
* 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.
21+
* It is oriented, so it can provide an optimum fit, it can bound more tightly.
22+
* @alias OrientedBoundingBox
23+
* @constructor
24+
*
25+
* @param {Cartesian3} [center=Cartesian3.ZERO] The center of the box.
26+
* @param {Matrix3} [halfAxes=Matrix3.ZERO] The three orthogonal half-axes of the bounding box.
27+
* Equivalently, the transformation matrix, to rotate and scale a 2x2x2
28+
* cube centered at the origin.
29+
*
30+
* @see OrientedBoundingBox.fromPoints
31+
* @see OrientedBoundingBox.fromBoundingRectangle
32+
* @see BoundingSphere
33+
* @see BoundingRectangle
34+
* @see ObjectOrientedBoundingBox
35+
*
36+
* @example
37+
* // Create an OrientedBoundingBox using a transformation matrix, a position where the box will be translated, and a scale.
38+
* var center = new Cesium.Cartesian3(1,0,0);
39+
* var halfAxes = Cesium.Matrix3.clone(Cesium.Matrix3.fromScale(new Cartesian3(1.0, 3.0, 2.0)));
40+
*
41+
* var obb = new Cesium.OrientedBoundingBox(center, halfAxes);
42+
*/
43+
var OrientedBoundingBox = function(center, halfAxes) {
44+
/**
45+
* The center of the box.
46+
* @type {Cartesian3}
47+
* @default {@link Cartesian3.ZERO}
48+
*/
49+
this.center = Cartesian3.clone(defaultValue(center, Cartesian3.ZERO));
50+
/**
51+
* The transformation matrix, to rotate the box to the right position.
52+
* @type {Matrix3}
53+
* @default {@link Matrix3.IDENTITY}
54+
*/
55+
this.halfAxes = Matrix3.clone(defaultValue(halfAxes, Matrix3.ZERO));
56+
};
57+
58+
var scratchCartesian1 = new Cartesian3();
59+
var scratchCartesian2 = new Cartesian3();
60+
var scratchCartesian3 = new Cartesian3();
61+
var scratchCartesian4 = new Cartesian3();
62+
var scratchCartesian5 = new Cartesian3();
63+
var scratchMatrix1 = new Matrix3();
64+
var scratchRotation = new Matrix3();
65+
var scratchCovarianceResult = new Matrix3();
66+
var scratchEigenResult = {
67+
unitary : new Matrix3(),
68+
diagonal : new Matrix3()
69+
};
70+
71+
/**
72+
* Computes an instance of an OrientedBoundingBox of the given positions.
73+
* This is an implementation of Stefan Gottschalk's Collision Queries using Oriented Bounding Boxes solution (PHD thesis).
74+
* Reference: http://gamma.cs.unc.edu/users/gottschalk/main.pdf
75+
*
76+
* @param {Cartesian3[]} positions List of {@link Cartesian3} points that the bounding box will enclose.
77+
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
78+
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
79+
*
80+
* @example
81+
* // Compute an object oriented bounding box enclosing two points.
82+
* var box = Cesium.OrientedBoundingBox.fromPoints([new Cesium.Cartesian3(2, 0, 0), new Cesium.Cartesian3(-2, 0, 0)]);
83+
*/
84+
OrientedBoundingBox.fromPoints = function(positions, result) {
85+
if (!defined(result)) {
86+
result = new OrientedBoundingBox();
87+
}
88+
89+
if (!defined(positions) || positions.length === 0) {
90+
Cartesian3.clone(Cartesian3.ZERO, result.center);
91+
Matrix3.clone(Matrix3.ZERO, result.halfAxes);
92+
return result;
93+
}
94+
95+
var i;
96+
var length = positions.length;
97+
98+
var meanPoint = Cartesian3.clone(positions[0], scratchCartesian1);
99+
for (i = 1; i < length; i++) {
100+
Cartesian3.add(meanPoint, positions[i], meanPoint);
101+
}
102+
var invLength = 1.0 / length;
103+
Cartesian3.multiplyByScalar(meanPoint, invLength, meanPoint);
104+
105+
var exx = 0.0;
106+
var exy = 0.0;
107+
var exz = 0.0;
108+
var eyy = 0.0;
109+
var eyz = 0.0;
110+
var ezz = 0.0;
111+
var p;
112+
113+
for (i = 0; i < length; i++) {
114+
p = Cartesian3.subtract(positions[i], meanPoint, scratchCartesian2);
115+
exx += p.x * p.x;
116+
exy += p.x * p.y;
117+
exz += p.x * p.z;
118+
eyy += p.y * p.y;
119+
eyz += p.y * p.z;
120+
ezz += p.z * p.z;
121+
}
122+
123+
exx *= invLength;
124+
exy *= invLength;
125+
exz *= invLength;
126+
eyy *= invLength;
127+
eyz *= invLength;
128+
ezz *= invLength;
129+
130+
var covarianceMatrix = scratchCovarianceResult;
131+
covarianceMatrix[0] = exx;
132+
covarianceMatrix[1] = exy;
133+
covarianceMatrix[2] = exz;
134+
covarianceMatrix[3] = exy;
135+
covarianceMatrix[4] = eyy;
136+
covarianceMatrix[5] = eyz;
137+
covarianceMatrix[6] = exz;
138+
covarianceMatrix[7] = eyz;
139+
covarianceMatrix[8] = ezz;
140+
141+
var eigenDecomposition = Matrix3.computeEigenDecomposition(covarianceMatrix, scratchEigenResult);
142+
var rotation = Matrix3.transpose(eigenDecomposition.unitary, scratchRotation);
143+
144+
p = Cartesian3.subtract(positions[0], meanPoint, scratchCartesian2);
145+
var tempPoint = Matrix3.multiplyByVector(rotation, p, scratchCartesian3);
146+
var maxPoint = Cartesian3.clone(tempPoint, scratchCartesian4);
147+
var minPoint = Cartesian3.clone(tempPoint, scratchCartesian5);
148+
149+
for (i = 1; i < length; i++) {
150+
p = Cartesian3.subtract(positions[i], meanPoint, p);
151+
Matrix3.multiplyByVector(rotation, p, tempPoint);
152+
Cartesian3.minimumByComponent(minPoint, tempPoint, minPoint);
153+
Cartesian3.maximumByComponent(maxPoint, tempPoint, maxPoint);
154+
}
155+
156+
var center = Cartesian3.add(minPoint, maxPoint, scratchCartesian3);
157+
Cartesian3.multiplyByScalar(center, 0.5, center);
158+
Matrix3.multiplyByVector(rotation, center, center);
159+
Cartesian3.add(meanPoint, center, result.center);
160+
161+
var scale = Cartesian3.subtract(maxPoint, minPoint, scratchCartesian3);
162+
Cartesian3.multiplyByScalar(scale, 0.5, scale);
163+
var scaleMat = Matrix3.fromScale(scale, scratchMatrix1);
164+
165+
Matrix3.multiply(rotation, scaleMat, result.halfAxes);
166+
167+
return result;
168+
};
169+
170+
/**
171+
* Computes an OrientedBoundingBox from a BoundingRectangle.
172+
* The BoundingRectangle is placed on the XY plane.
173+
*
174+
* @param {BoundingRectangle} boundingRectangle A bounding rectangle.
175+
* @param {Number} [rotation=0.0] The rotation of the bounding box in radians.
176+
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
177+
*
178+
* @example
179+
* // Compute an object oriented bounding box enclosing two points.
180+
* var box = Cesium.OrientedBoundingBox.fromBoundingRectangle(boundingRectangle, 0.0);
181+
*/
182+
OrientedBoundingBox.fromBoundingRectangle = function(boundingRectangle, rotation, result) {
183+
//>>includeStart('debug', pragmas.debug);
184+
if (!defined(boundingRectangle)) {
185+
throw new DeveloperError('boundingRectangle is required');
186+
}
187+
//>>includeEnd('debug');
188+
189+
if (!defined(result)) {
190+
result = new OrientedBoundingBox();
191+
}
192+
193+
var rotMat;
194+
if (defined(rotation)) {
195+
rotMat = Matrix3.fromRotationZ(rotation, scratchRotation);
196+
} else {
197+
rotMat = Matrix3.clone(Matrix3.IDENTITY, scratchRotation);
198+
}
199+
200+
var scale = scratchCartesian1;
201+
scale.x = boundingRectangle.width * 0.5;
202+
scale.y = boundingRectangle.height * 0.5;
203+
scale.z = 0.0;
204+
var scaleMat = Matrix3.fromScale(scale, scratchMatrix1);
205+
Matrix3.multiply(rotMat, scaleMat, result.halfAxes);
206+
207+
var translation = Matrix3.multiplyByVector(rotMat, scale, result.center);
208+
translation.x += boundingRectangle.x;
209+
translation.y += boundingRectangle.y;
210+
211+
return result;
212+
};
213+
214+
/**
215+
* Duplicates a OrientedBoundingBox instance.
216+
*
217+
* @param {OrientedBoundingBox} box The bounding box to duplicate.
218+
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
219+
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if none was provided. (Returns undefined if box is undefined)
220+
*/
221+
OrientedBoundingBox.clone = function(box, result) {
222+
if (!defined(box)) {
223+
return undefined;
224+
}
225+
226+
if (!defined(result)) {
227+
return new OrientedBoundingBox(box.center, box.halfAxes);
228+
}
229+
230+
Cartesian3.clone(box.center, result.center);
231+
Matrix3.clone(box.halfAxes, result.halfAxes);
232+
233+
return result;
234+
};
235+
236+
/**
237+
* Determines which side of a plane the oriented bounding box is located.
238+
*
239+
* @param {OrientedBoundingBox} box The oriented bounding box to test.
240+
* @param {Cartesian4} plane The coefficients of the plane in Hessian Normal Form, as `ax + by + cz + d = 0`,
241+
* where (a, b, c) must be a unit normal vector.
242+
* The coefficients a, b, c, and d are the components x, y, z,
243+
* and w of the {@link Cartesian4}, respectively.
244+
* @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
245+
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
246+
* on the opposite side, and {@link Intersect.INTERSECTING} if the box
247+
* intersects the plane.
248+
*/
249+
OrientedBoundingBox.intersectPlane = function(box, plane) {
250+
//>>includeStart('debug', pragmas.debug);
251+
if (!defined(box)) {
252+
throw new DeveloperError('box is required.');
253+
}
254+
255+
if (!defined(plane)) {
256+
throw new DeveloperError('plane is required.');
257+
}
258+
//>>includeEnd('debug');
259+
260+
var center = box.center;
261+
// plane is used as if it is its normal; the first three components are assumed to be normalized
262+
var radEffective = Math.abs(Cartesian3.dot(plane, Matrix3.getColumn(box.halfAxes, 0, scratchCartesian1))) +
263+
Math.abs(Cartesian3.dot(plane, Matrix3.getColumn(box.halfAxes, 1, scratchCartesian2))) +
264+
Math.abs(Cartesian3.dot(plane, Matrix3.getColumn(box.halfAxes, 2, scratchCartesian3)));
265+
var distanceToPlane = Cartesian3.dot(plane, center) + plane.w;
266+
267+
if (distanceToPlane <= -radEffective) {
268+
// The entire box is on the negative side of the plane normal
269+
return Intersect.OUTSIDE;
270+
} else if (distanceToPlane >= radEffective) {
271+
// The entire box is on the positive side of the plane normal
272+
return Intersect.INSIDE;
273+
}
274+
return Intersect.INTERSECTING;
275+
};
276+
277+
/**
278+
* Determines which side of a plane the oriented bounding box is located.
279+
*
280+
* @param {Cartesian4} plane The coefficients of the plane in Hessian Normal Form, as `ax + by + cz + d = 0`,
281+
* where (a, b, c) must be a unit normal vector.
282+
* The coefficients a, b, c, and d are the components x, y, z,
283+
* and w of the {@link Cartesian4}, respectively.
284+
* @returns {Intersect} {@link Intersect.INSIDE} if the entire box is on the side of the plane
285+
* the normal is pointing, {@link Intersect.OUTSIDE} if the entire box is
286+
* on the opposite side, and {@link Intersect.INTERSECTING} if the box
287+
* intersects the plane.
288+
*/
289+
OrientedBoundingBox.prototype.intersectPlane = function(plane) {
290+
return OrientedBoundingBox.intersectPlane(this, plane);
291+
};
292+
293+
/**
294+
* Compares the provided OrientedBoundingBox componentwise and returns
295+
* <code>true</code> if they are equal, <code>false</code> otherwise.
296+
*
297+
* @param {OrientedBoundingBox} left The first OrientedBoundingBox.
298+
* @param {OrientedBoundingBox} right The second OrientedBoundingBox.
299+
* @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
300+
*/
301+
OrientedBoundingBox.equals = function(left, right) {
302+
return (left === right) ||
303+
((defined(left)) &&
304+
(defined(right)) &&
305+
Cartesian3.equals(left.center, right.center) &&
306+
Matrix3.equals(left.halfAxes, right.halfAxes));
307+
};
308+
309+
/**
310+
* Duplicates this OrientedBoundingBox instance.
311+
*
312+
* @param {OrientedBoundingBox} [result] The object onto which to store the result.
313+
* @returns {OrientedBoundingBox} The modified result parameter or a new OrientedBoundingBox instance if one was not provided.
314+
*/
315+
OrientedBoundingBox.prototype.clone = function(result) {
316+
return OrientedBoundingBox.clone(this, result);
317+
};
318+
319+
/**
320+
* Compares this OrientedBoundingBox against the provided OrientedBoundingBox componentwise and returns
321+
* <code>true</code> if they are equal, <code>false</code> otherwise.
322+
*
323+
* @param {OrientedBoundingBox} [right] The right hand side OrientedBoundingBox.
324+
* @returns {Boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
325+
*/
326+
OrientedBoundingBox.prototype.equals = function(right) {
327+
return OrientedBoundingBox.equals(this, right);
328+
};
329+
330+
return OrientedBoundingBox;
331+
});

0 commit comments

Comments
 (0)