Adding mapbox-gl branch

This commit is contained in:
Andreas Hocevar
2015-03-16 18:50:27 +01:00
parent 7985f030fa
commit 57ee7f52fd
3109 changed files with 943365 additions and 0 deletions
@@ -0,0 +1,589 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Provides an object representation of an AffineTransform and
* methods for working with it.
*/
goog.provide('goog.math.AffineTransform');
goog.require('goog.math');
/**
* Creates a 2D affine transform. An affine transform performs a linear
* mapping from 2D coordinates to other 2D coordinates that preserves the
* "straightness" and "parallelness" of lines.
*
* Such a coordinate transformation can be represented by a 3 row by 3 column
* matrix with an implied last row of [ 0 0 1 ]. This matrix transforms source
* coordinates (x,y) into destination coordinates (x',y') by considering them
* to be a column vector and multiplying the coordinate vector by the matrix
* according to the following process:
* <pre>
* [ x'] [ m00 m01 m02 ] [ x ] [ m00x + m01y + m02 ]
* [ y'] = [ m10 m11 m12 ] [ y ] = [ m10x + m11y + m12 ]
* [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ]
* </pre>
*
* This class is optimized for speed and minimizes calculations based on its
* knowledge of the underlying matrix (as opposed to say simply performing
* matrix multiplication).
*
* @param {number=} opt_m00 The m00 coordinate of the transform.
* @param {number=} opt_m10 The m10 coordinate of the transform.
* @param {number=} opt_m01 The m01 coordinate of the transform.
* @param {number=} opt_m11 The m11 coordinate of the transform.
* @param {number=} opt_m02 The m02 coordinate of the transform.
* @param {number=} opt_m12 The m12 coordinate of the transform.
* @struct
* @constructor
* @final
*/
goog.math.AffineTransform = function(opt_m00, opt_m10, opt_m01,
opt_m11, opt_m02, opt_m12) {
if (arguments.length == 6) {
this.setTransform(/** @type {number} */ (opt_m00),
/** @type {number} */ (opt_m10),
/** @type {number} */ (opt_m01),
/** @type {number} */ (opt_m11),
/** @type {number} */ (opt_m02),
/** @type {number} */ (opt_m12));
} else if (arguments.length != 0) {
throw Error('Insufficient matrix parameters');
} else {
this.m00_ = this.m11_ = 1;
this.m10_ = this.m01_ = this.m02_ = this.m12_ = 0;
}
};
/**
* @return {boolean} Whether this transform is the identity transform.
*/
goog.math.AffineTransform.prototype.isIdentity = function() {
return this.m00_ == 1 && this.m10_ == 0 && this.m01_ == 0 &&
this.m11_ == 1 && this.m02_ == 0 && this.m12_ == 0;
};
/**
* @return {!goog.math.AffineTransform} A copy of this transform.
*/
goog.math.AffineTransform.prototype.clone = function() {
return new goog.math.AffineTransform(this.m00_, this.m10_, this.m01_,
this.m11_, this.m02_, this.m12_);
};
/**
* Sets this transform to the matrix specified by the 6 values.
*
* @param {number} m00 The m00 coordinate of the transform.
* @param {number} m10 The m10 coordinate of the transform.
* @param {number} m01 The m01 coordinate of the transform.
* @param {number} m11 The m11 coordinate of the transform.
* @param {number} m02 The m02 coordinate of the transform.
* @param {number} m12 The m12 coordinate of the transform.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.setTransform = function(m00, m10, m01,
m11, m02, m12) {
if (!goog.isNumber(m00) || !goog.isNumber(m10) || !goog.isNumber(m01) ||
!goog.isNumber(m11) || !goog.isNumber(m02) || !goog.isNumber(m12)) {
throw Error('Invalid transform parameters');
}
this.m00_ = m00;
this.m10_ = m10;
this.m01_ = m01;
this.m11_ = m11;
this.m02_ = m02;
this.m12_ = m12;
return this;
};
/**
* Sets this transform to be identical to the given transform.
*
* @param {!goog.math.AffineTransform} tx The transform to copy.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.copyFrom = function(tx) {
this.m00_ = tx.m00_;
this.m10_ = tx.m10_;
this.m01_ = tx.m01_;
this.m11_ = tx.m11_;
this.m02_ = tx.m02_;
this.m12_ = tx.m12_;
return this;
};
/**
* Concatenates this transform with a scaling transformation.
*
* @param {number} sx The x-axis scaling factor.
* @param {number} sy The y-axis scaling factor.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.scale = function(sx, sy) {
this.m00_ *= sx;
this.m10_ *= sx;
this.m01_ *= sy;
this.m11_ *= sy;
return this;
};
/**
* Pre-concatenates this transform with a scaling transformation,
* i.e. calculates the following matrix product:
*
* <pre>
* [sx 0 0] [m00 m01 m02]
* [ 0 sy 0] [m10 m11 m12]
* [ 0 0 1] [ 0 0 1]
* </pre>
*
* @param {number} sx The x-axis scaling factor.
* @param {number} sy The y-axis scaling factor.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.preScale = function(sx, sy) {
this.m00_ *= sx;
this.m01_ *= sx;
this.m02_ *= sx;
this.m10_ *= sy;
this.m11_ *= sy;
this.m12_ *= sy;
return this;
};
/**
* Concatenates this transform with a translate transformation.
*
* @param {number} dx The distance to translate in the x direction.
* @param {number} dy The distance to translate in the y direction.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.translate = function(dx, dy) {
this.m02_ += dx * this.m00_ + dy * this.m01_;
this.m12_ += dx * this.m10_ + dy * this.m11_;
return this;
};
/**
* Pre-concatenates this transform with a translate transformation,
* i.e. calculates the following matrix product:
*
* <pre>
* [1 0 dx] [m00 m01 m02]
* [0 1 dy] [m10 m11 m12]
* [0 0 1] [ 0 0 1]
* </pre>
*
* @param {number} dx The distance to translate in the x direction.
* @param {number} dy The distance to translate in the y direction.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.preTranslate = function(dx, dy) {
this.m02_ += dx;
this.m12_ += dy;
return this;
};
/**
* Concatenates this transform with a rotation transformation around an anchor
* point.
*
* @param {number} theta The angle of rotation measured in radians.
* @param {number} x The x coordinate of the anchor point.
* @param {number} y The y coordinate of the anchor point.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.rotate = function(theta, x, y) {
return this.concatenate(
goog.math.AffineTransform.getRotateInstance(theta, x, y));
};
/**
* Pre-concatenates this transform with a rotation transformation around an
* anchor point.
*
* @param {number} theta The angle of rotation measured in radians.
* @param {number} x The x coordinate of the anchor point.
* @param {number} y The y coordinate of the anchor point.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.preRotate = function(theta, x, y) {
return this.preConcatenate(
goog.math.AffineTransform.getRotateInstance(theta, x, y));
};
/**
* Concatenates this transform with a shear transformation.
*
* @param {number} shx The x shear factor.
* @param {number} shy The y shear factor.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.shear = function(shx, shy) {
var m00 = this.m00_;
var m10 = this.m10_;
this.m00_ += shy * this.m01_;
this.m10_ += shy * this.m11_;
this.m01_ += shx * m00;
this.m11_ += shx * m10;
return this;
};
/**
* Pre-concatenates this transform with a shear transformation.
* i.e. calculates the following matrix product:
*
* <pre>
* [ 1 shx 0] [m00 m01 m02]
* [shy 1 0] [m10 m11 m12]
* [ 0 0 1] [ 0 0 1]
* </pre>
*
* @param {number} shx The x shear factor.
* @param {number} shy The y shear factor.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.preShear = function(shx, shy) {
var m00 = this.m00_;
var m01 = this.m01_;
var m02 = this.m02_;
this.m00_ += shx * this.m10_;
this.m01_ += shx * this.m11_;
this.m02_ += shx * this.m12_;
this.m10_ += shy * m00;
this.m11_ += shy * m01;
this.m12_ += shy * m02;
return this;
};
/**
* @return {string} A string representation of this transform. The format of
* of the string is compatible with SVG matrix notation, i.e.
* "matrix(a,b,c,d,e,f)".
* @override
*/
goog.math.AffineTransform.prototype.toString = function() {
return 'matrix(' +
[this.m00_, this.m10_, this.m01_, this.m11_, this.m02_, this.m12_].join(
',') +
')';
};
/**
* @return {number} The scaling factor in the x-direction (m00).
*/
goog.math.AffineTransform.prototype.getScaleX = function() {
return this.m00_;
};
/**
* @return {number} The scaling factor in the y-direction (m11).
*/
goog.math.AffineTransform.prototype.getScaleY = function() {
return this.m11_;
};
/**
* @return {number} The translation in the x-direction (m02).
*/
goog.math.AffineTransform.prototype.getTranslateX = function() {
return this.m02_;
};
/**
* @return {number} The translation in the y-direction (m12).
*/
goog.math.AffineTransform.prototype.getTranslateY = function() {
return this.m12_;
};
/**
* @return {number} The shear factor in the x-direction (m01).
*/
goog.math.AffineTransform.prototype.getShearX = function() {
return this.m01_;
};
/**
* @return {number} The shear factor in the y-direction (m10).
*/
goog.math.AffineTransform.prototype.getShearY = function() {
return this.m10_;
};
/**
* Concatenates an affine transform to this transform.
*
* @param {!goog.math.AffineTransform} tx The transform to concatenate.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.concatenate = function(tx) {
var m0 = this.m00_;
var m1 = this.m01_;
this.m00_ = tx.m00_ * m0 + tx.m10_ * m1;
this.m01_ = tx.m01_ * m0 + tx.m11_ * m1;
this.m02_ += tx.m02_ * m0 + tx.m12_ * m1;
m0 = this.m10_;
m1 = this.m11_;
this.m10_ = tx.m00_ * m0 + tx.m10_ * m1;
this.m11_ = tx.m01_ * m0 + tx.m11_ * m1;
this.m12_ += tx.m02_ * m0 + tx.m12_ * m1;
return this;
};
/**
* Pre-concatenates an affine transform to this transform.
*
* @param {!goog.math.AffineTransform} tx The transform to preconcatenate.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.preConcatenate = function(tx) {
var m0 = this.m00_;
var m1 = this.m10_;
this.m00_ = tx.m00_ * m0 + tx.m01_ * m1;
this.m10_ = tx.m10_ * m0 + tx.m11_ * m1;
m0 = this.m01_;
m1 = this.m11_;
this.m01_ = tx.m00_ * m0 + tx.m01_ * m1;
this.m11_ = tx.m10_ * m0 + tx.m11_ * m1;
m0 = this.m02_;
m1 = this.m12_;
this.m02_ = tx.m00_ * m0 + tx.m01_ * m1 + tx.m02_;
this.m12_ = tx.m10_ * m0 + tx.m11_ * m1 + tx.m12_;
return this;
};
/**
* Transforms an array of coordinates by this transform and stores the result
* into a destination array.
*
* @param {!Array<number>} src The array containing the source points
* as x, y value pairs.
* @param {number} srcOff The offset to the first point to be transformed.
* @param {!Array<number>} dst The array into which to store the transformed
* point pairs.
* @param {number} dstOff The offset of the location of the first transformed
* point in the destination array.
* @param {number} numPts The number of points to tranform.
*/
goog.math.AffineTransform.prototype.transform = function(src, srcOff, dst,
dstOff, numPts) {
var i = srcOff;
var j = dstOff;
var srcEnd = srcOff + 2 * numPts;
while (i < srcEnd) {
var x = src[i++];
var y = src[i++];
dst[j++] = x * this.m00_ + y * this.m01_ + this.m02_;
dst[j++] = x * this.m10_ + y * this.m11_ + this.m12_;
}
};
/**
* @return {number} The determinant of this transform.
*/
goog.math.AffineTransform.prototype.getDeterminant = function() {
return this.m00_ * this.m11_ - this.m01_ * this.m10_;
};
/**
* Returns whether the transform is invertible. A transform is not invertible
* if the determinant is 0 or any value is non-finite or NaN.
*
* @return {boolean} Whether the transform is invertible.
*/
goog.math.AffineTransform.prototype.isInvertible = function() {
var det = this.getDeterminant();
return goog.math.isFiniteNumber(det) &&
goog.math.isFiniteNumber(this.m02_) &&
goog.math.isFiniteNumber(this.m12_) &&
det != 0;
};
/**
* @return {!goog.math.AffineTransform} An AffineTransform object
* representing the inverse transformation.
*/
goog.math.AffineTransform.prototype.createInverse = function() {
var det = this.getDeterminant();
return new goog.math.AffineTransform(
this.m11_ / det,
-this.m10_ / det,
-this.m01_ / det,
this.m00_ / det,
(this.m01_ * this.m12_ - this.m11_ * this.m02_) / det,
(this.m10_ * this.m02_ - this.m00_ * this.m12_) / det);
};
/**
* Creates a transform representing a scaling transformation.
*
* @param {number} sx The x-axis scaling factor.
* @param {number} sy The y-axis scaling factor.
* @return {!goog.math.AffineTransform} A transform representing a scaling
* transformation.
*/
goog.math.AffineTransform.getScaleInstance = function(sx, sy) {
return new goog.math.AffineTransform().setToScale(sx, sy);
};
/**
* Creates a transform representing a translation transformation.
*
* @param {number} dx The distance to translate in the x direction.
* @param {number} dy The distance to translate in the y direction.
* @return {!goog.math.AffineTransform} A transform representing a
* translation transformation.
*/
goog.math.AffineTransform.getTranslateInstance = function(dx, dy) {
return new goog.math.AffineTransform().setToTranslation(dx, dy);
};
/**
* Creates a transform representing a shearing transformation.
*
* @param {number} shx The x-axis shear factor.
* @param {number} shy The y-axis shear factor.
* @return {!goog.math.AffineTransform} A transform representing a shearing
* transformation.
*/
goog.math.AffineTransform.getShearInstance = function(shx, shy) {
return new goog.math.AffineTransform().setToShear(shx, shy);
};
/**
* Creates a transform representing a rotation transformation.
*
* @param {number} theta The angle of rotation measured in radians.
* @param {number} x The x coordinate of the anchor point.
* @param {number} y The y coordinate of the anchor point.
* @return {!goog.math.AffineTransform} A transform representing a rotation
* transformation.
*/
goog.math.AffineTransform.getRotateInstance = function(theta, x, y) {
return new goog.math.AffineTransform().setToRotation(theta, x, y);
};
/**
* Sets this transform to a scaling transformation.
*
* @param {number} sx The x-axis scaling factor.
* @param {number} sy The y-axis scaling factor.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.setToScale = function(sx, sy) {
return this.setTransform(sx, 0, 0, sy, 0, 0);
};
/**
* Sets this transform to a translation transformation.
*
* @param {number} dx The distance to translate in the x direction.
* @param {number} dy The distance to translate in the y direction.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.setToTranslation = function(dx, dy) {
return this.setTransform(1, 0, 0, 1, dx, dy);
};
/**
* Sets this transform to a shearing transformation.
*
* @param {number} shx The x-axis shear factor.
* @param {number} shy The y-axis shear factor.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.setToShear = function(shx, shy) {
return this.setTransform(1, shy, shx, 1, 0, 0);
};
/**
* Sets this transform to a rotation transformation.
*
* @param {number} theta The angle of rotation measured in radians.
* @param {number} x The x coordinate of the anchor point.
* @param {number} y The y coordinate of the anchor point.
* @return {!goog.math.AffineTransform} This affine transform.
*/
goog.math.AffineTransform.prototype.setToRotation = function(theta, x, y) {
var cos = Math.cos(theta);
var sin = Math.sin(theta);
return this.setTransform(cos, sin, -sin, cos,
x - x * cos + y * sin, y - x * sin - y * cos);
};
/**
* Compares two affine transforms for equality.
*
* @param {goog.math.AffineTransform} tx The other affine transform.
* @return {boolean} whether the two transforms are equal.
*/
goog.math.AffineTransform.prototype.equals = function(tx) {
if (this == tx) {
return true;
}
if (!tx) {
return false;
}
return this.m00_ == tx.m00_ &&
this.m01_ == tx.m01_ &&
this.m02_ == tx.m02_ &&
this.m10_ == tx.m10_ &&
this.m11_ == tx.m11_ &&
this.m12_ == tx.m12_;
};
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Unit Tests - goog.math.AffineTransform</title>
<script src="../base.js"></script>
<script>
goog.require('goog.math.AffineTransformTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,359 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.AffineTransformTest');
goog.require('goog.array');
goog.require('goog.math');
goog.require('goog.math.AffineTransform');
goog.require('goog.testing.jsunit');
goog.setTestOnly('goog.math.AffineTransformTest');
function testGetTranslateInstance() {
var tx = goog.math.AffineTransform.getTranslateInstance(2, 4);
assertEquals(1, tx.getScaleX());
assertEquals(0, tx.getShearY());
assertEquals(0, tx.getShearX());
assertEquals(1, tx.getScaleY());
assertEquals(2, tx.getTranslateX());
assertEquals(4, tx.getTranslateY());
}
function testGetScaleInstance() {
var tx = goog.math.AffineTransform.getScaleInstance(2, 4);
assertEquals(2, tx.getScaleX());
assertEquals(0, tx.getShearY());
assertEquals(0, tx.getShearX());
assertEquals(4, tx.getScaleY());
assertEquals(0, tx.getTranslateX());
assertEquals(0, tx.getTranslateY());
}
function testGetRotateInstance() {
var tx = goog.math.AffineTransform.getRotateInstance(Math.PI / 2, 1, 2);
assertRoughlyEquals(0, tx.getScaleX(), 1e-9);
assertRoughlyEquals(1, tx.getShearY(), 1e-9);
assertRoughlyEquals(-1, tx.getShearX(), 1e-9);
assertRoughlyEquals(0, tx.getScaleY(), 1e-9);
assertRoughlyEquals(3, tx.getTranslateX(), 1e-9);
assertRoughlyEquals(1, tx.getTranslateY(), 1e-9);
}
function testGetShearInstance() {
var tx = goog.math.AffineTransform.getShearInstance(2, 4);
assertEquals(1, tx.getScaleX());
assertEquals(4, tx.getShearY());
assertEquals(2, tx.getShearX());
assertEquals(1, tx.getScaleY());
assertEquals(0, tx.getTranslateX());
assertEquals(0, tx.getTranslateY());
}
function testConstructor() {
assertThrows(function() {
new goog.math.AffineTransform([0, 0]);
});
assertThrows(function() {
new goog.math.AffineTransform({});
});
assertThrows(function() {
new goog.math.AffineTransform(0, 0, 0, 'a', 0, 0);
});
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
assertEquals(1, tx.getScaleX());
assertEquals(2, tx.getShearY());
assertEquals(3, tx.getShearX());
assertEquals(4, tx.getScaleY());
assertEquals(5, tx.getTranslateX());
assertEquals(6, tx.getTranslateY());
tx = new goog.math.AffineTransform();
assert(tx.isIdentity());
}
function testIsIdentity() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
assertFalse(tx.isIdentity());
tx.setTransform(1, 0, 0, 1, 0, 0);
assert(tx.isIdentity());
}
function testClone() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
var copy = tx.clone();
assertEquals(copy.getScaleX(), tx.getScaleX());
assertEquals(copy.getShearY(), tx.getShearY());
assertEquals(copy.getShearX(), tx.getShearX());
assertEquals(copy.getScaleY(), tx.getScaleY());
assertEquals(copy.getTranslateX(), tx.getTranslateX());
assertEquals(copy.getTranslateY(), tx.getTranslateY());
}
function testSetTransform() {
var tx = new goog.math.AffineTransform();
assertThrows(function() {
tx.setTransform(1, 2, 3, 4, 6);
});
assertThrows(function() {
tx.setTransform('a', 2, 3, 4, 5, 6);
});
tx.setTransform(1, 2, 3, 4, 5, 6);
assertEquals(1, tx.getScaleX());
assertEquals(2, tx.getShearY());
assertEquals(3, tx.getShearX());
assertEquals(4, tx.getScaleY());
assertEquals(5, tx.getTranslateX());
assertEquals(6, tx.getTranslateY());
}
function testScale() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.scale(2, 3);
assertEquals(2, tx.getScaleX());
assertEquals(4, tx.getShearY());
assertEquals(9, tx.getShearX());
assertEquals(12, tx.getScaleY());
assertEquals(5, tx.getTranslateX());
assertEquals(6, tx.getTranslateY());
}
function testPreScale() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.preScale(2, 3);
assertEquals(2, tx.getScaleX());
assertEquals(6, tx.getShearY());
assertEquals(6, tx.getShearX());
assertEquals(12, tx.getScaleY());
assertEquals(10, tx.getTranslateX());
assertEquals(18, tx.getTranslateY());
}
function testTranslate() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.translate(2, 3);
assertEquals(1, tx.getScaleX());
assertEquals(2, tx.getShearY());
assertEquals(3, tx.getShearX());
assertEquals(4, tx.getScaleY());
assertEquals(16, tx.getTranslateX());
assertEquals(22, tx.getTranslateY());
}
function testPreTranslate() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.preTranslate(2, 3);
assertEquals(1, tx.getScaleX());
assertEquals(2, tx.getShearY());
assertEquals(3, tx.getShearX());
assertEquals(4, tx.getScaleY());
assertEquals(7, tx.getTranslateX());
assertEquals(9, tx.getTranslateY());
}
function testRotate() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.rotate(Math.PI / 2, 1, 1);
assertRoughlyEquals(3, tx.getScaleX(), 1e-9);
assertRoughlyEquals(4, tx.getShearY(), 1e-9);
assertRoughlyEquals(-1, tx.getShearX(), 1e-9);
assertRoughlyEquals(-2, tx.getScaleY(), 1e-9);
assertRoughlyEquals(7, tx.getTranslateX(), 1e-9);
assertRoughlyEquals(10, tx.getTranslateY(), 1e-9);
}
function testPreRotate() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.preRotate(Math.PI / 2, 1, 1);
assertRoughlyEquals(-2, tx.getScaleX(), 1e-9);
assertRoughlyEquals(1, tx.getShearY(), 1e-9);
assertRoughlyEquals(-4, tx.getShearX(), 1e-9);
assertRoughlyEquals(3, tx.getScaleY(), 1e-9);
assertRoughlyEquals(-4, tx.getTranslateX(), 1e-9);
assertRoughlyEquals(5, tx.getTranslateY(), 1e-9);
}
function testShear() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.shear(2, 3);
assertEquals(10, tx.getScaleX());
assertEquals(14, tx.getShearY());
assertEquals(5, tx.getShearX());
assertEquals(8, tx.getScaleY());
assertEquals(5, tx.getTranslateX());
assertEquals(6, tx.getTranslateY());
}
function testPreShear() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.preShear(2, 3);
assertEquals(5, tx.getScaleX());
assertEquals(5, tx.getShearY());
assertEquals(11, tx.getShearX());
assertEquals(13, tx.getScaleY());
assertEquals(17, tx.getTranslateX());
assertEquals(21, tx.getTranslateY());
}
function testConcatentate() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.concatenate(new goog.math.AffineTransform(2, 1, 6, 5, 4, 3));
assertEquals(5, tx.getScaleX());
assertEquals(8, tx.getShearY());
assertEquals(21, tx.getShearX());
assertEquals(32, tx.getScaleY());
assertEquals(18, tx.getTranslateX());
assertEquals(26, tx.getTranslateY());
}
function testPreConcatentate() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
tx.preConcatenate(new goog.math.AffineTransform(2, 1, 6, 5, 4, 3));
assertEquals(14, tx.getScaleX());
assertEquals(11, tx.getShearY());
assertEquals(30, tx.getShearX());
assertEquals(23, tx.getScaleY());
assertEquals(50, tx.getTranslateX());
assertEquals(38, tx.getTranslateY());
}
function testAssociativeConcatenate() {
var x = new goog.math.AffineTransform(2, 3, 5, 7, 11, 13).concatenate(
new goog.math.AffineTransform(17, 19, 23, 29, 31, 37));
var y = new goog.math.AffineTransform(17, 19, 23, 29, 31, 37)
.preConcatenate(new goog.math.AffineTransform(2, 3, 5, 7, 11, 13));
assertEquals(x.getScaleX(), y.getScaleX());
assertEquals(x.getShearY(), y.getShearY());
assertEquals(x.getShearX(), y.getShearX());
assertEquals(x.getScaleY(), y.getScaleY());
assertEquals(x.getTranslateX(), y.getTranslateX());
assertEquals(x.getTranslateY(), y.getTranslateY());
}
function testTransform() {
var srcPts = [0, 0, 1, 0, 1, 1, 0, 1];
var dstPts = [];
var tx = goog.math.AffineTransform.getScaleInstance(2, 3);
tx.translate(5, 10);
tx.rotate(Math.PI / 4, 5, 10);
tx.transform(srcPts, 0, dstPts, 0, 4);
assert(goog.array.equals(
[27.071068, 28.180195, 28.485281, 30.301516,
27.071068, 32.422836, 25.656855, 30.301516],
dstPts,
goog.math.nearlyEquals));
}
function testGetDeterminant() {
var tx = goog.math.AffineTransform.getScaleInstance(2, 3);
tx.translate(5, 10);
tx.rotate(Math.PI / 4, 5, 10);
assertRoughlyEquals(6, tx.getDeterminant(), 0.001);
}
function testIsInvertible() {
assertTrue(new goog.math.AffineTransform(2, 3, 4, 5, 6, 7).
isInvertible());
assertTrue(new goog.math.AffineTransform(1, 0, 0, 1, 0, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(NaN, 0, 0, 1, 0, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, NaN, 0, 1, 0, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, 0, NaN, 1, 0, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, 0, 0, NaN, 0, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, 0, 0, 1, NaN, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, 0, 0, 1, 0, NaN).
isInvertible());
assertFalse(new goog.math.AffineTransform(Infinity, 0, 0, 1, 0, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, Infinity, 0, 1, 0, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, 0, Infinity, 1, 0, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, 0, 0, Infinity, 0, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, 0, 0, 1, Infinity, 0).
isInvertible());
assertFalse(new goog.math.AffineTransform(1, 0, 0, 1, 0, Infinity).
isInvertible());
assertFalse(new goog.math.AffineTransform(0, 0, 0, 0, 1, 0).
isInvertible());
}
function testCreateInverse() {
var tx = goog.math.AffineTransform.getScaleInstance(2, 3);
tx.translate(5, 10);
tx.rotate(Math.PI / 4, 5, 10);
var inverse = tx.createInverse();
assert(goog.math.nearlyEquals(0.353553, inverse.getScaleX()));
assert(goog.math.nearlyEquals(-0.353553, inverse.getShearY()));
assert(goog.math.nearlyEquals(0.235702, inverse.getShearX()));
assert(goog.math.nearlyEquals(0.235702, inverse.getScaleY()));
assert(goog.math.nearlyEquals(-16.213203, inverse.getTranslateX()));
assert(goog.math.nearlyEquals(2.928932, inverse.getTranslateY()));
}
function testCopyFrom() {
var from = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
var to = new goog.math.AffineTransform();
to.copyFrom(from);
assertEquals(from.getScaleX(), to.getScaleX());
assertEquals(from.getShearY(), to.getShearY());
assertEquals(from.getShearX(), to.getShearX());
assertEquals(from.getScaleY(), to.getScaleY());
assertEquals(from.getTranslateX(), to.getTranslateX());
assertEquals(from.getTranslateY(), to.getTranslateY());
}
function testToString() {
var tx = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
assertEquals('matrix(1,2,3,4,5,6)', tx.toString());
}
function testEquals() {
var tx1 = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
var tx2 = new goog.math.AffineTransform(1, 2, 3, 4, 5, 6);
assertEqualsMethod(tx1, tx2, true);
tx2 = new goog.math.AffineTransform(-1, 2, 3, 4, 5, 6);
assertEqualsMethod(tx1, tx2, false);
tx2 = new goog.math.AffineTransform(1, -1, 3, 4, 5, 6);
assertEqualsMethod(tx1, tx2, false);
tx2 = new goog.math.AffineTransform(1, 2, -3, 4, 5, 6);
assertEqualsMethod(tx1, tx2, false);
tx2 = new goog.math.AffineTransform(1, 2, 3, -4, 5, 6);
assertEqualsMethod(tx1, tx2, false);
tx2 = new goog.math.AffineTransform(1, 2, 3, 4, -5, 6);
assertEqualsMethod(tx1, tx2, false);
tx2 = new goog.math.AffineTransform(1, 2, 3, 4, 5, -6);
assertEqualsMethod(tx1, tx2, false);
}
function assertEqualsMethod(tx1, tx2, expected) {
assertEquals(expected, tx1.equals(tx2));
assertEquals(expected, tx2.equals(tx1));
assertEquals(true, tx1.equals(tx1));
assertEquals(true, tx2.equals(tx2));
}
@@ -0,0 +1,340 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Represents a cubic Bezier curve.
*
* Uses the deCasteljau algorithm to compute points on the curve.
* http://en.wikipedia.org/wiki/De_Casteljau's_algorithm
*
* Currently it uses an unrolled version of the algorithm for speed. Eventually
* it may be useful to use the loop form of the algorithm in order to support
* curves of arbitrary degree.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.math.Bezier');
goog.require('goog.math');
goog.require('goog.math.Coordinate');
/**
* Object representing a cubic bezier curve.
* @param {number} x0 X coordinate of the start point.
* @param {number} y0 Y coordinate of the start point.
* @param {number} x1 X coordinate of the first control point.
* @param {number} y1 Y coordinate of the first control point.
* @param {number} x2 X coordinate of the second control point.
* @param {number} y2 Y coordinate of the second control point.
* @param {number} x3 X coordinate of the end point.
* @param {number} y3 Y coordinate of the end point.
* @struct
* @constructor
* @final
*/
goog.math.Bezier = function(x0, y0, x1, y1, x2, y2, x3, y3) {
/**
* X coordinate of the first point.
* @type {number}
*/
this.x0 = x0;
/**
* Y coordinate of the first point.
* @type {number}
*/
this.y0 = y0;
/**
* X coordinate of the first control point.
* @type {number}
*/
this.x1 = x1;
/**
* Y coordinate of the first control point.
* @type {number}
*/
this.y1 = y1;
/**
* X coordinate of the second control point.
* @type {number}
*/
this.x2 = x2;
/**
* Y coordinate of the second control point.
* @type {number}
*/
this.y2 = y2;
/**
* X coordinate of the end point.
* @type {number}
*/
this.x3 = x3;
/**
* Y coordinate of the end point.
* @type {number}
*/
this.y3 = y3;
};
/**
* Constant used to approximate ellipses.
* See: http://canvaspaint.org/blog/2006/12/ellipse/
* @type {number}
*/
goog.math.Bezier.KAPPA = 4 * (Math.sqrt(2) - 1) / 3;
/**
* @return {!goog.math.Bezier} A copy of this curve.
*/
goog.math.Bezier.prototype.clone = function() {
return new goog.math.Bezier(this.x0, this.y0, this.x1, this.y1, this.x2,
this.y2, this.x3, this.y3);
};
/**
* Test if the given curve is exactly the same as this one.
* @param {goog.math.Bezier} other The other curve.
* @return {boolean} Whether the given curve is the same as this one.
*/
goog.math.Bezier.prototype.equals = function(other) {
return this.x0 == other.x0 && this.y0 == other.y0 && this.x1 == other.x1 &&
this.y1 == other.y1 && this.x2 == other.x2 && this.y2 == other.y2 &&
this.x3 == other.x3 && this.y3 == other.y3;
};
/**
* Modifies the curve in place to progress in the opposite direction.
*/
goog.math.Bezier.prototype.flip = function() {
var temp = this.x0;
this.x0 = this.x3;
this.x3 = temp;
temp = this.y0;
this.y0 = this.y3;
this.y3 = temp;
temp = this.x1;
this.x1 = this.x2;
this.x2 = temp;
temp = this.y1;
this.y1 = this.y2;
this.y2 = temp;
};
/**
* Computes the curve's X coordinate at a point between 0 and 1.
* @param {number} t The point on the curve to find.
* @return {number} The computed coordinate.
*/
goog.math.Bezier.prototype.getPointX = function(t) {
// Special case start and end.
if (t == 0) {
return this.x0;
} else if (t == 1) {
return this.x3;
}
// Step one - from 4 points to 3
var ix0 = goog.math.lerp(this.x0, this.x1, t);
var ix1 = goog.math.lerp(this.x1, this.x2, t);
var ix2 = goog.math.lerp(this.x2, this.x3, t);
// Step two - from 3 points to 2
ix0 = goog.math.lerp(ix0, ix1, t);
ix1 = goog.math.lerp(ix1, ix2, t);
// Final step - last point
return goog.math.lerp(ix0, ix1, t);
};
/**
* Computes the curve's Y coordinate at a point between 0 and 1.
* @param {number} t The point on the curve to find.
* @return {number} The computed coordinate.
*/
goog.math.Bezier.prototype.getPointY = function(t) {
// Special case start and end.
if (t == 0) {
return this.y0;
} else if (t == 1) {
return this.y3;
}
// Step one - from 4 points to 3
var iy0 = goog.math.lerp(this.y0, this.y1, t);
var iy1 = goog.math.lerp(this.y1, this.y2, t);
var iy2 = goog.math.lerp(this.y2, this.y3, t);
// Step two - from 3 points to 2
iy0 = goog.math.lerp(iy0, iy1, t);
iy1 = goog.math.lerp(iy1, iy2, t);
// Final step - last point
return goog.math.lerp(iy0, iy1, t);
};
/**
* Computes the curve at a point between 0 and 1.
* @param {number} t The point on the curve to find.
* @return {!goog.math.Coordinate} The computed coordinate.
*/
goog.math.Bezier.prototype.getPoint = function(t) {
return new goog.math.Coordinate(this.getPointX(t), this.getPointY(t));
};
/**
* Changes this curve in place to be the portion of itself from [t, 1].
* @param {number} t The start of the desired portion of the curve.
*/
goog.math.Bezier.prototype.subdivideLeft = function(t) {
if (t == 1) {
return;
}
// Step one - from 4 points to 3
var ix0 = goog.math.lerp(this.x0, this.x1, t);
var iy0 = goog.math.lerp(this.y0, this.y1, t);
var ix1 = goog.math.lerp(this.x1, this.x2, t);
var iy1 = goog.math.lerp(this.y1, this.y2, t);
var ix2 = goog.math.lerp(this.x2, this.x3, t);
var iy2 = goog.math.lerp(this.y2, this.y3, t);
// Collect our new x1 and y1
this.x1 = ix0;
this.y1 = iy0;
// Step two - from 3 points to 2
ix0 = goog.math.lerp(ix0, ix1, t);
iy0 = goog.math.lerp(iy0, iy1, t);
ix1 = goog.math.lerp(ix1, ix2, t);
iy1 = goog.math.lerp(iy1, iy2, t);
// Collect our new x2 and y2
this.x2 = ix0;
this.y2 = iy0;
// Final step - last point
this.x3 = goog.math.lerp(ix0, ix1, t);
this.y3 = goog.math.lerp(iy0, iy1, t);
};
/**
* Changes this curve in place to be the portion of itself from [0, t].
* @param {number} t The end of the desired portion of the curve.
*/
goog.math.Bezier.prototype.subdivideRight = function(t) {
this.flip();
this.subdivideLeft(1 - t);
this.flip();
};
/**
* Changes this curve in place to be the portion of itself from [s, t].
* @param {number} s The start of the desired portion of the curve.
* @param {number} t The end of the desired portion of the curve.
*/
goog.math.Bezier.prototype.subdivide = function(s, t) {
this.subdivideRight(s);
this.subdivideLeft((t - s) / (1 - s));
};
/**
* Computes the position t of a point on the curve given its x coordinate.
* That is, for an input xVal, finds t s.t. getPointX(t) = xVal.
* As such, the following should always be true up to some small epsilon:
* t ~ solvePositionFromXValue(getPointX(t)) for t in [0, 1].
* @param {number} xVal The x coordinate of the point to find on the curve.
* @return {number} The position t.
*/
goog.math.Bezier.prototype.solvePositionFromXValue = function(xVal) {
// Desired precision on the computation.
var epsilon = 1e-6;
// Initial estimate of t using linear interpolation.
var t = (xVal - this.x0) / (this.x3 - this.x0);
if (t <= 0) {
return 0;
} else if (t >= 1) {
return 1;
}
// Try gradient descent to solve for t. If it works, it is very fast.
var tMin = 0;
var tMax = 1;
for (var i = 0; i < 8; i++) {
var value = this.getPointX(t);
var derivative = (this.getPointX(t + epsilon) - value) / epsilon;
if (Math.abs(value - xVal) < epsilon) {
return t;
} else if (Math.abs(derivative) < epsilon) {
break;
} else {
if (value < xVal) {
tMin = t;
} else {
tMax = t;
}
t -= (value - xVal) / derivative;
}
}
// If the gradient descent got stuck in a local minimum, e.g. because
// the derivative was close to 0, use a Dichotomy refinement instead.
// We limit the number of interations to 8.
for (var i = 0; Math.abs(value - xVal) > epsilon && i < 8; i++) {
if (value < xVal) {
tMin = t;
t = (t + tMax) / 2;
} else {
tMax = t;
t = (t + tMin) / 2;
}
value = this.getPointX(t);
}
return t;
};
/**
* Computes the y coordinate of a point on the curve given its x coordinate.
* @param {number} xVal The x coordinate of the point on the curve.
* @return {number} The y coordinate of the point on the curve.
*/
goog.math.Bezier.prototype.solveYValueFromXValue = function(xVal) {
return this.getPointY(this.solvePositionFromXValue(xVal));
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2007 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Bezier
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.BezierTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,126 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.BezierTest');
goog.setTestOnly('goog.math.BezierTest');
goog.require('goog.math');
goog.require('goog.math.Bezier');
goog.require('goog.math.Coordinate');
goog.require('goog.testing.jsunit');
function testEquals() {
var input = new goog.math.Bezier(1, 2, 3, 4, 5, 6, 7, 8);
assert(input.equals(input));
}
function testClone() {
var input = new goog.math.Bezier(1, 2, 3, 4, 5, 6, 7, 8);
assertNotEquals('Clone returns a new object', input, input.clone());
assert('Contents of clone match original', input.equals(input.clone()));
}
function testFlip() {
var input = new goog.math.Bezier(1, 1, 2, 2, 3, 3, 4, 4);
var compare = new goog.math.Bezier(4, 4, 3, 3, 2, 2, 1, 1);
var flipped = input.clone();
flipped.flip();
assert('Flipped behaves as expected', compare.equals(flipped));
flipped.flip();
assert('Flipping twice gives original', input.equals(flipped));
}
function testGetPoint() {
var input = new goog.math.Bezier(0, 1, 1, 2, 2, 3, 3, 4);
assert(goog.math.Coordinate.equals(input.getPoint(0),
new goog.math.Coordinate(0, 1)));
assert(goog.math.Coordinate.equals(input.getPoint(1),
new goog.math.Coordinate(3, 4)));
assert(goog.math.Coordinate.equals(input.getPoint(0.5),
new goog.math.Coordinate(1.5, 2.5)));
}
function testGetPointX() {
var input = new goog.math.Bezier(0, 1, 1, 2, 2, 3, 3, 4);
assert(goog.math.nearlyEquals(input.getPointX(0), 0));
assert(goog.math.nearlyEquals(input.getPointX(1), 3));
assert(goog.math.nearlyEquals(input.getPointX(0.5), 1.5));
}
function testGetPointY() {
var input = new goog.math.Bezier(0, 1, 1, 2, 2, 3, 3, 4);
assert(goog.math.nearlyEquals(input.getPointY(0), 1));
assert(goog.math.nearlyEquals(input.getPointY(1), 4));
assert(goog.math.nearlyEquals(input.getPointY(0.5), 2.5));
}
function testSubdivide() {
var input = new goog.math.Bezier(0, 1, 1, 2, 2, 3, 3, 4);
input.subdivide(1 / 3, 2 / 3);
assert(goog.math.nearlyEquals(1, input.x0));
assert(goog.math.nearlyEquals(2, input.y0));
assert(goog.math.nearlyEquals(2, input.x3));
assert(goog.math.nearlyEquals(3, input.y3));
}
function testSolvePositionFromXValue() {
var eps = 1e-6;
var bezier = new goog.math.Bezier(0, 0, 0.25, 0.1, 0.25, 1, 1, 1);
var pt = bezier.getPoint(0.5);
assertRoughlyEquals(0.3125, pt.x, eps);
assertRoughlyEquals(0.5375, pt.y, eps);
assertRoughlyEquals(0.321,
bezier.solvePositionFromXValue(bezier.getPoint(0.321).x), eps);
}
function testSolveYValueFromXValue() {
var eps = 1e-6;
// The following example is taken from
// http://www.netzgesta.de/dev/cubic-bezier-timing-function.html.
// The timing values shown in that page are 1 - <value> so the
// bezier curves in this test are constructed with 1 - ctrl points.
// E.g. ctrl points (0, 0, 0.25, 0.1, 0.25, 1, 1, 1) become
// (1, 1, 0.75, 0, 0.75, 0.9, 0, 0) here. Since chanding the order of
// the ctrl points does not affect the shape of the curve, once can also
// have (0, 0, 0.75, 0.9, 0.75, 0, 1, 1).
// netzgesta example.
var bezier = new goog.math.Bezier(1, 1, 0.75, 0.9, 0.75, 0, 0, 0);
assertRoughlyEquals(0.024374631, bezier.solveYValueFromXValue(0.2), eps);
assertRoughlyEquals(0.317459494, bezier.solveYValueFromXValue(0.6), eps);
assertRoughlyEquals(0.905205002, bezier.solveYValueFromXValue(0.9), eps);
// netzgesta example with ctrl points in the reverse order so that 1st and
// last ctrl points are (0, 0) and (1, 1). Note the result is exactly the
// same.
bezier = new goog.math.Bezier(0, 0, 0.75, 0, 0.75, 0.9, 1, 1);
assertRoughlyEquals(0.024374631, bezier.solveYValueFromXValue(0.2), eps);
assertRoughlyEquals(0.317459494, bezier.solveYValueFromXValue(0.6), eps);
assertRoughlyEquals(0.905205002, bezier.solveYValueFromXValue(0.9), eps);
// Ease-out css animation timing in webkit.
bezier = new goog.math.Bezier(0, 0, 0, 0, 0.58, 1, 1, 1);
assertRoughlyEquals(0.308366667, bezier.solveYValueFromXValue(0.2), eps);
assertRoughlyEquals(0.785139061, bezier.solveYValueFromXValue(0.6), eps);
assertRoughlyEquals(0.982973389, bezier.solveYValueFromXValue(0.9), eps);
}
@@ -0,0 +1,389 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing a numeric box.
*/
goog.provide('goog.math.Box');
goog.require('goog.math.Coordinate');
/**
* Class for representing a box. A box is specified as a top, right, bottom,
* and left. A box is useful for representing margins and padding.
*
* This class assumes 'screen coordinates': larger Y coordinates are further
* from the top of the screen.
*
* @param {number} top Top.
* @param {number} right Right.
* @param {number} bottom Bottom.
* @param {number} left Left.
* @struct
* @constructor
*/
goog.math.Box = function(top, right, bottom, left) {
/**
* Top
* @type {number}
*/
this.top = top;
/**
* Right
* @type {number}
*/
this.right = right;
/**
* Bottom
* @type {number}
*/
this.bottom = bottom;
/**
* Left
* @type {number}
*/
this.left = left;
};
/**
* Creates a Box by bounding a collection of goog.math.Coordinate objects
* @param {...goog.math.Coordinate} var_args Coordinates to be included inside
* the box.
* @return {!goog.math.Box} A Box containing all the specified Coordinates.
*/
goog.math.Box.boundingBox = function(var_args) {
var box = new goog.math.Box(arguments[0].y, arguments[0].x,
arguments[0].y, arguments[0].x);
for (var i = 1; i < arguments.length; i++) {
var coord = arguments[i];
box.top = Math.min(box.top, coord.y);
box.right = Math.max(box.right, coord.x);
box.bottom = Math.max(box.bottom, coord.y);
box.left = Math.min(box.left, coord.x);
}
return box;
};
/**
* @return {number} width The width of this Box.
*/
goog.math.Box.prototype.getWidth = function() {
return this.right - this.left;
};
/**
* @return {number} height The height of this Box.
*/
goog.math.Box.prototype.getHeight = function() {
return this.bottom - this.top;
};
/**
* Creates a copy of the box with the same dimensions.
* @return {!goog.math.Box} A clone of this Box.
*/
goog.math.Box.prototype.clone = function() {
return new goog.math.Box(this.top, this.right, this.bottom, this.left);
};
if (goog.DEBUG) {
/**
* Returns a nice string representing the box.
* @return {string} In the form (50t, 73r, 24b, 13l).
* @override
*/
goog.math.Box.prototype.toString = function() {
return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' +
this.left + 'l)';
};
}
/**
* Returns whether the box contains a coordinate or another box.
*
* @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
* @return {boolean} Whether the box contains the coordinate or other box.
*/
goog.math.Box.prototype.contains = function(other) {
return goog.math.Box.contains(this, other);
};
/**
* Expands box with the given margins.
*
* @param {number|goog.math.Box} top Top margin or box with all margins.
* @param {number=} opt_right Right margin.
* @param {number=} opt_bottom Bottom margin.
* @param {number=} opt_left Left margin.
* @return {!goog.math.Box} A reference to this Box.
*/
goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom,
opt_left) {
if (goog.isObject(top)) {
this.top -= top.top;
this.right += top.right;
this.bottom += top.bottom;
this.left -= top.left;
} else {
this.top -= top;
this.right += opt_right;
this.bottom += opt_bottom;
this.left -= opt_left;
}
return this;
};
/**
* Expand this box to include another box.
* NOTE(user): This is used in code that needs to be very fast, please don't
* add functionality to this function at the expense of speed (variable
* arguments, accepting multiple argument types, etc).
* @param {goog.math.Box} box The box to include in this one.
*/
goog.math.Box.prototype.expandToInclude = function(box) {
this.left = Math.min(this.left, box.left);
this.top = Math.min(this.top, box.top);
this.right = Math.max(this.right, box.right);
this.bottom = Math.max(this.bottom, box.bottom);
};
/**
* Compares boxes for equality.
* @param {goog.math.Box} a A Box.
* @param {goog.math.Box} b A Box.
* @return {boolean} True iff the boxes are equal, or if both are null.
*/
goog.math.Box.equals = function(a, b) {
if (a == b) {
return true;
}
if (!a || !b) {
return false;
}
return a.top == b.top && a.right == b.right &&
a.bottom == b.bottom && a.left == b.left;
};
/**
* Returns whether a box contains a coordinate or another box.
*
* @param {goog.math.Box} box A Box.
* @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
* @return {boolean} Whether the box contains the coordinate or other box.
*/
goog.math.Box.contains = function(box, other) {
if (!box || !other) {
return false;
}
if (other instanceof goog.math.Box) {
return other.left >= box.left && other.right <= box.right &&
other.top >= box.top && other.bottom <= box.bottom;
}
// other is a Coordinate.
return other.x >= box.left && other.x <= box.right &&
other.y >= box.top && other.y <= box.bottom;
};
/**
* Returns the relative x position of a coordinate compared to a box. Returns
* zero if the coordinate is inside the box.
*
* @param {goog.math.Box} box A Box.
* @param {goog.math.Coordinate} coord A Coordinate.
* @return {number} The x position of {@code coord} relative to the nearest
* side of {@code box}, or zero if {@code coord} is inside {@code box}.
*/
goog.math.Box.relativePositionX = function(box, coord) {
if (coord.x < box.left) {
return coord.x - box.left;
} else if (coord.x > box.right) {
return coord.x - box.right;
}
return 0;
};
/**
* Returns the relative y position of a coordinate compared to a box. Returns
* zero if the coordinate is inside the box.
*
* @param {goog.math.Box} box A Box.
* @param {goog.math.Coordinate} coord A Coordinate.
* @return {number} The y position of {@code coord} relative to the nearest
* side of {@code box}, or zero if {@code coord} is inside {@code box}.
*/
goog.math.Box.relativePositionY = function(box, coord) {
if (coord.y < box.top) {
return coord.y - box.top;
} else if (coord.y > box.bottom) {
return coord.y - box.bottom;
}
return 0;
};
/**
* Returns the distance between a coordinate and the nearest corner/side of a
* box. Returns zero if the coordinate is inside the box.
*
* @param {goog.math.Box} box A Box.
* @param {goog.math.Coordinate} coord A Coordinate.
* @return {number} The distance between {@code coord} and the nearest
* corner/side of {@code box}, or zero if {@code coord} is inside
* {@code box}.
*/
goog.math.Box.distance = function(box, coord) {
var x = goog.math.Box.relativePositionX(box, coord);
var y = goog.math.Box.relativePositionY(box, coord);
return Math.sqrt(x * x + y * y);
};
/**
* Returns whether two boxes intersect.
*
* @param {goog.math.Box} a A Box.
* @param {goog.math.Box} b A second Box.
* @return {boolean} Whether the boxes intersect.
*/
goog.math.Box.intersects = function(a, b) {
return (a.left <= b.right && b.left <= a.right &&
a.top <= b.bottom && b.top <= a.bottom);
};
/**
* Returns whether two boxes would intersect with additional padding.
*
* @param {goog.math.Box} a A Box.
* @param {goog.math.Box} b A second Box.
* @param {number} padding The additional padding.
* @return {boolean} Whether the boxes intersect.
*/
goog.math.Box.intersectsWithPadding = function(a, b, padding) {
return (a.left <= b.right + padding && b.left <= a.right + padding &&
a.top <= b.bottom + padding && b.top <= a.bottom + padding);
};
/**
* Rounds the fields to the next larger integer values.
*
* @return {!goog.math.Box} This box with ceil'd fields.
*/
goog.math.Box.prototype.ceil = function() {
this.top = Math.ceil(this.top);
this.right = Math.ceil(this.right);
this.bottom = Math.ceil(this.bottom);
this.left = Math.ceil(this.left);
return this;
};
/**
* Rounds the fields to the next smaller integer values.
*
* @return {!goog.math.Box} This box with floored fields.
*/
goog.math.Box.prototype.floor = function() {
this.top = Math.floor(this.top);
this.right = Math.floor(this.right);
this.bottom = Math.floor(this.bottom);
this.left = Math.floor(this.left);
return this;
};
/**
* Rounds the fields to nearest integer values.
*
* @return {!goog.math.Box} This box with rounded fields.
*/
goog.math.Box.prototype.round = function() {
this.top = Math.round(this.top);
this.right = Math.round(this.right);
this.bottom = Math.round(this.bottom);
this.left = Math.round(this.left);
return this;
};
/**
* Translates this box by the given offsets. If a {@code goog.math.Coordinate}
* is given, then the left and right values are translated by the coordinate's
* x value and the top and bottom values are translated by the coordinate's y
* value. Otherwise, {@code tx} and {@code opt_ty} are used to translate the x
* and y dimension values.
*
* @param {number|goog.math.Coordinate} tx The value to translate the x
* dimension values by or the the coordinate to translate this box by.
* @param {number=} opt_ty The value to translate y dimension values by.
* @return {!goog.math.Box} This box after translating.
*/
goog.math.Box.prototype.translate = function(tx, opt_ty) {
if (tx instanceof goog.math.Coordinate) {
this.left += tx.x;
this.right += tx.x;
this.top += tx.y;
this.bottom += tx.y;
} else {
this.left += tx;
this.right += tx;
if (goog.isNumber(opt_ty)) {
this.top += opt_ty;
this.bottom += opt_ty;
}
}
return this;
};
/**
* Scales this coordinate by the given scale factors. The x and y dimension
* values are scaled by {@code sx} and {@code opt_sy} respectively.
* If {@code opt_sy} is not given, then {@code sx} is used for both x and y.
*
* @param {number} sx The scale factor to use for the x dimension.
* @param {number=} opt_sy The scale factor to use for the y dimension.
* @return {!goog.math.Box} This box after scaling.
*/
goog.math.Box.prototype.scale = function(sx, opt_sy) {
var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
this.left *= sx;
this.right *= sx;
this.top *= sy;
this.bottom *= sy;
return this;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Box
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.BoxTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,321 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.BoxTest');
goog.setTestOnly('goog.math.BoxTest');
goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.testing.jsunit');
function testBoxEquals() {
var a = new goog.math.Box(1, 2, 3, 4);
var b = new goog.math.Box(1, 2, 3, 4);
assertTrue(goog.math.Box.equals(a, a));
assertTrue(goog.math.Box.equals(a, b));
assertTrue(goog.math.Box.equals(b, a));
assertFalse('Box should not equal null.', goog.math.Box.equals(a, null));
assertFalse('Box should not equal null.', goog.math.Box.equals(null, a));
assertFalse(goog.math.Box.equals(a, new goog.math.Box(4, 2, 3, 4)));
assertFalse(goog.math.Box.equals(a, new goog.math.Box(1, 4, 3, 4)));
assertFalse(goog.math.Box.equals(a, new goog.math.Box(1, 2, 4, 4)));
assertFalse(goog.math.Box.equals(a, new goog.math.Box(1, 2, 3, 1)));
assertTrue('Null boxes should be equal.', goog.math.Box.equals(null, null));
}
function testBoxClone() {
var b = new goog.math.Box(0, 0, 0, 0);
assertTrue(goog.math.Box.equals(b, b.clone()));
b.left = 0;
b.top = 1;
b.right = 2;
b.bottom = 3;
assertTrue(goog.math.Box.equals(b, b.clone()));
}
function testBoxRelativePositionX() {
var box = new goog.math.Box(50, 100, 100, 50);
assertEquals(0,
goog.math.Box.relativePositionX(box, new goog.math.Coordinate(75, 0)));
assertEquals(0,
goog.math.Box.relativePositionX(box, new goog.math.Coordinate(75, 75)));
assertEquals(0,
goog.math.Box.relativePositionX(box, new goog.math.Coordinate(75, 105)));
assertEquals(-5,
goog.math.Box.relativePositionX(box, new goog.math.Coordinate(45, 75)));
assertEquals(5,
goog.math.Box.relativePositionX(box, new goog.math.Coordinate(105, 75)));
}
function testBoxRelativePositionY() {
var box = new goog.math.Box(50, 100, 100, 50);
assertEquals(0,
goog.math.Box.relativePositionY(box, new goog.math.Coordinate(0, 75)));
assertEquals(0,
goog.math.Box.relativePositionY(box, new goog.math.Coordinate(75, 75)));
assertEquals(0,
goog.math.Box.relativePositionY(box, new goog.math.Coordinate(105, 75)));
assertEquals(-5,
goog.math.Box.relativePositionY(box, new goog.math.Coordinate(75, 45)));
assertEquals(5,
goog.math.Box.relativePositionY(box, new goog.math.Coordinate(75, 105)));
}
function testBoxDistance() {
var box = new goog.math.Box(50, 100, 100, 50);
assertEquals(0,
goog.math.Box.distance(box, new goog.math.Coordinate(75, 75)));
assertEquals(25,
goog.math.Box.distance(box, new goog.math.Coordinate(75, 25)));
assertEquals(10,
goog.math.Box.distance(box, new goog.math.Coordinate(40, 80)));
assertEquals(5,
goog.math.Box.distance(box, new goog.math.Coordinate(46, 47)));
assertEquals(10,
goog.math.Box.distance(box, new goog.math.Coordinate(106, 108)));
}
function testBoxContains() {
var box = new goog.math.Box(50, 100, 100, 50);
assertTrue(goog.math.Box.contains(box, new goog.math.Coordinate(75, 75)));
assertTrue(goog.math.Box.contains(box, new goog.math.Coordinate(50, 100)));
assertTrue(goog.math.Box.contains(box, new goog.math.Coordinate(100, 99)));
assertFalse(goog.math.Box.contains(box, new goog.math.Coordinate(100, 101)));
assertFalse(goog.math.Box.contains(box, new goog.math.Coordinate(49, 50)));
assertFalse(goog.math.Box.contains(box, new goog.math.Coordinate(25, 25)));
}
function testBoxContainsBox() {
var box = new goog.math.Box(50, 100, 100, 50);
function assertContains(boxB) {
assertTrue(box + ' expected to contain ' + boxB,
goog.math.Box.contains(box, boxB));
}
function assertNotContains(boxB) {
assertFalse(box + ' expected to not contain ' + boxB,
goog.math.Box.contains(box, boxB));
}
assertContains(new goog.math.Box(60, 90, 90, 60));
assertNotContains(new goog.math.Box(1, 3, 4, 2));
assertNotContains(new goog.math.Box(30, 90, 60, 60));
assertNotContains(new goog.math.Box(60, 110, 60, 60));
assertNotContains(new goog.math.Box(60, 90, 110, 60));
assertNotContains(new goog.math.Box(60, 90, 60, 40));
}
function testBoxesIntersect() {
var box = new goog.math.Box(50, 100, 100, 50);
function assertIntersects(boxB) {
assertTrue(box + ' expected to intersect ' + boxB,
goog.math.Box.intersects(box, boxB));
}
function assertNotIntersects(boxB) {
assertFalse(box + ' expected to not intersect ' + boxB,
goog.math.Box.intersects(box, boxB));
}
assertIntersects(box);
assertIntersects(new goog.math.Box(20, 80, 80, 20));
assertIntersects(new goog.math.Box(50, 80, 100, 20));
assertIntersects(new goog.math.Box(80, 80, 120, 20));
assertIntersects(new goog.math.Box(20, 100, 80, 50));
assertIntersects(new goog.math.Box(80, 100, 120, 50));
assertIntersects(new goog.math.Box(20, 120, 80, 80));
assertIntersects(new goog.math.Box(50, 120, 100, 80));
assertIntersects(new goog.math.Box(80, 120, 120, 80));
assertIntersects(new goog.math.Box(20, 120, 120, 20));
assertIntersects(new goog.math.Box(70, 80, 80, 70));
assertNotIntersects(new goog.math.Box(10, 30, 30, 10));
assertNotIntersects(new goog.math.Box(10, 70, 30, 30));
assertNotIntersects(new goog.math.Box(10, 100, 30, 50));
assertNotIntersects(new goog.math.Box(10, 120, 30, 80));
assertNotIntersects(new goog.math.Box(10, 140, 30, 120));
assertNotIntersects(new goog.math.Box(30, 30, 70, 10));
assertNotIntersects(new goog.math.Box(30, 140, 70, 120));
assertNotIntersects(new goog.math.Box(50, 30, 100, 10));
assertNotIntersects(new goog.math.Box(50, 140, 100, 120));
assertNotIntersects(new goog.math.Box(80, 30, 120, 10));
assertNotIntersects(new goog.math.Box(80, 140, 120, 120));
assertNotIntersects(new goog.math.Box(120, 30, 140, 10));
assertNotIntersects(new goog.math.Box(120, 70, 140, 30));
assertNotIntersects(new goog.math.Box(120, 100, 140, 50));
assertNotIntersects(new goog.math.Box(120, 120, 140, 80));
assertNotIntersects(new goog.math.Box(120, 140, 140, 120));
}
function testBoxesIntersectWithPadding() {
var box = new goog.math.Box(50, 100, 100, 50);
function assertIntersects(boxB, padding) {
assertTrue(box + ' expected to intersect ' + boxB + ' with padding ' +
padding, goog.math.Box.intersectsWithPadding(box, boxB, padding));
}
function assertNotIntersects(boxB, padding) {
assertFalse(box + ' expected to not intersect ' + boxB + ' with padding ' +
padding, goog.math.Box.intersectsWithPadding(box, boxB, padding));
}
assertIntersects(box, 10);
assertIntersects(new goog.math.Box(20, 80, 80, 20), 10);
assertIntersects(new goog.math.Box(50, 80, 100, 20), 10);
assertIntersects(new goog.math.Box(80, 80, 120, 20), 10);
assertIntersects(new goog.math.Box(20, 100, 80, 50), 10);
assertIntersects(new goog.math.Box(80, 100, 120, 50), 10);
assertIntersects(new goog.math.Box(20, 120, 80, 80), 10);
assertIntersects(new goog.math.Box(50, 120, 100, 80), 10);
assertIntersects(new goog.math.Box(80, 120, 120, 80), 10);
assertIntersects(new goog.math.Box(20, 120, 120, 20), 10);
assertIntersects(new goog.math.Box(70, 80, 80, 70), 10);
assertIntersects(new goog.math.Box(10, 30, 30, 10), 20);
assertIntersects(new goog.math.Box(10, 70, 30, 30), 20);
assertIntersects(new goog.math.Box(10, 100, 30, 50), 20);
assertIntersects(new goog.math.Box(10, 120, 30, 80), 20);
assertIntersects(new goog.math.Box(10, 140, 30, 120), 20);
assertIntersects(new goog.math.Box(30, 30, 70, 10), 20);
assertIntersects(new goog.math.Box(30, 140, 70, 120), 20);
assertIntersects(new goog.math.Box(50, 30, 100, 10), 20);
assertIntersects(new goog.math.Box(50, 140, 100, 120), 20);
assertIntersects(new goog.math.Box(80, 30, 120, 10), 20);
assertIntersects(new goog.math.Box(80, 140, 120, 120), 20);
assertIntersects(new goog.math.Box(120, 30, 140, 10), 20);
assertIntersects(new goog.math.Box(120, 70, 140, 30), 20);
assertIntersects(new goog.math.Box(120, 100, 140, 50), 20);
assertIntersects(new goog.math.Box(120, 120, 140, 80), 20);
assertIntersects(new goog.math.Box(120, 140, 140, 120), 20);
assertNotIntersects(new goog.math.Box(10, 30, 30, 10), 10);
assertNotIntersects(new goog.math.Box(10, 70, 30, 30), 10);
assertNotIntersects(new goog.math.Box(10, 100, 30, 50), 10);
assertNotIntersects(new goog.math.Box(10, 120, 30, 80), 10);
assertNotIntersects(new goog.math.Box(10, 140, 30, 120), 10);
assertNotIntersects(new goog.math.Box(30, 30, 70, 10), 10);
assertNotIntersects(new goog.math.Box(30, 140, 70, 120), 10);
assertNotIntersects(new goog.math.Box(50, 30, 100, 10), 10);
assertNotIntersects(new goog.math.Box(50, 140, 100, 120), 10);
assertNotIntersects(new goog.math.Box(80, 30, 120, 10), 10);
assertNotIntersects(new goog.math.Box(80, 140, 120, 120), 10);
assertNotIntersects(new goog.math.Box(120, 30, 140, 10), 10);
assertNotIntersects(new goog.math.Box(120, 70, 140, 30), 10);
assertNotIntersects(new goog.math.Box(120, 100, 140, 50), 10);
assertNotIntersects(new goog.math.Box(120, 120, 140, 80), 10);
assertNotIntersects(new goog.math.Box(120, 140, 140, 120), 10);
}
function testExpandToInclude() {
var box = new goog.math.Box(10, 50, 50, 10);
box.expandToInclude(new goog.math.Box(60, 70, 70, 60));
assertEquals(10, box.left);
assertEquals(10, box.top);
assertEquals(70, box.right);
assertEquals(70, box.bottom);
box.expandToInclude(new goog.math.Box(30, 40, 40, 30));
assertEquals(10, box.left);
assertEquals(10, box.top);
assertEquals(70, box.right);
assertEquals(70, box.bottom);
box.expandToInclude(new goog.math.Box(0, 100, 100, 0));
assertEquals(0, box.left);
assertEquals(0, box.top);
assertEquals(100, box.right);
assertEquals(100, box.bottom);
}
function testGetWidth() {
var box = new goog.math.Box(10, 50, 30, 25);
assertEquals(25, box.getWidth());
}
function testGetHeight() {
var box = new goog.math.Box(10, 50, 30, 25);
assertEquals(20, box.getHeight());
}
function testBoundingBox() {
assertObjectEquals(
new goog.math.Box(1, 10, 11, 0),
goog.math.Box.boundingBox(
new goog.math.Coordinate(5, 5),
new goog.math.Coordinate(5, 11),
new goog.math.Coordinate(0, 5),
new goog.math.Coordinate(5, 1),
new goog.math.Coordinate(10, 5)));
}
function testBoxCeil() {
var box = new goog.math.Box(11.4, 26.6, 17.8, 9.2);
assertEquals('The function should return the target instance',
box, box.ceil());
assertObjectEquals(new goog.math.Box(12, 27, 18, 10), box);
}
function testBoxFloor() {
var box = new goog.math.Box(11.4, 26.6, 17.8, 9.2);
assertEquals('The function should return the target instance',
box, box.floor());
assertObjectEquals(new goog.math.Box(11, 26, 17, 9), box);
}
function testBoxRound() {
var box = new goog.math.Box(11.4, 26.6, 17.8, 9.2);
assertEquals('The function should return the target instance',
box, box.round());
assertObjectEquals(new goog.math.Box(11, 27, 18, 9), box);
}
function testBoxTranslateCoordinate() {
var box = new goog.math.Box(10, 30, 20, 5);
var c = new goog.math.Coordinate(10, 5);
assertEquals('The function should return the target instance',
box, box.translate(c));
assertObjectEquals(new goog.math.Box(15, 40, 25, 15), box);
}
function testBoxTranslateXY() {
var box = new goog.math.Box(10, 30, 20, 5);
assertEquals('The function should return the target instance',
box, box.translate(5, 2));
assertObjectEquals(new goog.math.Box(12, 35, 22, 10), box);
}
function testBoxTranslateX() {
var box = new goog.math.Box(10, 30, 20, 5);
assertEquals('The function should return the target instance',
box, box.translate(3));
assertObjectEquals(new goog.math.Box(10, 33, 20, 8), box);
}
function testBoxScaleXY() {
var box = new goog.math.Box(10, 20, 30, 5);
assertEquals('The function should return the target instance',
box, box.scale(2, 3));
assertObjectEquals(new goog.math.Box(30, 40, 90, 10), box);
}
function testBoxScaleFactor() {
var box = new goog.math.Box(10, 20, 30, 5);
assertEquals('The function should return the target instance',
box, box.scale(2));
assertObjectEquals(new goog.math.Box(20, 40, 60, 10), box);
}
@@ -0,0 +1,268 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing two-dimensional positions.
*/
goog.provide('goog.math.Coordinate');
goog.require('goog.math');
/**
* Class for representing coordinates and positions.
* @param {number=} opt_x Left, defaults to 0.
* @param {number=} opt_y Top, defaults to 0.
* @struct
* @constructor
*/
goog.math.Coordinate = function(opt_x, opt_y) {
/**
* X-value
* @type {number}
*/
this.x = goog.isDef(opt_x) ? opt_x : 0;
/**
* Y-value
* @type {number}
*/
this.y = goog.isDef(opt_y) ? opt_y : 0;
};
/**
* Returns a new copy of the coordinate.
* @return {!goog.math.Coordinate} A clone of this coordinate.
*/
goog.math.Coordinate.prototype.clone = function() {
return new goog.math.Coordinate(this.x, this.y);
};
if (goog.DEBUG) {
/**
* Returns a nice string representing the coordinate.
* @return {string} In the form (50, 73).
* @override
*/
goog.math.Coordinate.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ')';
};
}
/**
* Compares coordinates for equality.
* @param {goog.math.Coordinate} a A Coordinate.
* @param {goog.math.Coordinate} b A Coordinate.
* @return {boolean} True iff the coordinates are equal, or if both are null.
*/
goog.math.Coordinate.equals = function(a, b) {
if (a == b) {
return true;
}
if (!a || !b) {
return false;
}
return a.x == b.x && a.y == b.y;
};
/**
* Returns the distance between two coordinates.
* @param {!goog.math.Coordinate} a A Coordinate.
* @param {!goog.math.Coordinate} b A Coordinate.
* @return {number} The distance between {@code a} and {@code b}.
*/
goog.math.Coordinate.distance = function(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
};
/**
* Returns the magnitude of a coordinate.
* @param {!goog.math.Coordinate} a A Coordinate.
* @return {number} The distance between the origin and {@code a}.
*/
goog.math.Coordinate.magnitude = function(a) {
return Math.sqrt(a.x * a.x + a.y * a.y);
};
/**
* Returns the angle from the origin to a coordinate.
* @param {!goog.math.Coordinate} a A Coordinate.
* @return {number} The angle, in degrees, clockwise from the positive X
* axis to {@code a}.
*/
goog.math.Coordinate.azimuth = function(a) {
return goog.math.angle(0, 0, a.x, a.y);
};
/**
* Returns the squared distance between two coordinates. Squared distances can
* be used for comparisons when the actual value is not required.
*
* Performance note: eliminating the square root is an optimization often used
* in lower-level languages, but the speed difference is not nearly as
* pronounced in JavaScript (only a few percent.)
*
* @param {!goog.math.Coordinate} a A Coordinate.
* @param {!goog.math.Coordinate} b A Coordinate.
* @return {number} The squared distance between {@code a} and {@code b}.
*/
goog.math.Coordinate.squaredDistance = function(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return dx * dx + dy * dy;
};
/**
* Returns the difference between two coordinates as a new
* goog.math.Coordinate.
* @param {!goog.math.Coordinate} a A Coordinate.
* @param {!goog.math.Coordinate} b A Coordinate.
* @return {!goog.math.Coordinate} A Coordinate representing the difference
* between {@code a} and {@code b}.
*/
goog.math.Coordinate.difference = function(a, b) {
return new goog.math.Coordinate(a.x - b.x, a.y - b.y);
};
/**
* Returns the sum of two coordinates as a new goog.math.Coordinate.
* @param {!goog.math.Coordinate} a A Coordinate.
* @param {!goog.math.Coordinate} b A Coordinate.
* @return {!goog.math.Coordinate} A Coordinate representing the sum of the two
* coordinates.
*/
goog.math.Coordinate.sum = function(a, b) {
return new goog.math.Coordinate(a.x + b.x, a.y + b.y);
};
/**
* Rounds the x and y fields to the next larger integer values.
* @return {!goog.math.Coordinate} This coordinate with ceil'd fields.
*/
goog.math.Coordinate.prototype.ceil = function() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
};
/**
* Rounds the x and y fields to the next smaller integer values.
* @return {!goog.math.Coordinate} This coordinate with floored fields.
*/
goog.math.Coordinate.prototype.floor = function() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
};
/**
* Rounds the x and y fields to the nearest integer values.
* @return {!goog.math.Coordinate} This coordinate with rounded fields.
*/
goog.math.Coordinate.prototype.round = function() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
};
/**
* Translates this box by the given offsets. If a {@code goog.math.Coordinate}
* is given, then the x and y values are translated by the coordinate's x and y.
* Otherwise, x and y are translated by {@code tx} and {@code opt_ty}
* respectively.
* @param {number|goog.math.Coordinate} tx The value to translate x by or the
* the coordinate to translate this coordinate by.
* @param {number=} opt_ty The value to translate y by.
* @return {!goog.math.Coordinate} This coordinate after translating.
*/
goog.math.Coordinate.prototype.translate = function(tx, opt_ty) {
if (tx instanceof goog.math.Coordinate) {
this.x += tx.x;
this.y += tx.y;
} else {
this.x += tx;
if (goog.isNumber(opt_ty)) {
this.y += opt_ty;
}
}
return this;
};
/**
* Scales this coordinate by the given scale factors. The x and y values are
* scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy}
* is not given, then {@code sx} is used for both x and y.
* @param {number} sx The scale factor to use for the x dimension.
* @param {number=} opt_sy The scale factor to use for the y dimension.
* @return {!goog.math.Coordinate} This coordinate after scaling.
*/
goog.math.Coordinate.prototype.scale = function(sx, opt_sy) {
var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
this.x *= sx;
this.y *= sy;
return this;
};
/**
* Rotates this coordinate clockwise about the origin (or, optionally, the given
* center) by the given angle, in radians.
* @param {number} radians The angle by which to rotate this coordinate
* clockwise about the given center, in radians.
* @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
* to (0, 0) if not given.
*/
goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) {
var center = opt_center || new goog.math.Coordinate(0, 0);
var x = this.x;
var y = this.y;
var cos = Math.cos(radians);
var sin = Math.sin(radians);
this.x = (x - center.x) * cos - (y - center.y) * sin + center.x;
this.y = (x - center.x) * sin + (y - center.y) * cos + center.y;
};
/**
* Rotates this coordinate clockwise about the origin (or, optionally, the given
* center) by the given angle, in degrees.
* @param {number} degrees The angle by which to rotate this coordinate
* clockwise about the given center, in degrees.
* @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
* to (0, 0) if not given.
*/
goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) {
this.rotateRadians(goog.math.toRadians(degrees), opt_center);
};
@@ -0,0 +1,170 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing three-dimensional points.
*
* Based heavily on coordinate.js by:
*/
goog.provide('goog.math.Coordinate3');
/**
* Class for representing coordinates and positions in 3 dimensions.
*
* @param {number=} opt_x X coordinate, defaults to 0.
* @param {number=} opt_y Y coordinate, defaults to 0.
* @param {number=} opt_z Z coordinate, defaults to 0.
* @struct
* @constructor
*/
goog.math.Coordinate3 = function(opt_x, opt_y, opt_z) {
/**
* X-value
* @type {number}
*/
this.x = goog.isDef(opt_x) ? opt_x : 0;
/**
* Y-value
* @type {number}
*/
this.y = goog.isDef(opt_y) ? opt_y : 0;
/**
* Z-value
* @type {number}
*/
this.z = goog.isDef(opt_z) ? opt_z : 0;
};
/**
* Returns a new copy of the coordinate.
*
* @return {!goog.math.Coordinate3} A clone of this coordinate.
*/
goog.math.Coordinate3.prototype.clone = function() {
return new goog.math.Coordinate3(this.x, this.y, this.z);
};
if (goog.DEBUG) {
/**
* Returns a nice string representing the coordinate.
*
* @return {string} In the form (50, 73, 31).
* @override
*/
goog.math.Coordinate3.prototype.toString = function() {
return '(' + this.x + ', ' + this.y + ', ' + this.z + ')';
};
}
/**
* Compares coordinates for equality.
*
* @param {goog.math.Coordinate3} a A Coordinate3.
* @param {goog.math.Coordinate3} b A Coordinate3.
* @return {boolean} True iff the coordinates are equal, or if both are null.
*/
goog.math.Coordinate3.equals = function(a, b) {
if (a == b) {
return true;
}
if (!a || !b) {
return false;
}
return a.x == b.x && a.y == b.y && a.z == b.z;
};
/**
* Returns the distance between two coordinates.
*
* @param {goog.math.Coordinate3} a A Coordinate3.
* @param {goog.math.Coordinate3} b A Coordinate3.
* @return {number} The distance between {@code a} and {@code b}.
*/
goog.math.Coordinate3.distance = function(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dz = a.z - b.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
};
/**
* Returns the squared distance between two coordinates. Squared distances can
* be used for comparisons when the actual value is not required.
*
* Performance note: eliminating the square root is an optimization often used
* in lower-level languages, but the speed difference is not nearly as
* pronounced in JavaScript (only a few percent.)
*
* @param {goog.math.Coordinate3} a A Coordinate3.
* @param {goog.math.Coordinate3} b A Coordinate3.
* @return {number} The squared distance between {@code a} and {@code b}.
*/
goog.math.Coordinate3.squaredDistance = function(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dz = a.z - b.z;
return dx * dx + dy * dy + dz * dz;
};
/**
* Returns the difference between two coordinates as a new
* goog.math.Coordinate3.
*
* @param {goog.math.Coordinate3} a A Coordinate3.
* @param {goog.math.Coordinate3} b A Coordinate3.
* @return {!goog.math.Coordinate3} A Coordinate3 representing the difference
* between {@code a} and {@code b}.
*/
goog.math.Coordinate3.difference = function(a, b) {
return new goog.math.Coordinate3(a.x - b.x, a.y - b.y, a.z - b.z);
};
/**
* Returns the contents of this coordinate as a 3 value Array.
*
* @return {!Array<number>} A new array.
*/
goog.math.Coordinate3.prototype.toArray = function() {
return [this.x, this.y, this.z];
};
/**
* Converts a three element array into a Coordinate3 object. If the value
* passed in is not an array, not array-like, or not of the right length, an
* error is thrown.
*
* @param {Array<number>} a Array of numbers to become a coordinate.
* @return {!goog.math.Coordinate3} A new coordinate from the array values.
* @throws {Error} When the oject passed in is not valid.
*/
goog.math.Coordinate3.fromArray = function(a) {
if (a.length <= 3) {
return new goog.math.Coordinate3(a[0], a[1], a[2]);
}
throw Error('Conversion from an array requires an array of length 3');
};
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Coordinate3 Unit Tests
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Coordinate3
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.Coordinate3Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,196 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.Coordinate3Test');
goog.setTestOnly('goog.math.Coordinate3Test');
goog.require('goog.math.Coordinate3');
goog.require('goog.testing.jsunit');
function assertCoordinate3Equals(a, b) {
assertTrue(b + ' should be equal to ' + a,
goog.math.Coordinate3.equals(a, b));
}
function testCoordinate3MissingXYZ() {
var noXYZ = new goog.math.Coordinate3();
assertEquals(0, noXYZ.x);
assertEquals(0, noXYZ.y);
assertEquals(0, noXYZ.z);
assertCoordinate3Equals(noXYZ, new goog.math.Coordinate3());
}
function testCoordinate3MissingYZ() {
var noYZ = new goog.math.Coordinate3(10);
assertEquals(10, noYZ.x);
assertEquals(0, noYZ.y);
assertEquals(0, noYZ.z);
assertCoordinate3Equals(noYZ, new goog.math.Coordinate3(10));
}
function testCoordinate3MissingZ() {
var noZ = new goog.math.Coordinate3(10, 20);
assertEquals(10, noZ.x);
assertEquals(20, noZ.y);
assertEquals(0, noZ.z);
assertCoordinate3Equals(noZ, new goog.math.Coordinate3(10, 20));
}
function testCoordinate3IntegerValues() {
var intCoord = new goog.math.Coordinate3(10, 20, -19);
assertEquals(10, intCoord.x);
assertEquals(20, intCoord.y);
assertEquals(-19, intCoord.z);
assertCoordinate3Equals(intCoord, new goog.math.Coordinate3(10, 20, -19));
}
function testCoordinate3FloatValues() {
var floatCoord = new goog.math.Coordinate3(10.5, 20.897, -71.385);
assertEquals(10.5, floatCoord.x);
assertEquals(20.897, floatCoord.y);
assertEquals(-71.385, floatCoord.z);
assertCoordinate3Equals(floatCoord,
new goog.math.Coordinate3(10.5, 20.897, -71.385));
}
function testCoordinate3OneNonNumericValue() {
var dim5 = new goog.math.Coordinate3('ten', 1000, 85);
assertTrue(isNaN(dim5.x));
assertEquals(1000, dim5.y);
assertEquals(85, dim5.z);
}
function testCoordinate3AllNonNumericValues() {
var nonNumeric = new goog.math.Coordinate3('ten',
{woop: 'test'},
Math.sqrt(-1));
assertTrue(isNaN(nonNumeric.x));
assertTrue(isNaN(nonNumeric.y));
assertTrue(isNaN(nonNumeric.z));
}
function testCoordinate3Origin() {
var origin = new goog.math.Coordinate3(0, 0, 0);
assertEquals(0, origin.x);
assertEquals(0, origin.y);
assertEquals(0, origin.z);
assertCoordinate3Equals(origin, new goog.math.Coordinate3(0, 0, 0));
}
function testCoordinate3Clone() {
var c = new goog.math.Coordinate3();
assertCoordinate3Equals(c, c.clone());
c.x = -12;
c.y = 13;
c.z = 5;
assertCoordinate3Equals(c, c.clone());
}
function testToString() {
assertEquals('(0, 0, 0)', new
goog.math.Coordinate3().toString());
assertEquals('(1, 0, 0)', new
goog.math.Coordinate3(1).toString());
assertEquals('(1, 2, 0)', new
goog.math.Coordinate3(1, 2).toString());
assertEquals('(0, 0, 0)', new goog.math.Coordinate3(0, 0, 0).toString());
assertEquals('(1, 2, 3)', new goog.math.Coordinate3(1, 2, 3).toString());
assertEquals('(-4, 5, -3)', new goog.math.Coordinate3(-4, 5, -3).toString());
assertEquals('(11.25, -71.935, 2.8)',
new goog.math.Coordinate3(11.25, -71.935, 2.8).toString());
}
function testEquals() {
var a = new goog.math.Coordinate3(3, 4, 5);
var b = new goog.math.Coordinate3(3, 4, 5);
var c = new goog.math.Coordinate3(-3, 4, -5);
assertTrue(goog.math.Coordinate3.equals(null, null));
assertFalse(goog.math.Coordinate3.equals(a, null));
assertTrue(goog.math.Coordinate3.equals(a, a));
assertTrue(goog.math.Coordinate3.equals(a, b));
assertFalse(goog.math.Coordinate3.equals(a, c));
}
function testCoordinate3Distance() {
var a = new goog.math.Coordinate3(-2, -3, 1);
var b = new goog.math.Coordinate3(2, 0, 1);
assertEquals(5, goog.math.Coordinate3.distance(a, b));
}
function testCoordinate3SquaredDistance() {
var a = new goog.math.Coordinate3(7, 11, 1);
var b = new goog.math.Coordinate3(3, -1, 1);
assertEquals(160, goog.math.Coordinate3.squaredDistance(a, b));
}
function testCoordinate3Difference() {
var a = new goog.math.Coordinate3(7, 11, 1);
var b = new goog.math.Coordinate3(3, -1, 1);
assertCoordinate3Equals(goog.math.Coordinate3.difference(a, b),
new goog.math.Coordinate3(4, 12, 0));
}
function testToArray() {
var a = new goog.math.Coordinate3(7, 11, 1);
var b = a.toArray();
assertEquals(b.length, 3);
assertEquals(b[0], 7);
assertEquals(b[1], 11);
assertEquals(b[2], 1);
var c = new goog.math.Coordinate3('abc', 'def', 'xyz');
var result = c.toArray();
assertTrue(isNaN(result[0]));
assertTrue(isNaN(result[1]));
assertTrue(isNaN(result[2]));
}
function testFromArray() {
var a = [1, 2, 3];
var b = goog.math.Coordinate3.fromArray(a);
assertEquals('(1, 2, 3)', b.toString());
var c = [1, 2];
var d = goog.math.Coordinate3.fromArray(c);
assertEquals('(1, 2, 0)', d.toString());
var e = [1];
var f = goog.math.Coordinate3.fromArray(e);
assertEquals('(1, 0, 0)', f.toString());
var g = [];
var h = goog.math.Coordinate3.fromArray(g);
assertEquals('(0, 0, 0)', h.toString());
var tooLong = [1, 2, 3, 4, 5, 6];
assertThrows('Error should be thrown attempting to convert an invalid type.',
goog.partial(goog.math.Coordinate3.fromArray, tooLong));
}
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Coordinate
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.CoordinateTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,170 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.CoordinateTest');
goog.setTestOnly('goog.math.CoordinateTest');
goog.require('goog.math.Coordinate');
goog.require('goog.testing.jsunit');
function testCoordinate1() {
var dim1 = new goog.math.Coordinate();
assertEquals(0, dim1.x);
assertEquals(0, dim1.y);
assertEquals('(0, 0)', dim1.toString());
}
function testCoordinate2() {
var dim2 = new goog.math.Coordinate(10);
assertEquals(10, dim2.x);
assertEquals(0, dim2.y);
assertEquals('(10, 0)', dim2.toString());
}
function testCoordinate3() {
var dim3 = new goog.math.Coordinate(10, 20);
assertEquals(10, dim3.x);
assertEquals(20, dim3.y);
assertEquals('(10, 20)', dim3.toString());
}
function testCoordinate4() {
var dim4 = new goog.math.Coordinate(10.5, 20.897);
assertEquals(10.5, dim4.x);
assertEquals(20.897, dim4.y);
assertEquals('(10.5, 20.897)', dim4.toString());
}
function testCoordinate5() {
var dim5 = new goog.math.Coordinate(NaN, 1000);
assertTrue(isNaN(dim5.x));
assertEquals(1000, dim5.y);
assertEquals('(NaN, 1000)', dim5.toString());
}
function testCoordinateSquaredDistance() {
var a = new goog.math.Coordinate(7, 11);
var b = new goog.math.Coordinate(3, -1);
assertEquals(160, goog.math.Coordinate.squaredDistance(a, b));
}
function testCoordinateDistance() {
var a = new goog.math.Coordinate(-2, -3);
var b = new goog.math.Coordinate(2, 0);
assertEquals(5, goog.math.Coordinate.distance(a, b));
}
function testCoordinateMagnitude() {
var a = new goog.math.Coordinate(5, 5);
assertEquals(Math.sqrt(50), goog.math.Coordinate.magnitude(a));
}
function testCoordinateAzimuth() {
var a = new goog.math.Coordinate(5, 5);
assertEquals(45, goog.math.Coordinate.azimuth(a));
}
function testCoordinateClone() {
var c = new goog.math.Coordinate();
assertEquals(c.toString(), c.clone().toString());
c.x = -12;
c.y = 13;
assertEquals(c.toString(), c.clone().toString());
}
function testCoordinateDifference() {
assertObjectEquals(new goog.math.Coordinate(3, -40),
goog.math.Coordinate.difference(
new goog.math.Coordinate(5, 10),
new goog.math.Coordinate(2, 50)));
}
function testCoordinateSum() {
assertObjectEquals(new goog.math.Coordinate(7, 60),
goog.math.Coordinate.sum(
new goog.math.Coordinate(5, 10),
new goog.math.Coordinate(2, 50)));
}
function testCoordinateCeil() {
var c = new goog.math.Coordinate(5.2, 7.6);
assertObjectEquals(new goog.math.Coordinate(6, 8), c.ceil());
c = new goog.math.Coordinate(-1.2, -3.9);
assertObjectEquals(new goog.math.Coordinate(-1, -3), c.ceil());
}
function testCoordinateFloor() {
var c = new goog.math.Coordinate(5.2, 7.6);
assertObjectEquals(new goog.math.Coordinate(5, 7), c.floor());
c = new goog.math.Coordinate(-1.2, -3.9);
assertObjectEquals(new goog.math.Coordinate(-2, -4), c.floor());
}
function testCoordinateRound() {
var c = new goog.math.Coordinate(5.2, 7.6);
assertObjectEquals(new goog.math.Coordinate(5, 8), c.round());
c = new goog.math.Coordinate(-1.2, -3.9);
assertObjectEquals(new goog.math.Coordinate(-1, -4), c.round());
}
function testCoordinateTranslateCoordinate() {
var c = new goog.math.Coordinate(10, 20);
var t = new goog.math.Coordinate(5, 10);
// The translate function modifies the coordinate instead of
// returning a new one.
assertEquals(c, c.translate(t));
assertObjectEquals(new goog.math.Coordinate(15, 30), c);
}
function testCoordinateTranslateXY() {
var c = new goog.math.Coordinate(10, 20);
// The translate function modifies the coordinate instead of
// returning a new one.
assertEquals(c, c.translate(25, 5));
assertObjectEquals(new goog.math.Coordinate(35, 25), c);
}
function testCoordinateTranslateX() {
var c = new goog.math.Coordinate(10, 20);
// The translate function modifies the coordinate instead of
// returning a new one.
assertEquals(c, c.translate(5));
assertObjectEquals(new goog.math.Coordinate(15, 20), c);
}
function testCoordinateScaleXY() {
var c = new goog.math.Coordinate(10, 15);
// The scale function modifies the coordinate instead of returning a new one.
assertEquals(c, c.scale(2, 3));
assertObjectEquals(new goog.math.Coordinate(20, 45), c);
}
function testCoordinateScaleFactor() {
var c = new goog.math.Coordinate(10, 15);
// The scale function modifies the coordinate instead of returning a new one.
assertEquals(c, c.scale(2));
assertObjectEquals(new goog.math.Coordinate(20, 30), c);
}
function testCoordinateRotateRadians() {
var c = new goog.math.Coordinate(15, 75);
c.rotateRadians(Math.PI / 2, new goog.math.Coordinate(10, 70));
assertObjectEquals(new goog.math.Coordinate(5, 75), c);
}
function testCoordinateRotateDegrees() {
var c = new goog.math.Coordinate(15, 75);
c.rotateDegrees(90, new goog.math.Coordinate(10, 70));
assertObjectEquals(new goog.math.Coordinate(5, 75), c);
}
@@ -0,0 +1,104 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Utility class to manage the mathematics behind computing an
* exponential backoff model. Given an initial backoff value and a maximum
* backoff value, every call to backoff() will double the value until maximum
* backoff value is reached.
*
*/
goog.provide('goog.math.ExponentialBackoff');
goog.require('goog.asserts');
/**
* @struct
* @constructor
*
* @param {number} initialValue The initial backoff value.
* @param {number} maxValue The maximum backoff value.
*/
goog.math.ExponentialBackoff = function(initialValue, maxValue) {
goog.asserts.assert(initialValue > 0,
'Initial value must be greater than zero.');
goog.asserts.assert(maxValue >= initialValue,
'Max value should be at least as large as initial value.');
/**
* @type {number}
* @private
*/
this.initialValue_ = initialValue;
/**
* @type {number}
* @private
*/
this.maxValue_ = maxValue;
/**
* The current backoff value.
* @type {number}
* @private
*/
this.currValue_ = initialValue;
};
/**
* The number of backoffs that have happened.
* @type {number}
* @private
*/
goog.math.ExponentialBackoff.prototype.currCount_ = 0;
/**
* Resets the backoff value to its initial value.
*/
goog.math.ExponentialBackoff.prototype.reset = function() {
this.currValue_ = this.initialValue_;
this.currCount_ = 0;
};
/**
* @return {number} The current backoff value.
*/
goog.math.ExponentialBackoff.prototype.getValue = function() {
return this.currValue_;
};
/**
* @return {number} The number of times this class has backed off.
*/
goog.math.ExponentialBackoff.prototype.getBackoffCount = function() {
return this.currCount_;
};
/**
* Initiates a backoff.
*/
goog.math.ExponentialBackoff.prototype.backoff = function() {
this.currValue_ = Math.min(this.maxValue_, this.currValue_ * 2);
this.currCount_++;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.ExponentialBackoff
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.ExponentialBackoffTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,63 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.ExponentialBackoffTest');
goog.setTestOnly('goog.math.ExponentialBackoffTest');
goog.require('goog.math.ExponentialBackoff');
goog.require('goog.testing.jsunit');
var INITIAL_VALUE = 1;
var MAX_VALUE = 10;
function assertValueAndCount(value, count, backoff) {
assertEquals('Wrong value', value, backoff.getValue());
assertEquals('Wrong backoff count', count, backoff.getBackoffCount());
}
function createBackoff() {
return new goog.math.ExponentialBackoff(INITIAL_VALUE, MAX_VALUE);
}
function testInitialState() {
var backoff = createBackoff();
assertValueAndCount(INITIAL_VALUE, 0, backoff);
}
function testBackoff() {
var backoff = createBackoff();
backoff.backoff();
assertValueAndCount(2 /* value */, 1 /* count */, backoff);
backoff.backoff();
assertValueAndCount(4 /* value */, 2 /* count */, backoff);
backoff.backoff();
assertValueAndCount(8 /* value */, 3 /* count */, backoff);
backoff.backoff();
assertValueAndCount(MAX_VALUE, 4 /* count */, backoff);
backoff.backoff();
assertValueAndCount(MAX_VALUE, 5 /* count */, backoff);
}
function testReset() {
var backoff = createBackoff();
backoff.backoff();
backoff.reset();
assertValueAndCount(INITIAL_VALUE, 0 /* count */, backoff);
}
@@ -0,0 +1,739 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines an Integer class for representing (potentially)
* infinite length two's-complement integer values.
*
* For the specific case of 64-bit integers, use goog.math.Long, which is more
* efficient.
*
*/
goog.provide('goog.math.Integer');
/**
* Constructs a two's-complement integer an array containing bits of the
* integer in 32-bit (signed) pieces, given in little-endian order (i.e.,
* lowest-order bits in the first piece), and the sign of -1 or 0.
*
* See the from* functions below for other convenient ways of constructing
* Integers.
*
* The internal representation of an integer is an array of 32-bit signed
* pieces, along with a sign (0 or -1) that indicates the contents of all the
* other 32-bit pieces out to infinity. We use 32-bit pieces because these are
* the size of integers on which Javascript performs bit-operations. For
* operations like addition and multiplication, we split each number into 16-bit
* pieces, which can easily be multiplied within Javascript's floating-point
* representation without overflow or change in sign.
*
* @struct
* @constructor
* @param {Array<number>} bits Array containing the bits of the number.
* @param {number} sign The sign of the number: -1 for negative and 0 positive.
* @final
*/
goog.math.Integer = function(bits, sign) {
/**
* @type {!Array<number>}
* @private
*/
this.bits_ = [];
/**
* @type {number}
* @private
*/
this.sign_ = sign;
// Copy the 32-bit signed integer values passed in. We prune out those at the
// top that equal the sign since they are redundant.
var top = true;
for (var i = bits.length - 1; i >= 0; i--) {
var val = bits[i] | 0;
if (!top || val != sign) {
this.bits_[i] = val;
top = false;
}
}
};
// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the
// from* methods on which they depend.
/**
* A cache of the Integer representations of small integer values.
* @type {!Object}
* @private
*/
goog.math.Integer.IntCache_ = {};
/**
* Returns an Integer representing the given (32-bit) integer value.
* @param {number} value A 32-bit integer value.
* @return {!goog.math.Integer} The corresponding Integer value.
*/
goog.math.Integer.fromInt = function(value) {
if (-128 <= value && value < 128) {
var cachedObj = goog.math.Integer.IntCache_[value];
if (cachedObj) {
return cachedObj;
}
}
var obj = new goog.math.Integer([value | 0], value < 0 ? -1 : 0);
if (-128 <= value && value < 128) {
goog.math.Integer.IntCache_[value] = obj;
}
return obj;
};
/**
* Returns an Integer representing the given value, provided that it is a finite
* number. Otherwise, zero is returned.
* @param {number} value The value in question.
* @return {!goog.math.Integer} The corresponding Integer value.
*/
goog.math.Integer.fromNumber = function(value) {
if (isNaN(value) || !isFinite(value)) {
return goog.math.Integer.ZERO;
} else if (value < 0) {
return goog.math.Integer.fromNumber(-value).negate();
} else {
var bits = [];
var pow = 1;
for (var i = 0; value >= pow; i++) {
bits[i] = (value / pow) | 0;
pow *= goog.math.Integer.TWO_PWR_32_DBL_;
}
return new goog.math.Integer(bits, 0);
}
};
/**
* Returns a Integer representing the value that comes by concatenating the
* given entries, each is assumed to be 32 signed bits, given in little-endian
* order (lowest order bits in the lowest index), and sign-extending the highest
* order 32-bit value.
* @param {Array<number>} bits The bits of the number, in 32-bit signed pieces,
* in little-endian order.
* @return {!goog.math.Integer} The corresponding Integer value.
*/
goog.math.Integer.fromBits = function(bits) {
var high = bits[bits.length - 1];
return new goog.math.Integer(bits, high & (1 << 31) ? -1 : 0);
};
/**
* Returns an Integer representation of the given string, written using the
* given radix.
* @param {string} str The textual representation of the Integer.
* @param {number=} opt_radix The radix in which the text is written.
* @return {!goog.math.Integer} The corresponding Integer value.
*/
goog.math.Integer.fromString = function(str, opt_radix) {
if (str.length == 0) {
throw Error('number format error: empty string');
}
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (str.charAt(0) == '-') {
return goog.math.Integer.fromString(str.substring(1), radix).negate();
} else if (str.indexOf('-') >= 0) {
throw Error('number format error: interior "-" character');
}
// Do several (8) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = goog.math.Integer.fromNumber(Math.pow(radix, 8));
var result = goog.math.Integer.ZERO;
for (var i = 0; i < str.length; i += 8) {
var size = Math.min(8, str.length - i);
var value = parseInt(str.substring(i, i + size), radix);
if (size < 8) {
var power = goog.math.Integer.fromNumber(Math.pow(radix, size));
result = result.multiply(power).add(goog.math.Integer.fromNumber(value));
} else {
result = result.multiply(radixToPower);
result = result.add(goog.math.Integer.fromNumber(value));
}
}
return result;
};
/**
* A number used repeatedly in calculations. This must appear before the first
* call to the from* functions below.
* @type {number}
* @private
*/
goog.math.Integer.TWO_PWR_32_DBL_ = (1 << 16) * (1 << 16);
/** @type {!goog.math.Integer} */
goog.math.Integer.ZERO = goog.math.Integer.fromInt(0);
/** @type {!goog.math.Integer} */
goog.math.Integer.ONE = goog.math.Integer.fromInt(1);
/**
* @type {!goog.math.Integer}
* @private
*/
goog.math.Integer.TWO_PWR_24_ = goog.math.Integer.fromInt(1 << 24);
/**
* Returns the value, assuming it is a 32-bit integer.
* @return {number} The corresponding int value.
*/
goog.math.Integer.prototype.toInt = function() {
return this.bits_.length > 0 ? this.bits_[0] : this.sign_;
};
/** @return {number} The closest floating-point representation to this value. */
goog.math.Integer.prototype.toNumber = function() {
if (this.isNegative()) {
return -this.negate().toNumber();
} else {
var val = 0;
var pow = 1;
for (var i = 0; i < this.bits_.length; i++) {
val += this.getBitsUnsigned(i) * pow;
pow *= goog.math.Integer.TWO_PWR_32_DBL_;
}
return val;
}
};
/**
* @param {number=} opt_radix The radix in which the text should be written.
* @return {string} The textual representation of this value.
* @override
*/
goog.math.Integer.prototype.toString = function(opt_radix) {
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (this.isZero()) {
return '0';
} else if (this.isNegative()) {
return '-' + this.negate().toString(radix);
}
// Do several (6) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = goog.math.Integer.fromNumber(Math.pow(radix, 6));
var rem = this;
var result = '';
while (true) {
var remDiv = rem.divide(radixToPower);
var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt();
var digits = intval.toString(radix);
rem = remDiv;
if (rem.isZero()) {
return digits + result;
} else {
while (digits.length < 6) {
digits = '0' + digits;
}
result = '' + digits + result;
}
}
};
/**
* Returns the index-th 32-bit (signed) piece of the Integer according to
* little-endian order (i.e., index 0 contains the smallest bits).
* @param {number} index The index in question.
* @return {number} The requested 32-bits as a signed number.
*/
goog.math.Integer.prototype.getBits = function(index) {
if (index < 0) {
return 0; // Allowing this simplifies bit shifting operations below...
} else if (index < this.bits_.length) {
return this.bits_[index];
} else {
return this.sign_;
}
};
/**
* Returns the index-th 32-bit piece as an unsigned number.
* @param {number} index The index in question.
* @return {number} The requested 32-bits as an unsigned number.
*/
goog.math.Integer.prototype.getBitsUnsigned = function(index) {
var val = this.getBits(index);
return val >= 0 ? val : goog.math.Integer.TWO_PWR_32_DBL_ + val;
};
/** @return {number} The sign bit of this number, -1 or 0. */
goog.math.Integer.prototype.getSign = function() {
return this.sign_;
};
/** @return {boolean} Whether this value is zero. */
goog.math.Integer.prototype.isZero = function() {
if (this.sign_ != 0) {
return false;
}
for (var i = 0; i < this.bits_.length; i++) {
if (this.bits_[i] != 0) {
return false;
}
}
return true;
};
/** @return {boolean} Whether this value is negative. */
goog.math.Integer.prototype.isNegative = function() {
return this.sign_ == -1;
};
/** @return {boolean} Whether this value is odd. */
goog.math.Integer.prototype.isOdd = function() {
return (this.bits_.length == 0) && (this.sign_ == -1) ||
(this.bits_.length > 0) && ((this.bits_[0] & 1) != 0);
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer equals the other.
*/
goog.math.Integer.prototype.equals = function(other) {
if (this.sign_ != other.sign_) {
return false;
}
var len = Math.max(this.bits_.length, other.bits_.length);
for (var i = 0; i < len; i++) {
if (this.getBits(i) != other.getBits(i)) {
return false;
}
}
return true;
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer does not equal the other.
*/
goog.math.Integer.prototype.notEquals = function(other) {
return !this.equals(other);
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer is greater than the other.
*/
goog.math.Integer.prototype.greaterThan = function(other) {
return this.compare(other) > 0;
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer is greater than or equal to the other.
*/
goog.math.Integer.prototype.greaterThanOrEqual = function(other) {
return this.compare(other) >= 0;
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer is less than the other.
*/
goog.math.Integer.prototype.lessThan = function(other) {
return this.compare(other) < 0;
};
/**
* @param {goog.math.Integer} other Integer to compare against.
* @return {boolean} Whether this Integer is less than or equal to the other.
*/
goog.math.Integer.prototype.lessThanOrEqual = function(other) {
return this.compare(other) <= 0;
};
/**
* Compares this Integer with the given one.
* @param {goog.math.Integer} other Integer to compare against.
* @return {number} 0 if they are the same, 1 if the this is greater, and -1
* if the given one is greater.
*/
goog.math.Integer.prototype.compare = function(other) {
var diff = this.subtract(other);
if (diff.isNegative()) {
return -1;
} else if (diff.isZero()) {
return 0;
} else {
return +1;
}
};
/**
* Returns an integer with only the first numBits bits of this value, sign
* extended from the final bit.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Integer} The shorted integer value.
*/
goog.math.Integer.prototype.shorten = function(numBits) {
var arr_index = (numBits - 1) >> 5;
var bit_index = (numBits - 1) % 32;
var bits = [];
for (var i = 0; i < arr_index; i++) {
bits[i] = this.getBits(i);
}
var sigBits = bit_index == 31 ? 0xFFFFFFFF : (1 << (bit_index + 1)) - 1;
var val = this.getBits(arr_index) & sigBits;
if (val & (1 << bit_index)) {
val |= 0xFFFFFFFF - sigBits;
bits[arr_index] = val;
return new goog.math.Integer(bits, -1);
} else {
bits[arr_index] = val;
return new goog.math.Integer(bits, 0);
}
};
/** @return {!goog.math.Integer} The negation of this value. */
goog.math.Integer.prototype.negate = function() {
return this.not().add(goog.math.Integer.ONE);
};
/**
* Returns the sum of this and the given Integer.
* @param {goog.math.Integer} other The Integer to add to this.
* @return {!goog.math.Integer} The Integer result.
*/
goog.math.Integer.prototype.add = function(other) {
var len = Math.max(this.bits_.length, other.bits_.length);
var arr = [];
var carry = 0;
for (var i = 0; i <= len; i++) {
var a1 = this.getBits(i) >>> 16;
var a0 = this.getBits(i) & 0xFFFF;
var b1 = other.getBits(i) >>> 16;
var b0 = other.getBits(i) & 0xFFFF;
var c0 = carry + a0 + b0;
var c1 = (c0 >>> 16) + a1 + b1;
carry = c1 >>> 16;
c0 &= 0xFFFF;
c1 &= 0xFFFF;
arr[i] = (c1 << 16) | c0;
}
return goog.math.Integer.fromBits(arr);
};
/**
* Returns the difference of this and the given Integer.
* @param {goog.math.Integer} other The Integer to subtract from this.
* @return {!goog.math.Integer} The Integer result.
*/
goog.math.Integer.prototype.subtract = function(other) {
return this.add(other.negate());
};
/**
* Returns the product of this and the given Integer.
* @param {goog.math.Integer} other The Integer to multiply against this.
* @return {!goog.math.Integer} The product of this and the other.
*/
goog.math.Integer.prototype.multiply = function(other) {
if (this.isZero()) {
return goog.math.Integer.ZERO;
} else if (other.isZero()) {
return goog.math.Integer.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().multiply(other.negate());
} else {
return this.negate().multiply(other).negate();
}
} else if (other.isNegative()) {
return this.multiply(other.negate()).negate();
}
// If both numbers are small, use float multiplication
if (this.lessThan(goog.math.Integer.TWO_PWR_24_) &&
other.lessThan(goog.math.Integer.TWO_PWR_24_)) {
return goog.math.Integer.fromNumber(this.toNumber() * other.toNumber());
}
// Fill in an array of 16-bit products.
var len = this.bits_.length + other.bits_.length;
var arr = [];
for (var i = 0; i < 2 * len; i++) {
arr[i] = 0;
}
for (var i = 0; i < this.bits_.length; i++) {
for (var j = 0; j < other.bits_.length; j++) {
var a1 = this.getBits(i) >>> 16;
var a0 = this.getBits(i) & 0xFFFF;
var b1 = other.getBits(j) >>> 16;
var b0 = other.getBits(j) & 0xFFFF;
arr[2 * i + 2 * j] += a0 * b0;
goog.math.Integer.carry16_(arr, 2 * i + 2 * j);
arr[2 * i + 2 * j + 1] += a1 * b0;
goog.math.Integer.carry16_(arr, 2 * i + 2 * j + 1);
arr[2 * i + 2 * j + 1] += a0 * b1;
goog.math.Integer.carry16_(arr, 2 * i + 2 * j + 1);
arr[2 * i + 2 * j + 2] += a1 * b1;
goog.math.Integer.carry16_(arr, 2 * i + 2 * j + 2);
}
}
// Combine the 16-bit values into 32-bit values.
for (var i = 0; i < len; i++) {
arr[i] = (arr[2 * i + 1] << 16) | arr[2 * i];
}
for (var i = len; i < 2 * len; i++) {
arr[i] = 0;
}
return new goog.math.Integer(arr, 0);
};
/**
* Carries any overflow from the given index into later entries.
* @param {Array<number>} bits Array of 16-bit values in little-endian order.
* @param {number} index The index in question.
* @private
*/
goog.math.Integer.carry16_ = function(bits, index) {
while ((bits[index] & 0xFFFF) != bits[index]) {
bits[index + 1] += bits[index] >>> 16;
bits[index] &= 0xFFFF;
}
};
/**
* Returns this Integer divided by the given one.
* @param {goog.math.Integer} other Th Integer to divide this by.
* @return {!goog.math.Integer} This value divided by the given one.
*/
goog.math.Integer.prototype.divide = function(other) {
if (other.isZero()) {
throw Error('division by zero');
} else if (this.isZero()) {
return goog.math.Integer.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().divide(other.negate());
} else {
return this.negate().divide(other).negate();
}
} else if (other.isNegative()) {
return this.divide(other.negate()).negate();
}
// Repeat the following until the remainder is less than other: find a
// floating-point that approximates remainder / other *from below*, add this
// into the result, and subtract it from the remainder. It is critical that
// the approximate value is less than or equal to the real value so that the
// remainder never becomes negative.
var res = goog.math.Integer.ZERO;
var rem = this;
while (rem.greaterThanOrEqual(other)) {
// Approximate the result of division. This may be a little greater or
// smaller than the actual value.
var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()));
// We will tweak the approximate result by changing it in the 48-th digit or
// the smallest non-fractional digit, whichever is larger.
var log2 = Math.ceil(Math.log(approx) / Math.LN2);
var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48);
// Decrease the approximation until it is smaller than the remainder. Note
// that if it is too large, the product overflows and is negative.
var approxRes = goog.math.Integer.fromNumber(approx);
var approxRem = approxRes.multiply(other);
while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
approx -= delta;
approxRes = goog.math.Integer.fromNumber(approx);
approxRem = approxRes.multiply(other);
}
// We know the answer can't be zero... and actually, zero would cause
// infinite recursion since we would make no progress.
if (approxRes.isZero()) {
approxRes = goog.math.Integer.ONE;
}
res = res.add(approxRes);
rem = rem.subtract(approxRem);
}
return res;
};
/**
* Returns this Integer modulo the given one.
* @param {goog.math.Integer} other The Integer by which to mod.
* @return {!goog.math.Integer} This value modulo the given one.
*/
goog.math.Integer.prototype.modulo = function(other) {
return this.subtract(this.divide(other).multiply(other));
};
/** @return {!goog.math.Integer} The bitwise-NOT of this value. */
goog.math.Integer.prototype.not = function() {
var len = this.bits_.length;
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = ~this.bits_[i];
}
return new goog.math.Integer(arr, ~this.sign_);
};
/**
* Returns the bitwise-AND of this Integer and the given one.
* @param {goog.math.Integer} other The Integer to AND with this.
* @return {!goog.math.Integer} The bitwise-AND of this and the other.
*/
goog.math.Integer.prototype.and = function(other) {
var len = Math.max(this.bits_.length, other.bits_.length);
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = this.getBits(i) & other.getBits(i);
}
return new goog.math.Integer(arr, this.sign_ & other.sign_);
};
/**
* Returns the bitwise-OR of this Integer and the given one.
* @param {goog.math.Integer} other The Integer to OR with this.
* @return {!goog.math.Integer} The bitwise-OR of this and the other.
*/
goog.math.Integer.prototype.or = function(other) {
var len = Math.max(this.bits_.length, other.bits_.length);
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = this.getBits(i) | other.getBits(i);
}
return new goog.math.Integer(arr, this.sign_ | other.sign_);
};
/**
* Returns the bitwise-XOR of this Integer and the given one.
* @param {goog.math.Integer} other The Integer to XOR with this.
* @return {!goog.math.Integer} The bitwise-XOR of this and the other.
*/
goog.math.Integer.prototype.xor = function(other) {
var len = Math.max(this.bits_.length, other.bits_.length);
var arr = [];
for (var i = 0; i < len; i++) {
arr[i] = this.getBits(i) ^ other.getBits(i);
}
return new goog.math.Integer(arr, this.sign_ ^ other.sign_);
};
/**
* Returns this value with bits shifted to the left by the given amount.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Integer} This shifted to the left by the given amount.
*/
goog.math.Integer.prototype.shiftLeft = function(numBits) {
var arr_delta = numBits >> 5;
var bit_delta = numBits % 32;
var len = this.bits_.length + arr_delta + (bit_delta > 0 ? 1 : 0);
var arr = [];
for (var i = 0; i < len; i++) {
if (bit_delta > 0) {
arr[i] = (this.getBits(i - arr_delta) << bit_delta) |
(this.getBits(i - arr_delta - 1) >>> (32 - bit_delta));
} else {
arr[i] = this.getBits(i - arr_delta);
}
}
return new goog.math.Integer(arr, this.sign_);
};
/**
* Returns this value with bits shifted to the right by the given amount.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Integer} This shifted to the right by the given amount.
*/
goog.math.Integer.prototype.shiftRight = function(numBits) {
var arr_delta = numBits >> 5;
var bit_delta = numBits % 32;
var len = this.bits_.length - arr_delta;
var arr = [];
for (var i = 0; i < len; i++) {
if (bit_delta > 0) {
arr[i] = (this.getBits(i + arr_delta) >>> bit_delta) |
(this.getBits(i + arr_delta + 1) << (32 - bit_delta));
} else {
arr[i] = this.getBits(i + arr_delta);
}
}
return new goog.math.Integer(arr, this.sign_);
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2009 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Integer
</title>
<script src="../base.js" type="text/javascript">
</script>
<script type="text/javascript">
goog.require('goog.math.IntegerTest');
</script>
</head>
<body>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,64 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The base interface for one-dimensional data interpolation.
*
*/
goog.provide('goog.math.interpolator.Interpolator1');
/**
* An interface for one dimensional data interpolation.
* @interface
*/
goog.math.interpolator.Interpolator1 = function() {
};
/**
* Sets the data to be interpolated. Note that the data points are expected
* to be sorted according to their abscissa values and not have duplicate
* values. E.g. calling setData([0, 0, 1], [1, 1, 3]) may give undefined
* results, the correct call should be setData([0, 1], [1, 3]).
* Calling setData multiple times does not merge the data samples. The last
* call to setData is the one used when computing the interpolation.
* @param {!Array<number>} x The abscissa of the data points.
* @param {!Array<number>} y The ordinate of the data points.
*/
goog.math.interpolator.Interpolator1.prototype.setData;
/**
* Computes the interpolated value at abscissa x. If x is outside the range
* of the data points passed in setData, the value is extrapolated.
* @param {number} x The abscissa to sample at.
* @return {number} The interpolated value at abscissa x.
*/
goog.math.interpolator.Interpolator1.prototype.interpolate;
/**
* Computes the inverse interpolator. That is, it returns invInterp s.t.
* this.interpolate(invInterp.interpolate(t))) = t. Note that the inverse
* interpolator is only well defined if the data being interpolated is
* 'invertible', i.e. it represents a bijective function.
* In addition, the returned interpolator is only guaranteed to give the exact
* inverse at the input data passed in getData.
* If 'this' has no data, the returned Interpolator will be empty as well.
* @return {!goog.math.interpolator.Interpolator1} The inverse interpolator.
*/
goog.math.interpolator.Interpolator1.prototype.getInverse;
@@ -0,0 +1,84 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A one dimensional linear interpolator.
*
*/
goog.provide('goog.math.interpolator.Linear1');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.math');
goog.require('goog.math.interpolator.Interpolator1');
/**
* A one dimensional linear interpolator.
* @implements {goog.math.interpolator.Interpolator1}
* @constructor
* @final
*/
goog.math.interpolator.Linear1 = function() {
/**
* The abscissa of the data points.
* @type {!Array<number>}
* @private
*/
this.x_ = [];
/**
* The ordinate of the data points.
* @type {!Array<number>}
* @private
*/
this.y_ = [];
};
/** @override */
goog.math.interpolator.Linear1.prototype.setData = function(x, y) {
goog.asserts.assert(x.length == y.length,
'input arrays to setData should have the same length');
if (x.length == 1) {
this.x_ = [x[0], x[0] + 1];
this.y_ = [y[0], y[0]];
} else {
this.x_ = x.slice();
this.y_ = y.slice();
}
};
/** @override */
goog.math.interpolator.Linear1.prototype.interpolate = function(x) {
var pos = goog.array.binarySearch(this.x_, x);
if (pos < 0) {
pos = -pos - 2;
}
pos = goog.math.clamp(pos, 0, this.x_.length - 2);
var progress = (x - this.x_[pos]) / (this.x_[pos + 1] - this.x_[pos]);
return goog.math.lerp(this.y_[pos], this.y_[pos + 1], progress);
};
/** @override */
goog.math.interpolator.Linear1.prototype.getInverse = function() {
var interpolator = new goog.math.interpolator.Linear1();
interpolator.setData(this.y_, this.x_);
return interpolator;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.interpolator.Linear1
</title>
<script src="../../base.js">
</script>
<script>
goog.require('goog.math.interpolator.Linear1Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,84 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.interpolator.Linear1Test');
goog.setTestOnly('goog.math.interpolator.Linear1Test');
goog.require('goog.math.interpolator.Linear1');
goog.require('goog.testing.jsunit');
function testLinear() {
// Test special case with no data to interpolate.
var x = [];
var y = [];
var interp = new goog.math.interpolator.Linear1();
interp.setData(x, y);
assertTrue(isNaN(interp.interpolate(1)));
// Test special case with 1 data point.
x = [0];
y = [3];
interp = new goog.math.interpolator.Linear1();
interp.setData(x, y);
assertRoughlyEquals(3, interp.interpolate(1), 1e-4);
// Test general case.
x = [0, 1, 3, 6, 7];
y = [0, 0, 0, 0, 0];
for (var i = 0; i < x.length; ++i) {
y[i] = Math.sin(x[i]);
}
interp = new goog.math.interpolator.Linear1();
interp.setData(x, y);
var xi = [0, 0.5, 1, 2, 3, 4, 5, 6, 7];
var expected = [0, 0.4207, 0.8415, 0.4913, 0.1411, 0.0009, -0.1392,
-0.2794, 0.657];
var result = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < xi.length; ++i) {
result[i] = interp.interpolate(xi[i]);
}
assertElementsRoughlyEqual(expected, result, 1e-4);
}
function testOutOfBounds() {
var x = [0, 1, 2];
var y = [2, 5, 4];
var interp = new goog.math.interpolator.Linear1();
interp.setData(x, y);
assertRoughlyEquals(interp.interpolate(-1), -1, 1e-4);
assertRoughlyEquals(interp.interpolate(4), 2, 1e-4);
}
function testInverse() {
var x = [0, 1, 3, 6, 7];
var y = [0, 2, 7, 8, 10];
var interp = new goog.math.interpolator.Linear1();
interp.setData(x, y);
var invInterp = interp.getInverse();
var xi = [0, 0.5, 1, 2, 3, 4, 5, 6, 7];
var yi = [0, 1, 2, 4.5, 7, 7.3333, 7.6667, 8, 10];
var resultX = [0, 0, 0, 0, 0, 0, 0, 0, 0];
var resultY = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < xi.length; ++i) {
resultY[i] = interp.interpolate(xi[i]);
resultX[i] = invInterp.interpolate(yi[i]);
}
assertElementsRoughlyEqual(xi, resultX, 1e-4);
assertElementsRoughlyEqual(yi, resultY, 1e-4);
}
@@ -0,0 +1,82 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A one dimensional monotone cubic spline interpolator.
*
* See http://en.wikipedia.org/wiki/Monotone_cubic_interpolation.
*
*/
goog.provide('goog.math.interpolator.Pchip1');
goog.require('goog.math');
goog.require('goog.math.interpolator.Spline1');
/**
* A one dimensional monotone cubic spline interpolator.
* @extends {goog.math.interpolator.Spline1}
* @constructor
* @final
*/
goog.math.interpolator.Pchip1 = function() {
goog.math.interpolator.Pchip1.base(this, 'constructor');
};
goog.inherits(goog.math.interpolator.Pchip1, goog.math.interpolator.Spline1);
/** @override */
goog.math.interpolator.Pchip1.prototype.computeDerivatives = function(
dx, slope) {
var len = dx.length;
var deriv = new Array(len + 1);
for (var i = 1; i < len; ++i) {
if (goog.math.sign(slope[i - 1]) * goog.math.sign(slope[i]) <= 0) {
deriv[i] = 0;
} else {
var w1 = 2 * dx[i] + dx[i - 1];
var w2 = dx[i] + 2 * dx[i - 1];
deriv[i] = (w1 + w2) / (w1 / slope[i - 1] + w2 / slope[i]);
}
}
deriv[0] = this.computeDerivativeAtBoundary_(
dx[0], dx[1], slope[0], slope[1]);
deriv[len] = this.computeDerivativeAtBoundary_(
dx[len - 1], dx[len - 2], slope[len - 1], slope[len - 2]);
return deriv;
};
/**
* Computes the derivative of a data point at a boundary.
* @param {number} dx0 The spacing of the 1st data point.
* @param {number} dx1 The spacing of the 2nd data point.
* @param {number} slope0 The slope of the 1st data point.
* @param {number} slope1 The slope of the 2nd data point.
* @return {number} The derivative at the 1st data point.
* @private
*/
goog.math.interpolator.Pchip1.prototype.computeDerivativeAtBoundary_ = function(
dx0, dx1, slope0, slope1) {
var deriv = ((2 * dx0 + dx1) * slope0 - dx0 * slope1) / (dx0 + dx1);
if (goog.math.sign(deriv) != goog.math.sign(slope0)) {
deriv = 0;
} else if (goog.math.sign(slope0) != goog.math.sign(slope1) &&
Math.abs(deriv) > Math.abs(3 * slope0)) {
deriv = 3 * slope0;
}
return deriv;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.interpolator.Pchip1
</title>
<script src="../../base.js">
</script>
<script>
goog.require('goog.math.interpolator.Pchip1Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,71 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.interpolator.Pchip1Test');
goog.setTestOnly('goog.math.interpolator.Pchip1Test');
goog.require('goog.math.interpolator.Pchip1');
goog.require('goog.testing.jsunit');
function testSpline() {
var x = [0, 1, 3, 6, 7];
var y = [0, 0, 0, 0, 0];
for (var i = 0; i < x.length; ++i) {
y[i] = Math.sin(x[i]);
}
var interp = new goog.math.interpolator.Pchip1();
interp.setData(x, y);
var xi = [0, 0.5, 1, 2, 3, 4, 5, 6, 7];
var expected = [0, 0.5756, 0.8415, 0.5428, 0.1411, -0.0595, -0.2162,
-0.2794, 0.657];
var result = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < xi.length; ++i) {
result[i] = interp.interpolate(xi[i]);
}
assertElementsRoughlyEqual(expected, result, 1e-4);
}
function testOutOfBounds() {
var x = [0, 1, 2, 4];
var y = [2, 5, 4, 2];
var interp = new goog.math.interpolator.Pchip1();
interp.setData(x, y);
assertRoughlyEquals(-3, interp.interpolate(-1), 1e-4);
assertRoughlyEquals(1, interp.interpolate(5), 1e-4);
}
function testInverse() {
var x = [0, 1, 3, 6, 7];
var y = [0, 2, 7, 8, 10];
var interp = new goog.math.interpolator.Pchip1();
interp.setData(x, y);
var invInterp = interp.getInverse();
var xi = [0, 0.5, 1, 2, 3, 4, 5, 6, 7];
var yi = [0, 0.9548, 2, 4.8938, 7, 7.3906, 7.5902, 8, 10];
var expectedX = [0, 0.888, 1, 0.2852, 3, 4.1206, 4.7379, 6, 7];
var resultX = [0, 0, 0, 0, 0, 0, 0, 0, 0];
var resultY = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < xi.length; ++i) {
resultY[i] = interp.interpolate(xi[i]);
resultX[i] = invInterp.interpolate(yi[i]);
}
assertElementsRoughlyEqual(expectedX, resultX, 1e-4);
assertElementsRoughlyEqual(yi, resultY, 1e-4);
}
@@ -0,0 +1,203 @@
// Copyright 2012 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A one dimensional cubic spline interpolator with not-a-knot
* boundary conditions.
*
* See http://en.wikipedia.org/wiki/Spline_interpolation.
*
*/
goog.provide('goog.math.interpolator.Spline1');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.math');
goog.require('goog.math.interpolator.Interpolator1');
goog.require('goog.math.tdma');
/**
* A one dimensional cubic spline interpolator with natural boundary conditions.
* @implements {goog.math.interpolator.Interpolator1}
* @constructor
*/
goog.math.interpolator.Spline1 = function() {
/**
* The abscissa of the data points.
* @type {!Array<number>}
* @private
*/
this.x_ = [];
/**
* The spline interval coefficients.
* Note that, in general, the length of coeffs and x is not the same.
* @type {!Array<!Array<number>>}
* @private
*/
this.coeffs_ = [[0, 0, 0, Number.NaN]];
};
/** @override */
goog.math.interpolator.Spline1.prototype.setData = function(x, y) {
goog.asserts.assert(x.length == y.length,
'input arrays to setData should have the same length');
if (x.length > 0) {
this.coeffs_ = this.computeSplineCoeffs_(x, y);
this.x_ = x.slice();
} else {
this.coeffs_ = [[0, 0, 0, Number.NaN]];
this.x_ = [];
}
};
/** @override */
goog.math.interpolator.Spline1.prototype.interpolate = function(x) {
var pos = goog.array.binarySearch(this.x_, x);
if (pos < 0) {
pos = -pos - 2;
}
pos = goog.math.clamp(pos, 0, this.coeffs_.length - 1);
var d = x - this.x_[pos];
var d2 = d * d;
var d3 = d2 * d;
var coeffs = this.coeffs_[pos];
return coeffs[0] * d3 + coeffs[1] * d2 + coeffs[2] * d + coeffs[3];
};
/**
* Solve for the spline coefficients such that the spline precisely interpolates
* the data points.
* @param {Array<number>} x The abscissa of the spline data points.
* @param {Array<number>} y The ordinate of the spline data points.
* @return {!Array<!Array<number>>} The spline interval coefficients.
* @private
*/
goog.math.interpolator.Spline1.prototype.computeSplineCoeffs_ = function(x, y) {
var nIntervals = x.length - 1;
var dx = new Array(nIntervals);
var delta = new Array(nIntervals);
for (var i = 0; i < nIntervals; ++i) {
dx[i] = x[i + 1] - x[i];
delta[i] = (y[i + 1] - y[i]) / dx[i];
}
// Compute the spline coefficients from the 1st order derivatives.
var coeffs = [];
if (nIntervals == 0) {
// Nearest neighbor interpolation.
coeffs[0] = [0, 0, 0, y[0]];
} else if (nIntervals == 1) {
// Straight line interpolation.
coeffs[0] = [0, 0, delta[0], y[0]];
} else if (nIntervals == 2) {
// Parabola interpolation.
var c3 = 0;
var c2 = (delta[1] - delta[0]) / (dx[0] + dx[1]);
var c1 = delta[0] - c2 * dx[0];
var c0 = y[0];
coeffs[0] = [c3, c2, c1, c0];
} else {
// General Spline interpolation. Compute the 1st order derivatives from
// the Spline equations.
var deriv = this.computeDerivatives(dx, delta);
for (var i = 0; i < nIntervals; ++i) {
var c3 = (deriv[i] - 2 * delta[i] + deriv[i + 1]) / (dx[i] * dx[i]);
var c2 = (3 * delta[i] - 2 * deriv[i] - deriv[i + 1]) / dx[i];
var c1 = deriv[i];
var c0 = y[i];
coeffs[i] = [c3, c2, c1, c0];
}
}
return coeffs;
};
/**
* Computes the derivative at each point of the spline such that
* the curve is C2. It uses not-a-knot boundary conditions.
* @param {Array<number>} dx The spacing between consecutive data points.
* @param {Array<number>} slope The slopes between consecutive data points.
* @return {!Array<number>} The Spline derivative at each data point.
* @protected
*/
goog.math.interpolator.Spline1.prototype.computeDerivatives = function(
dx, slope) {
var nIntervals = dx.length;
// Compute the main diagonal of the system of equations.
var mainDiag = new Array(nIntervals + 1);
mainDiag[0] = dx[1];
for (var i = 1; i < nIntervals; ++i) {
mainDiag[i] = 2 * (dx[i] + dx[i - 1]);
}
mainDiag[nIntervals] = dx[nIntervals - 2];
// Compute the sub diagonal of the system of equations.
var subDiag = new Array(nIntervals);
for (var i = 0; i < nIntervals; ++i) {
subDiag[i] = dx[i + 1];
}
subDiag[nIntervals - 1] = dx[nIntervals - 2] + dx[nIntervals - 1];
// Compute the super diagonal of the system of equations.
var supDiag = new Array(nIntervals);
supDiag[0] = dx[0] + dx[1];
for (var i = 1; i < nIntervals; ++i) {
supDiag[i] = dx[i - 1];
}
// Compute the right vector of the system of equations.
var vecRight = new Array(nIntervals + 1);
vecRight[0] = ((dx[0] + 2 * supDiag[0]) * dx[1] * slope[0] +
dx[0] * dx[0] * slope[1]) / supDiag[0];
for (var i = 1; i < nIntervals; ++i) {
vecRight[i] = 3 * (dx[i] * slope[i - 1] + dx[i - 1] * slope[i]);
}
vecRight[nIntervals] = (dx[nIntervals - 1] * dx[nIntervals - 1] *
slope[nIntervals - 2] + (2 * subDiag[nIntervals - 1] +
dx[nIntervals - 1]) * dx[nIntervals - 2] * slope[nIntervals - 1]) /
subDiag[nIntervals - 1];
// Solve the system of equations.
var deriv = goog.math.tdma.solve(
subDiag, mainDiag, supDiag, vecRight);
return deriv;
};
/**
* Note that the inverse of a cubic spline is not a cubic spline in general.
* As a result the inverse implementation is only approximate. In
* particular, it only guarantees the exact inverse at the original input data
* points passed to setData.
* @override
*/
goog.math.interpolator.Spline1.prototype.getInverse = function() {
var interpolator = new goog.math.interpolator.Spline1();
var y = [];
for (var i = 0; i < this.x_.length; i++) {
y[i] = this.interpolate(this.x_[i]);
}
interpolator.setData(y, this.x_);
return interpolator;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.interpolator.Spline1
</title>
<script src="../../base.js">
</script>
<script>
goog.require('goog.math.interpolator.Spline1Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,100 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.interpolator.Spline1Test');
goog.setTestOnly('goog.math.interpolator.Spline1Test');
goog.require('goog.math.interpolator.Spline1');
goog.require('goog.testing.jsunit');
function testSpline() {
// Test special case with no data to interpolate.
var x = [];
var y = [];
var interp = new goog.math.interpolator.Spline1();
interp.setData(x, y);
assertTrue(isNaN(interp.interpolate(1)));
// Test special case with 1 data point.
x = [0];
y = [2];
interp = new goog.math.interpolator.Spline1();
interp.setData(x, y);
assertRoughlyEquals(2, interp.interpolate(1), 1e-4);
// Test special case with 2 data points.
x = [0, 1];
y = [2, 5];
interp = new goog.math.interpolator.Spline1();
interp.setData(x, y);
assertRoughlyEquals(3.5, interp.interpolate(.5), 1e-4);
// Test special case with 3 data points.
x = [0, 1, 2];
y = [2, 5, 4];
interp = new goog.math.interpolator.Spline1();
interp.setData(x, y);
assertRoughlyEquals(4, interp.interpolate(.5), 1e-4);
assertRoughlyEquals(-1, interp.interpolate(3), 1e-4);
// Test general case.
x = [0, 1, 3, 6, 7];
y = [0, 0, 0, 0, 0];
for (var i = 0; i < x.length; ++i) {
y[i] = Math.sin(x[i]);
}
interp = new goog.math.interpolator.Spline1();
interp.setData(x, y);
var xi = [0, 0.5, 1, 2, 3, 4, 5, 6, 7];
var expected = [0, 0.5775, 0.8415, 0.7047, 0.1411, -0.3601, -0.55940,
-0.2794, 0.6570];
var result = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < xi.length; ++i) {
result[i] = interp.interpolate(xi[i]);
}
assertElementsRoughlyEqual(expected, result, 1e-4);
}
function testOutOfBounds() {
var x = [0, 1, 2, 4];
var y = [2, 5, 4, 1];
var interp = new goog.math.interpolator.Spline1();
interp.setData(x, y);
assertRoughlyEquals(-7.75, interp.interpolate(-1), 1e-4);
assertRoughlyEquals(4.5, interp.interpolate(5), 1e-4);
}
function testInverse() {
var x = [0, 1, 3, 6, 7];
var y = [0, 2, 7, 8, 10];
var interp = new goog.math.interpolator.Spline1();
interp.setData(x, y);
var invInterp = interp.getInverse();
var xi = [0, 0.5, 1, 2, 3, 4, 5, 6, 7];
var yi = [0, 0.8159, 2, 4.7892, 7, 7.6912, 7.6275, 8, 10];
var expectedX = [0, 0.8142, 1, 0.2638, 3, 5.0534, 4.8544, 6, 7];
var resultX = [0, 0, 0, 0, 0, 0, 0, 0, 0];
var resultY = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < xi.length; ++i) {
resultY[i] = interp.interpolate(xi[i]);
resultX[i] = invInterp.interpolate(yi[i]);
}
assertElementsRoughlyEqual(expectedX, resultX, 1e-4);
assertElementsRoughlyEqual(yi, resultY, 1e-4);
}
@@ -0,0 +1,179 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Represents a line in 2D space.
*
* @author robbyw@google.com (Robby Walker)
*/
goog.provide('goog.math.Line');
goog.require('goog.math');
goog.require('goog.math.Coordinate');
/**
* Object representing a line.
* @param {number} x0 X coordinate of the start point.
* @param {number} y0 Y coordinate of the start point.
* @param {number} x1 X coordinate of the end point.
* @param {number} y1 Y coordinate of the end point.
* @struct
* @constructor
* @final
*/
goog.math.Line = function(x0, y0, x1, y1) {
/**
* X coordinate of the first point.
* @type {number}
*/
this.x0 = x0;
/**
* Y coordinate of the first point.
* @type {number}
*/
this.y0 = y0;
/**
* X coordinate of the first control point.
* @type {number}
*/
this.x1 = x1;
/**
* Y coordinate of the first control point.
* @type {number}
*/
this.y1 = y1;
};
/**
* @return {!goog.math.Line} A copy of this line.
*/
goog.math.Line.prototype.clone = function() {
return new goog.math.Line(this.x0, this.y0, this.x1, this.y1);
};
/**
* Tests whether the given line is exactly the same as this one.
* @param {goog.math.Line} other The other line.
* @return {boolean} Whether the given line is the same as this one.
*/
goog.math.Line.prototype.equals = function(other) {
return this.x0 == other.x0 && this.y0 == other.y0 &&
this.x1 == other.x1 && this.y1 == other.y1;
};
/**
* @return {number} The squared length of the line segment used to define the
* line.
*/
goog.math.Line.prototype.getSegmentLengthSquared = function() {
var xdist = this.x1 - this.x0;
var ydist = this.y1 - this.y0;
return xdist * xdist + ydist * ydist;
};
/**
* @return {number} The length of the line segment used to define the line.
*/
goog.math.Line.prototype.getSegmentLength = function() {
return Math.sqrt(this.getSegmentLengthSquared());
};
/**
* Computes the interpolation parameter for the point on the line closest to
* a given point.
* @param {number|goog.math.Coordinate} x The x coordinate of the point, or
* a coordinate object.
* @param {number=} opt_y The y coordinate of the point - required if x is a
* number, ignored if x is a goog.math.Coordinate.
* @return {number} The interpolation parameter of the point on the line
* closest to the given point.
* @private
*/
goog.math.Line.prototype.getClosestLinearInterpolation_ = function(x, opt_y) {
var y;
if (x instanceof goog.math.Coordinate) {
y = x.y;
x = x.x;
} else {
y = opt_y;
}
var x0 = this.x0;
var y0 = this.y0;
var xChange = this.x1 - x0;
var yChange = this.y1 - y0;
return ((x - x0) * xChange + (y - y0) * yChange) /
this.getSegmentLengthSquared();
};
/**
* Returns the point on the line segment proportional to t, where for t = 0 we
* return the starting point and for t = 1 we return the end point. For t < 0
* or t > 1 we extrapolate along the line defined by the line segment.
* @param {number} t The interpolation parameter along the line segment.
* @return {!goog.math.Coordinate} The point on the line segment at t.
*/
goog.math.Line.prototype.getInterpolatedPoint = function(t) {
return new goog.math.Coordinate(
goog.math.lerp(this.x0, this.x1, t),
goog.math.lerp(this.y0, this.y1, t));
};
/**
* Computes the point on the line closest to a given point. Note that a line
* in this case is defined as the infinite line going through the start and end
* points. To find the closest point on the line segment itself see
* {@see #getClosestSegmentPoint}.
* @param {number|goog.math.Coordinate} x The x coordinate of the point, or
* a coordinate object.
* @param {number=} opt_y The y coordinate of the point - required if x is a
* number, ignored if x is a goog.math.Coordinate.
* @return {!goog.math.Coordinate} The point on the line closest to the given
* point.
*/
goog.math.Line.prototype.getClosestPoint = function(x, opt_y) {
return this.getInterpolatedPoint(
this.getClosestLinearInterpolation_(x, opt_y));
};
/**
* Computes the point on the line segment closest to a given point.
* @param {number|goog.math.Coordinate} x The x coordinate of the point, or
* a coordinate object.
* @param {number=} opt_y The y coordinate of the point - required if x is a
* number, ignored if x is a goog.math.Coordinate.
* @return {!goog.math.Coordinate} The point on the line segment closest to the
* given point.
*/
goog.math.Line.prototype.getClosestSegmentPoint = function(x, opt_y) {
return this.getInterpolatedPoint(
goog.math.clamp(this.getClosestLinearInterpolation_(x, opt_y), 0, 1));
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Line
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.LineTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,59 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.LineTest');
goog.setTestOnly('goog.math.LineTest');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Line');
goog.require('goog.testing.jsunit');
function testEquals() {
var input = new goog.math.Line(1, 2, 3, 4);
assert(input.equals(input));
}
function testClone() {
var input = new goog.math.Line(1, 2, 3, 4);
assertNotEquals('Clone returns a new object', input, input.clone());
assertTrue('Contents of clone match original', input.equals(input.clone()));
}
function testGetLength() {
var input = new goog.math.Line(0, 0, Math.sqrt(2), Math.sqrt(2));
assertRoughlyEquals(input.getSegmentLengthSquared(), 4, 1e-10);
assertRoughlyEquals(input.getSegmentLength(), 2, 1e-10);
}
function testGetClosestPoint() {
var input = new goog.math.Line(0, 1, 1, 2);
var point = input.getClosestPoint(0, 3);
assertRoughlyEquals(point.x, 1, 1e-10);
assertRoughlyEquals(point.y, 2, 1e-10);
}
function testGetClosestSegmentPoint() {
var input = new goog.math.Line(0, 1, 2, 3);
var point = input.getClosestSegmentPoint(4, 4);
assertRoughlyEquals(point.x, 2, 1e-10);
assertRoughlyEquals(point.y, 3, 1e-10);
point = input.getClosestSegmentPoint(new goog.math.Coordinate(-1, -10));
assertRoughlyEquals(point.x, 0, 1e-10);
assertRoughlyEquals(point.y, 1, 1e-10);
}
@@ -0,0 +1,804 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines a Long class for representing a 64-bit two's-complement
* integer value, which faithfully simulates the behavior of a Java "long". This
* implementation is derived from LongLib in GWT.
*
*/
goog.provide('goog.math.Long');
/**
* Constructs a 64-bit two's-complement integer, given its low and high 32-bit
* values as *signed* integers. See the from* functions below for more
* convenient ways of constructing Longs.
*
* The internal representation of a long is the two given signed, 32-bit values.
* We use 32-bit pieces because these are the size of integers on which
* Javascript performs bit-operations. For operations like addition and
* multiplication, we split each number into 16-bit pieces, which can easily be
* multiplied within Javascript's floating-point representation without overflow
* or change in sign.
*
* In the algorithms below, we frequently reduce the negative case to the
* positive case by negating the input(s) and then post-processing the result.
* Note that we must ALWAYS check specially whether those values are MIN_VALUE
* (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as
* a positive number, it overflows back into a negative). Not handling this
* case would often result in infinite recursion.
*
* @param {number} low The low (signed) 32 bits of the long.
* @param {number} high The high (signed) 32 bits of the long.
* @struct
* @constructor
* @final
*/
goog.math.Long = function(low, high) {
/**
* @type {number}
* @private
*/
this.low_ = low | 0; // force into 32 signed bits.
/**
* @type {number}
* @private
*/
this.high_ = high | 0; // force into 32 signed bits.
};
// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the
// from* methods on which they depend.
/**
* A cache of the Long representations of small integer values.
* @type {!Object}
* @private
*/
goog.math.Long.IntCache_ = {};
/**
* Returns a Long representing the given (32-bit) integer value.
* @param {number} value The 32-bit integer in question.
* @return {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromInt = function(value) {
if (-128 <= value && value < 128) {
var cachedObj = goog.math.Long.IntCache_[value];
if (cachedObj) {
return cachedObj;
}
}
var obj = new goog.math.Long(value | 0, value < 0 ? -1 : 0);
if (-128 <= value && value < 128) {
goog.math.Long.IntCache_[value] = obj;
}
return obj;
};
/**
* Returns a Long representing the given value, provided that it is a finite
* number. Otherwise, zero is returned.
* @param {number} value The number in question.
* @return {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromNumber = function(value) {
if (isNaN(value) || !isFinite(value)) {
return goog.math.Long.ZERO;
} else if (value <= -goog.math.Long.TWO_PWR_63_DBL_) {
return goog.math.Long.MIN_VALUE;
} else if (value + 1 >= goog.math.Long.TWO_PWR_63_DBL_) {
return goog.math.Long.MAX_VALUE;
} else if (value < 0) {
return goog.math.Long.fromNumber(-value).negate();
} else {
return new goog.math.Long(
(value % goog.math.Long.TWO_PWR_32_DBL_) | 0,
(value / goog.math.Long.TWO_PWR_32_DBL_) | 0);
}
};
/**
* Returns a Long representing the 64-bit integer that comes by concatenating
* the given high and low bits. Each is assumed to use 32 bits.
* @param {number} lowBits The low 32-bits.
* @param {number} highBits The high 32-bits.
* @return {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromBits = function(lowBits, highBits) {
return new goog.math.Long(lowBits, highBits);
};
/**
* Returns a Long representation of the given string, written using the given
* radix.
* @param {string} str The textual representation of the Long.
* @param {number=} opt_radix The radix in which the text is written.
* @return {!goog.math.Long} The corresponding Long value.
*/
goog.math.Long.fromString = function(str, opt_radix) {
if (str.length == 0) {
throw Error('number format error: empty string');
}
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (str.charAt(0) == '-') {
return goog.math.Long.fromString(str.substring(1), radix).negate();
} else if (str.indexOf('-') >= 0) {
throw Error('number format error: interior "-" character: ' + str);
}
// Do several (8) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = goog.math.Long.fromNumber(Math.pow(radix, 8));
var result = goog.math.Long.ZERO;
for (var i = 0; i < str.length; i += 8) {
var size = Math.min(8, str.length - i);
var value = parseInt(str.substring(i, i + size), radix);
if (size < 8) {
var power = goog.math.Long.fromNumber(Math.pow(radix, size));
result = result.multiply(power).add(goog.math.Long.fromNumber(value));
} else {
result = result.multiply(radixToPower);
result = result.add(goog.math.Long.fromNumber(value));
}
}
return result;
};
// NOTE: the compiler should inline these constant values below and then remove
// these variables, so there should be no runtime penalty for these.
/**
* Number used repeated below in calculations. This must appear before the
* first call to any from* function below.
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_16_DBL_ = 1 << 16;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_24_DBL_ = 1 << 24;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_32_DBL_ =
goog.math.Long.TWO_PWR_16_DBL_ * goog.math.Long.TWO_PWR_16_DBL_;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_31_DBL_ =
goog.math.Long.TWO_PWR_32_DBL_ / 2;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_48_DBL_ =
goog.math.Long.TWO_PWR_32_DBL_ * goog.math.Long.TWO_PWR_16_DBL_;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_64_DBL_ =
goog.math.Long.TWO_PWR_32_DBL_ * goog.math.Long.TWO_PWR_32_DBL_;
/**
* @type {number}
* @private
*/
goog.math.Long.TWO_PWR_63_DBL_ =
goog.math.Long.TWO_PWR_64_DBL_ / 2;
/** @type {!goog.math.Long} */
goog.math.Long.ZERO = goog.math.Long.fromInt(0);
/** @type {!goog.math.Long} */
goog.math.Long.ONE = goog.math.Long.fromInt(1);
/** @type {!goog.math.Long} */
goog.math.Long.NEG_ONE = goog.math.Long.fromInt(-1);
/** @type {!goog.math.Long} */
goog.math.Long.MAX_VALUE =
goog.math.Long.fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0);
/** @type {!goog.math.Long} */
goog.math.Long.MIN_VALUE = goog.math.Long.fromBits(0, 0x80000000 | 0);
/**
* @type {!goog.math.Long}
* @private
*/
goog.math.Long.TWO_PWR_24_ = goog.math.Long.fromInt(1 << 24);
/** @return {number} The value, assuming it is a 32-bit integer. */
goog.math.Long.prototype.toInt = function() {
return this.low_;
};
/** @return {number} The closest floating-point representation to this value. */
goog.math.Long.prototype.toNumber = function() {
return this.high_ * goog.math.Long.TWO_PWR_32_DBL_ +
this.getLowBitsUnsigned();
};
/**
* @param {number=} opt_radix The radix in which the text should be written.
* @return {string} The textual representation of this value.
* @override
*/
goog.math.Long.prototype.toString = function(opt_radix) {
var radix = opt_radix || 10;
if (radix < 2 || 36 < radix) {
throw Error('radix out of range: ' + radix);
}
if (this.isZero()) {
return '0';
}
if (this.isNegative()) {
if (this.equals(goog.math.Long.MIN_VALUE)) {
// We need to change the Long value before it can be negated, so we remove
// the bottom-most digit in this base and then recurse to do the rest.
var radixLong = goog.math.Long.fromNumber(radix);
var div = this.div(radixLong);
var rem = div.multiply(radixLong).subtract(this);
return div.toString(radix) + rem.toInt().toString(radix);
} else {
return '-' + this.negate().toString(radix);
}
}
// Do several (6) digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
var radixToPower = goog.math.Long.fromNumber(Math.pow(radix, 6));
var rem = this;
var result = '';
while (true) {
var remDiv = rem.div(radixToPower);
var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt();
var digits = intval.toString(radix);
rem = remDiv;
if (rem.isZero()) {
return digits + result;
} else {
while (digits.length < 6) {
digits = '0' + digits;
}
result = '' + digits + result;
}
}
};
/** @return {number} The high 32-bits as a signed value. */
goog.math.Long.prototype.getHighBits = function() {
return this.high_;
};
/** @return {number} The low 32-bits as a signed value. */
goog.math.Long.prototype.getLowBits = function() {
return this.low_;
};
/** @return {number} The low 32-bits as an unsigned value. */
goog.math.Long.prototype.getLowBitsUnsigned = function() {
return (this.low_ >= 0) ?
this.low_ : goog.math.Long.TWO_PWR_32_DBL_ + this.low_;
};
/**
* @return {number} Returns the number of bits needed to represent the absolute
* value of this Long.
*/
goog.math.Long.prototype.getNumBitsAbs = function() {
if (this.isNegative()) {
if (this.equals(goog.math.Long.MIN_VALUE)) {
return 64;
} else {
return this.negate().getNumBitsAbs();
}
} else {
var val = this.high_ != 0 ? this.high_ : this.low_;
for (var bit = 31; bit > 0; bit--) {
if ((val & (1 << bit)) != 0) {
break;
}
}
return this.high_ != 0 ? bit + 33 : bit + 1;
}
};
/** @return {boolean} Whether this value is zero. */
goog.math.Long.prototype.isZero = function() {
return this.high_ == 0 && this.low_ == 0;
};
/** @return {boolean} Whether this value is negative. */
goog.math.Long.prototype.isNegative = function() {
return this.high_ < 0;
};
/** @return {boolean} Whether this value is odd. */
goog.math.Long.prototype.isOdd = function() {
return (this.low_ & 1) == 1;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long equals the other.
*/
goog.math.Long.prototype.equals = function(other) {
return (this.high_ == other.high_) && (this.low_ == other.low_);
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long does not equal the other.
*/
goog.math.Long.prototype.notEquals = function(other) {
return (this.high_ != other.high_) || (this.low_ != other.low_);
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is less than the other.
*/
goog.math.Long.prototype.lessThan = function(other) {
return this.compare(other) < 0;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is less than or equal to the other.
*/
goog.math.Long.prototype.lessThanOrEqual = function(other) {
return this.compare(other) <= 0;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is greater than the other.
*/
goog.math.Long.prototype.greaterThan = function(other) {
return this.compare(other) > 0;
};
/**
* @param {goog.math.Long} other Long to compare against.
* @return {boolean} Whether this Long is greater than or equal to the other.
*/
goog.math.Long.prototype.greaterThanOrEqual = function(other) {
return this.compare(other) >= 0;
};
/**
* Compares this Long with the given one.
* @param {goog.math.Long} other Long to compare against.
* @return {number} 0 if they are the same, 1 if the this is greater, and -1
* if the given one is greater.
*/
goog.math.Long.prototype.compare = function(other) {
if (this.equals(other)) {
return 0;
}
var thisNeg = this.isNegative();
var otherNeg = other.isNegative();
if (thisNeg && !otherNeg) {
return -1;
}
if (!thisNeg && otherNeg) {
return 1;
}
// at this point, the signs are the same, so subtraction will not overflow
if (this.subtract(other).isNegative()) {
return -1;
} else {
return 1;
}
};
/** @return {!goog.math.Long} The negation of this value. */
goog.math.Long.prototype.negate = function() {
if (this.equals(goog.math.Long.MIN_VALUE)) {
return goog.math.Long.MIN_VALUE;
} else {
return this.not().add(goog.math.Long.ONE);
}
};
/**
* Returns the sum of this and the given Long.
* @param {goog.math.Long} other Long to add to this one.
* @return {!goog.math.Long} The sum of this and the given Long.
*/
goog.math.Long.prototype.add = function(other) {
// Divide each number into 4 chunks of 16 bits, and then sum the chunks.
var a48 = this.high_ >>> 16;
var a32 = this.high_ & 0xFFFF;
var a16 = this.low_ >>> 16;
var a00 = this.low_ & 0xFFFF;
var b48 = other.high_ >>> 16;
var b32 = other.high_ & 0xFFFF;
var b16 = other.low_ >>> 16;
var b00 = other.low_ & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 + b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 + b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 + b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 + b48;
c48 &= 0xFFFF;
return goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns the difference of this and the given Long.
* @param {goog.math.Long} other Long to subtract from this.
* @return {!goog.math.Long} The difference of this and the given Long.
*/
goog.math.Long.prototype.subtract = function(other) {
return this.add(other.negate());
};
/**
* Returns the product of this and the given long.
* @param {goog.math.Long} other Long to multiply with this.
* @return {!goog.math.Long} The product of this and the other.
*/
goog.math.Long.prototype.multiply = function(other) {
if (this.isZero()) {
return goog.math.Long.ZERO;
} else if (other.isZero()) {
return goog.math.Long.ZERO;
}
if (this.equals(goog.math.Long.MIN_VALUE)) {
return other.isOdd() ? goog.math.Long.MIN_VALUE : goog.math.Long.ZERO;
} else if (other.equals(goog.math.Long.MIN_VALUE)) {
return this.isOdd() ? goog.math.Long.MIN_VALUE : goog.math.Long.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().multiply(other.negate());
} else {
return this.negate().multiply(other).negate();
}
} else if (other.isNegative()) {
return this.multiply(other.negate()).negate();
}
// If both longs are small, use float multiplication
if (this.lessThan(goog.math.Long.TWO_PWR_24_) &&
other.lessThan(goog.math.Long.TWO_PWR_24_)) {
return goog.math.Long.fromNumber(this.toNumber() * other.toNumber());
}
// Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.
// We can skip products that would overflow.
var a48 = this.high_ >>> 16;
var a32 = this.high_ & 0xFFFF;
var a16 = this.low_ >>> 16;
var a00 = this.low_ & 0xFFFF;
var b48 = other.high_ >>> 16;
var b32 = other.high_ & 0xFFFF;
var b16 = other.low_ >>> 16;
var b00 = other.low_ & 0xFFFF;
var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
c00 += a00 * b00;
c16 += c00 >>> 16;
c00 &= 0xFFFF;
c16 += a16 * b00;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c16 += a00 * b16;
c32 += c16 >>> 16;
c16 &= 0xFFFF;
c32 += a32 * b00;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a16 * b16;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c32 += a00 * b32;
c48 += c32 >>> 16;
c32 &= 0xFFFF;
c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
c48 &= 0xFFFF;
return goog.math.Long.fromBits((c16 << 16) | c00, (c48 << 16) | c32);
};
/**
* Returns this Long divided by the given one.
* @param {goog.math.Long} other Long by which to divide.
* @return {!goog.math.Long} This Long divided by the given one.
*/
goog.math.Long.prototype.div = function(other) {
if (other.isZero()) {
throw Error('division by zero');
} else if (this.isZero()) {
return goog.math.Long.ZERO;
}
if (this.equals(goog.math.Long.MIN_VALUE)) {
if (other.equals(goog.math.Long.ONE) ||
other.equals(goog.math.Long.NEG_ONE)) {
return goog.math.Long.MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE
} else if (other.equals(goog.math.Long.MIN_VALUE)) {
return goog.math.Long.ONE;
} else {
// At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|.
var halfThis = this.shiftRight(1);
var approx = halfThis.div(other).shiftLeft(1);
if (approx.equals(goog.math.Long.ZERO)) {
return other.isNegative() ? goog.math.Long.ONE : goog.math.Long.NEG_ONE;
} else {
var rem = this.subtract(other.multiply(approx));
var result = approx.add(rem.div(other));
return result;
}
}
} else if (other.equals(goog.math.Long.MIN_VALUE)) {
return goog.math.Long.ZERO;
}
if (this.isNegative()) {
if (other.isNegative()) {
return this.negate().div(other.negate());
} else {
return this.negate().div(other).negate();
}
} else if (other.isNegative()) {
return this.div(other.negate()).negate();
}
// Repeat the following until the remainder is less than other: find a
// floating-point that approximates remainder / other *from below*, add this
// into the result, and subtract it from the remainder. It is critical that
// the approximate value is less than or equal to the real value so that the
// remainder never becomes negative.
var res = goog.math.Long.ZERO;
var rem = this;
while (rem.greaterThanOrEqual(other)) {
// Approximate the result of division. This may be a little greater or
// smaller than the actual value.
var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()));
// We will tweak the approximate result by changing it in the 48-th digit or
// the smallest non-fractional digit, whichever is larger.
var log2 = Math.ceil(Math.log(approx) / Math.LN2);
var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48);
// Decrease the approximation until it is smaller than the remainder. Note
// that if it is too large, the product overflows and is negative.
var approxRes = goog.math.Long.fromNumber(approx);
var approxRem = approxRes.multiply(other);
while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
approx -= delta;
approxRes = goog.math.Long.fromNumber(approx);
approxRem = approxRes.multiply(other);
}
// We know the answer can't be zero... and actually, zero would cause
// infinite recursion since we would make no progress.
if (approxRes.isZero()) {
approxRes = goog.math.Long.ONE;
}
res = res.add(approxRes);
rem = rem.subtract(approxRem);
}
return res;
};
/**
* Returns this Long modulo the given one.
* @param {goog.math.Long} other Long by which to mod.
* @return {!goog.math.Long} This Long modulo the given one.
*/
goog.math.Long.prototype.modulo = function(other) {
return this.subtract(this.div(other).multiply(other));
};
/** @return {!goog.math.Long} The bitwise-NOT of this value. */
goog.math.Long.prototype.not = function() {
return goog.math.Long.fromBits(~this.low_, ~this.high_);
};
/**
* Returns the bitwise-AND of this Long and the given one.
* @param {goog.math.Long} other The Long with which to AND.
* @return {!goog.math.Long} The bitwise-AND of this and the other.
*/
goog.math.Long.prototype.and = function(other) {
return goog.math.Long.fromBits(this.low_ & other.low_,
this.high_ & other.high_);
};
/**
* Returns the bitwise-OR of this Long and the given one.
* @param {goog.math.Long} other The Long with which to OR.
* @return {!goog.math.Long} The bitwise-OR of this and the other.
*/
goog.math.Long.prototype.or = function(other) {
return goog.math.Long.fromBits(this.low_ | other.low_,
this.high_ | other.high_);
};
/**
* Returns the bitwise-XOR of this Long and the given one.
* @param {goog.math.Long} other The Long with which to XOR.
* @return {!goog.math.Long} The bitwise-XOR of this and the other.
*/
goog.math.Long.prototype.xor = function(other) {
return goog.math.Long.fromBits(this.low_ ^ other.low_,
this.high_ ^ other.high_);
};
/**
* Returns this Long with bits shifted to the left by the given amount.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Long} This shifted to the left by the given amount.
*/
goog.math.Long.prototype.shiftLeft = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var low = this.low_;
if (numBits < 32) {
var high = this.high_;
return goog.math.Long.fromBits(
low << numBits,
(high << numBits) | (low >>> (32 - numBits)));
} else {
return goog.math.Long.fromBits(0, low << (numBits - 32));
}
}
};
/**
* Returns this Long with bits shifted to the right by the given amount.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Long} This shifted to the right by the given amount.
*/
goog.math.Long.prototype.shiftRight = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return goog.math.Long.fromBits(
(low >>> numBits) | (high << (32 - numBits)),
high >> numBits);
} else {
return goog.math.Long.fromBits(
high >> (numBits - 32),
high >= 0 ? 0 : -1);
}
}
};
/**
* Returns this Long with bits shifted to the right by the given amount, with
* zeros placed into the new leading bits.
* @param {number} numBits The number of bits by which to shift.
* @return {!goog.math.Long} This shifted to the right by the given amount, with
* zeros placed into the new leading bits.
*/
goog.math.Long.prototype.shiftRightUnsigned = function(numBits) {
numBits &= 63;
if (numBits == 0) {
return this;
} else {
var high = this.high_;
if (numBits < 32) {
var low = this.low_;
return goog.math.Long.fromBits(
(low >>> numBits) | (high << (32 - numBits)),
high >>> numBits);
} else if (numBits == 32) {
return goog.math.Long.fromBits(high, 0);
} else {
return goog.math.Long.fromBits(high >>> (numBits - 32), 0);
}
}
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2009 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Long
</title>
<script src="../base.js" type="text/javascript">
</script>
<script type="text/javascript">
goog.require('goog.math.LongTest');
</script>
</head>
<body>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,435 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Additional mathematical functions.
*/
goog.provide('goog.math');
goog.require('goog.array');
goog.require('goog.asserts');
/**
* Returns a random integer greater than or equal to 0 and less than {@code a}.
* @param {number} a The upper bound for the random integer (exclusive).
* @return {number} A random integer N such that 0 <= N < a.
*/
goog.math.randomInt = function(a) {
return Math.floor(Math.random() * a);
};
/**
* Returns a random number greater than or equal to {@code a} and less than
* {@code b}.
* @param {number} a The lower bound for the random number (inclusive).
* @param {number} b The upper bound for the random number (exclusive).
* @return {number} A random number N such that a <= N < b.
*/
goog.math.uniformRandom = function(a, b) {
return a + Math.random() * (b - a);
};
/**
* Takes a number and clamps it to within the provided bounds.
* @param {number} value The input number.
* @param {number} min The minimum value to return.
* @param {number} max The maximum value to return.
* @return {number} The input number if it is within bounds, or the nearest
* number within the bounds.
*/
goog.math.clamp = function(value, min, max) {
return Math.min(Math.max(value, min), max);
};
/**
* The % operator in JavaScript returns the remainder of a / b, but differs from
* some other languages in that the result will have the same sign as the
* dividend. For example, -1 % 8 == -1, whereas in some other languages
* (such as Python) the result would be 7. This function emulates the more
* correct modulo behavior, which is useful for certain applications such as
* calculating an offset index in a circular list.
*
* @param {number} a The dividend.
* @param {number} b The divisor.
* @return {number} a % b where the result is between 0 and b (either 0 <= x < b
* or b < x <= 0, depending on the sign of b).
*/
goog.math.modulo = function(a, b) {
var r = a % b;
// If r and b differ in sign, add b to wrap the result to the correct sign.
return (r * b < 0) ? r + b : r;
};
/**
* Performs linear interpolation between values a and b. Returns the value
* between a and b proportional to x (when x is between 0 and 1. When x is
* outside this range, the return value is a linear extrapolation).
* @param {number} a A number.
* @param {number} b A number.
* @param {number} x The proportion between a and b.
* @return {number} The interpolated value between a and b.
*/
goog.math.lerp = function(a, b, x) {
return a + x * (b - a);
};
/**
* Tests whether the two values are equal to each other, within a certain
* tolerance to adjust for floating point errors.
* @param {number} a A number.
* @param {number} b A number.
* @param {number=} opt_tolerance Optional tolerance range. Defaults
* to 0.000001. If specified, should be greater than 0.
* @return {boolean} Whether {@code a} and {@code b} are nearly equal.
*/
goog.math.nearlyEquals = function(a, b, opt_tolerance) {
return Math.abs(a - b) <= (opt_tolerance || 0.000001);
};
// TODO(user): Rename to normalizeAngle, retaining old name as deprecated
// alias.
/**
* Normalizes an angle to be in range [0-360). Angles outside this range will
* be normalized to be the equivalent angle with that range.
* @param {number} angle Angle in degrees.
* @return {number} Standardized angle.
*/
goog.math.standardAngle = function(angle) {
return goog.math.modulo(angle, 360);
};
/**
* Normalizes an angle to be in range [0-2*PI). Angles outside this range will
* be normalized to be the equivalent angle with that range.
* @param {number} angle Angle in radians.
* @return {number} Standardized angle.
*/
goog.math.standardAngleInRadians = function(angle) {
return goog.math.modulo(angle, 2 * Math.PI);
};
/**
* Converts degrees to radians.
* @param {number} angleDegrees Angle in degrees.
* @return {number} Angle in radians.
*/
goog.math.toRadians = function(angleDegrees) {
return angleDegrees * Math.PI / 180;
};
/**
* Converts radians to degrees.
* @param {number} angleRadians Angle in radians.
* @return {number} Angle in degrees.
*/
goog.math.toDegrees = function(angleRadians) {
return angleRadians * 180 / Math.PI;
};
/**
* For a given angle and radius, finds the X portion of the offset.
* @param {number} degrees Angle in degrees (zero points in +X direction).
* @param {number} radius Radius.
* @return {number} The x-distance for the angle and radius.
*/
goog.math.angleDx = function(degrees, radius) {
return radius * Math.cos(goog.math.toRadians(degrees));
};
/**
* For a given angle and radius, finds the Y portion of the offset.
* @param {number} degrees Angle in degrees (zero points in +X direction).
* @param {number} radius Radius.
* @return {number} The y-distance for the angle and radius.
*/
goog.math.angleDy = function(degrees, radius) {
return radius * Math.sin(goog.math.toRadians(degrees));
};
/**
* Computes the angle between two points (x1,y1) and (x2,y2).
* Angle zero points in the +X direction, 90 degrees points in the +Y
* direction (down) and from there we grow clockwise towards 360 degrees.
* @param {number} x1 x of first point.
* @param {number} y1 y of first point.
* @param {number} x2 x of second point.
* @param {number} y2 y of second point.
* @return {number} Standardized angle in degrees of the vector from
* x1,y1 to x2,y2.
*/
goog.math.angle = function(x1, y1, x2, y2) {
return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(y2 - y1,
x2 - x1)));
};
/**
* Computes the difference between startAngle and endAngle (angles in degrees).
* @param {number} startAngle Start angle in degrees.
* @param {number} endAngle End angle in degrees.
* @return {number} The number of degrees that when added to
* startAngle will result in endAngle. Positive numbers mean that the
* direction is clockwise. Negative numbers indicate a counter-clockwise
* direction.
* The shortest route (clockwise vs counter-clockwise) between the angles
* is used.
* When the difference is 180 degrees, the function returns 180 (not -180)
* angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10.
* angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20.
*/
goog.math.angleDifference = function(startAngle, endAngle) {
var d = goog.math.standardAngle(endAngle) -
goog.math.standardAngle(startAngle);
if (d > 180) {
d = d - 360;
} else if (d <= -180) {
d = 360 + d;
}
return d;
};
/**
* Returns the sign of a number as per the "sign" or "signum" function.
* @param {number} x The number to take the sign of.
* @return {number} -1 when negative, 1 when positive, 0 when 0.
*/
goog.math.sign = function(x) {
return x == 0 ? 0 : (x < 0 ? -1 : 1);
};
/**
* JavaScript implementation of Longest Common Subsequence problem.
* http://en.wikipedia.org/wiki/Longest_common_subsequence
*
* Returns the longest possible array that is subarray of both of given arrays.
*
* @param {Array<Object>} array1 First array of objects.
* @param {Array<Object>} array2 Second array of objects.
* @param {Function=} opt_compareFn Function that acts as a custom comparator
* for the array ojects. Function should return true if objects are equal,
* otherwise false.
* @param {Function=} opt_collectorFn Function used to decide what to return
* as a result subsequence. It accepts 2 arguments: index of common element
* in the first array and index in the second. The default function returns
* element from the first array.
* @return {!Array<Object>} A list of objects that are common to both arrays
* such that there is no common subsequence with size greater than the
* length of the list.
*/
goog.math.longestCommonSubsequence = function(
array1, array2, opt_compareFn, opt_collectorFn) {
var compare = opt_compareFn || function(a, b) {
return a == b;
};
var collect = opt_collectorFn || function(i1, i2) {
return array1[i1];
};
var length1 = array1.length;
var length2 = array2.length;
var arr = [];
for (var i = 0; i < length1 + 1; i++) {
arr[i] = [];
arr[i][0] = 0;
}
for (var j = 0; j < length2 + 1; j++) {
arr[0][j] = 0;
}
for (i = 1; i <= length1; i++) {
for (j = 1; j <= length2; j++) {
if (compare(array1[i - 1], array2[j - 1])) {
arr[i][j] = arr[i - 1][j - 1] + 1;
} else {
arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]);
}
}
}
// Backtracking
var result = [];
var i = length1, j = length2;
while (i > 0 && j > 0) {
if (compare(array1[i - 1], array2[j - 1])) {
result.unshift(collect(i - 1, j - 1));
i--;
j--;
} else {
if (arr[i - 1][j] > arr[i][j - 1]) {
i--;
} else {
j--;
}
}
}
return result;
};
/**
* Returns the sum of the arguments.
* @param {...number} var_args Numbers to add.
* @return {number} The sum of the arguments (0 if no arguments were provided,
* {@code NaN} if any of the arguments is not a valid number).
*/
goog.math.sum = function(var_args) {
return /** @type {number} */ (goog.array.reduce(arguments,
function(sum, value) {
return sum + value;
}, 0));
};
/**
* Returns the arithmetic mean of the arguments.
* @param {...number} var_args Numbers to average.
* @return {number} The average of the arguments ({@code NaN} if no arguments
* were provided or any of the arguments is not a valid number).
*/
goog.math.average = function(var_args) {
return goog.math.sum.apply(null, arguments) / arguments.length;
};
/**
* Returns the unbiased sample variance of the arguments. For a definition,
* see e.g. http://en.wikipedia.org/wiki/Variance
* @param {...number} var_args Number samples to analyze.
* @return {number} The unbiased sample variance of the arguments (0 if fewer
* than two samples were provided, or {@code NaN} if any of the samples is
* not a valid number).
*/
goog.math.sampleVariance = function(var_args) {
var sampleSize = arguments.length;
if (sampleSize < 2) {
return 0;
}
var mean = goog.math.average.apply(null, arguments);
var variance = goog.math.sum.apply(null, goog.array.map(arguments,
function(val) {
return Math.pow(val - mean, 2);
})) / (sampleSize - 1);
return variance;
};
/**
* Returns the sample standard deviation of the arguments. For a definition of
* sample standard deviation, see e.g.
* http://en.wikipedia.org/wiki/Standard_deviation
* @param {...number} var_args Number samples to analyze.
* @return {number} The sample standard deviation of the arguments (0 if fewer
* than two samples were provided, or {@code NaN} if any of the samples is
* not a valid number).
*/
goog.math.standardDeviation = function(var_args) {
return Math.sqrt(goog.math.sampleVariance.apply(null, arguments));
};
/**
* Returns whether the supplied number represents an integer, i.e. that is has
* no fractional component. No range-checking is performed on the number.
* @param {number} num The number to test.
* @return {boolean} Whether {@code num} is an integer.
*/
goog.math.isInt = function(num) {
return isFinite(num) && num % 1 == 0;
};
/**
* Returns whether the supplied number is finite and not NaN.
* @param {number} num The number to test.
* @return {boolean} Whether {@code num} is a finite number.
*/
goog.math.isFiniteNumber = function(num) {
return isFinite(num) && !isNaN(num);
};
/**
* Returns the precise value of floor(log10(num)).
* Simpler implementations didn't work because of floating point rounding
* errors. For example
* <ul>
* <li>Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3.
* <li>Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15.
* <li>Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1.
* </ul>
* @param {number} num A floating point number.
* @return {number} Its logarithm to base 10 rounded down to the nearest
* integer if num > 0. -Infinity if num == 0. NaN if num < 0.
*/
goog.math.log10Floor = function(num) {
if (num > 0) {
var x = Math.round(Math.log(num) * Math.LOG10E);
return x - (parseFloat('1e' + x) > num);
}
return num == 0 ? -Infinity : NaN;
};
/**
* A tweaked variant of {@code Math.floor} which tolerates if the passed number
* is infinitesimally smaller than the closest integer. It often happens with
* the results of floating point calculations because of the finite precision
* of the intermediate results. For example {@code Math.floor(Math.log(1000) /
* Math.LN10) == 2}, not 3 as one would expect.
* @param {number} num A number.
* @param {number=} opt_epsilon An infinitesimally small positive number, the
* rounding error to tolerate.
* @return {number} The largest integer less than or equal to {@code num}.
*/
goog.math.safeFloor = function(num, opt_epsilon) {
goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
return Math.floor(num + (opt_epsilon || 2e-15));
};
/**
* A tweaked variant of {@code Math.ceil}. See {@code goog.math.safeFloor} for
* details.
* @param {number} num A number.
* @param {number=} opt_epsilon An infinitesimally small positive number, the
* rounding error to tolerate.
* @return {number} The smallest integer greater than or equal to {@code num}.
*/
goog.math.safeCeil = function(num, opt_epsilon) {
goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
return Math.ceil(num - (opt_epsilon || 2e-15));
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.mathTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,332 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.mathTest');
goog.setTestOnly('goog.mathTest');
goog.require('goog.math');
goog.require('goog.testing.jsunit');
function testRandomInt() {
assertEquals(0, goog.math.randomInt(0));
assertEquals(0, goog.math.randomInt(1));
var r = goog.math.randomInt(3);
assertTrue(0 <= r && r < 3);
}
function testUniformRandom() {
assertEquals(5.2, goog.math.uniformRandom(5.2, 5.2));
assertEquals(-6, goog.math.uniformRandom(-6, -6));
var r = goog.math.uniformRandom(-0.5, 0.5);
assertTrue(-0.5 <= r && r < 0.5);
}
function testClamp() {
assertEquals(3, goog.math.clamp(3, -5, 5));
assertEquals(5, goog.math.clamp(5, -5, 5));
assertEquals(-5, goog.math.clamp(-5, -5, 5));
assertEquals(-5, goog.math.clamp(-22, -5, 5));
assertEquals(5, goog.math.clamp(6, -5, 5));
}
function testModulo() {
assertEquals(0, goog.math.modulo(256, 8));
assertEquals(7, goog.math.modulo(7, 8));
assertEquals(7, goog.math.modulo(23, 8));
assertEquals(7, goog.math.modulo(-1, 8));
// Safari 5.1.7 has a bug in its JS engine where modulo is computed
// incorrectly when using variables. We avoid using
// goog.testing.ExpectedFailure here since it pulls in a bunch of
// extra dependencies for maintaining a DOM console.
var a = 1;
var b = -5;
if (a % b === 1 % -5) {
assertEquals(-4, goog.math.modulo(1, -5));
assertEquals(-4, goog.math.modulo(6, -5));
}
assertEquals(-4, goog.math.modulo(-4, -5));
}
function testLerp() {
assertEquals(0, goog.math.lerp(0, 0, 0));
assertEquals(3, goog.math.lerp(0, 6, 0.5));
assertEquals(3, goog.math.lerp(-1, 1, 2));
}
function testNearlyEquals() {
assertTrue('Numbers inside default tolerance should be equal',
goog.math.nearlyEquals(0.000001, 0.000001001));
assertFalse('Numbers outside default tolerance should be unequal',
goog.math.nearlyEquals(0.000001, 0.000003));
assertTrue('Numbers inside custom tolerance should be equal',
goog.math.nearlyEquals(0.001, 0.002, 0.1));
assertFalse('Numbers outside custom tolerance should be unequal',
goog.math.nearlyEquals(0.001, -0.1, 0.1));
assertTrue('Integer tolerance greater than one should succeed',
goog.math.nearlyEquals(87, 85, 3));
}
function testStandardAngleInRadians() {
assertRoughlyEquals(0, goog.math.standardAngleInRadians(2 * Math.PI), 1e-10);
assertRoughlyEquals(
Math.PI, goog.math.standardAngleInRadians(Math.PI), 1e-10);
assertRoughlyEquals(
Math.PI, goog.math.standardAngleInRadians(-1 * Math.PI), 1e-10);
assertRoughlyEquals(
Math.PI / 2, goog.math.standardAngleInRadians(-1.5 * Math.PI), 1e-10);
assertRoughlyEquals(
Math.PI, goog.math.standardAngleInRadians(5 * Math.PI), 1e-10);
assertEquals(0.01, goog.math.standardAngleInRadians(0.01));
assertEquals(0, goog.math.standardAngleInRadians(0));
}
function testStandardAngle() {
assertEquals(359.5, goog.math.standardAngle(-360.5));
assertEquals(0, goog.math.standardAngle(-360));
assertEquals(359.5, goog.math.standardAngle(-0.5));
assertEquals(0, goog.math.standardAngle(0));
assertEquals(0.5, goog.math.standardAngle(0.5));
assertEquals(0, goog.math.standardAngle(360));
assertEquals(1, goog.math.standardAngle(721));
}
function testToRadians() {
assertEquals(-Math.PI, goog.math.toRadians(-180));
assertEquals(0, goog.math.toRadians(0));
assertEquals(Math.PI, goog.math.toRadians(180));
}
function testToDegrees() {
assertEquals(-180, goog.math.toDegrees(-Math.PI));
assertEquals(0, goog.math.toDegrees(0));
assertEquals(180, goog.math.toDegrees(Math.PI));
}
function testAngleDx() {
assertRoughlyEquals(0, goog.math.angleDx(0, 0), 1e-10);
assertRoughlyEquals(0, goog.math.angleDx(90, 0), 1e-10);
assertRoughlyEquals(100, goog.math.angleDx(0, 100), 1e-10);
assertRoughlyEquals(0, goog.math.angleDx(90, 100), 1e-10);
assertRoughlyEquals(-100, goog.math.angleDx(180, 100), 1e-10);
assertRoughlyEquals(0, goog.math.angleDx(270, 100), 1e-10);
}
function testAngleDy() {
assertRoughlyEquals(0, goog.math.angleDy(0, 0), 1e-10);
assertRoughlyEquals(0, goog.math.angleDy(90, 0), 1e-10);
assertRoughlyEquals(0, goog.math.angleDy(0, 100), 1e-10);
assertRoughlyEquals(100, goog.math.angleDy(90, 100), 1e-10);
assertRoughlyEquals(0, goog.math.angleDy(180, 100), 1e-10);
assertRoughlyEquals(-100, goog.math.angleDy(270, 100), 1e-10);
}
function testAngle() {
assertRoughlyEquals(0, goog.math.angle(10, 10, 20, 10), 1e-10);
assertRoughlyEquals(90, goog.math.angle(10, 10, 10, 20), 1e-10);
assertRoughlyEquals(225, goog.math.angle(10, 10, 0, 0), 1e-10);
assertRoughlyEquals(270, goog.math.angle(10, 10, 10, 0), 1e-10);
// 0 is the conventional result, but mathematically this is undefined.
assertEquals(0, goog.math.angle(10, 10, 10, 10));
}
function testAngleDifference() {
assertEquals(10, goog.math.angleDifference(30, 40));
assertEquals(-10, goog.math.angleDifference(40, 30));
assertEquals(180, goog.math.angleDifference(10, 190));
assertEquals(180, goog.math.angleDifference(190, 10));
assertEquals(20, goog.math.angleDifference(350, 10));
assertEquals(-20, goog.math.angleDifference(10, 350));
assertEquals(100, goog.math.angleDifference(350, 90));
assertEquals(-80, goog.math.angleDifference(350, 270));
assertEquals(0, goog.math.angleDifference(15, 15));
}
function testSign() {
assertEquals(-1, goog.math.sign(-1));
assertEquals(1, goog.math.sign(1));
assertEquals(0, goog.math.sign(0));
assertEquals(0, goog.math.sign(-0));
assertEquals(1, goog.math.sign(0.0001));
assertEquals(-1, goog.math.sign(-0.0001));
assertEquals(-1, goog.math.sign(-Infinity));
assertEquals(1, goog.math.sign(Infinity));
assertEquals(1, goog.math.sign(3141592653589793));
}
function testLongestCommonSubsequence() {
var func = goog.math.longestCommonSubsequence;
assertArrayEquals([2], func([1, 2], [2, 1]));
assertArrayEquals([1, 2], func([1, 2, 5], [2, 1, 2]));
assertArrayEquals([1, 2, 3, 4, 5],
func([1, 0, 2, 3, 8, 4, 9, 5], [8, 1, 2, 4, 3, 6, 4, 5]));
assertArrayEquals([1, 1, 1, 1, 1], func([1, 1, 1, 1, 1], [1, 1, 1, 1, 1]));
assertArrayEquals([5], func([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]));
assertArrayEquals([1, 8, 11],
func([1, 6, 8, 11, 13], [1, 3, 5, 8, 9, 11, 12]));
}
function testLongestCommonSubsequenceWithCustomComparator() {
var func = goog.math.longestCommonSubsequence;
var compareFn = function(a, b) {
return a.field == b.field;
};
var a1 = {field: 'a1', field2: 'hello'};
var a2 = {field: 'a2', field2: 33};
var a3 = {field: 'a3'};
var a4 = {field: 'a3'};
assertArrayEquals([a1, a2], func([a1, a2, a3], [a3, a1, a2], compareFn));
assertArrayEquals([a1, a3], func([a1, a3], [a1, a4], compareFn));
// testing the same arrays without compare function
assertArrayEquals([a1], func([a1, a3], [a1, a4]));
}
function testLongestCommonSubsequenceWithCustomCollector() {
var func = goog.math.longestCommonSubsequence;
var collectorFn = function(a, b) {
return b;
};
assertArrayEquals([1, 2, 4, 6, 7],
func([1, 0, 2, 3, 8, 4, 9, 5], [8, 1, 2, 4, 3, 6, 4, 5],
null, collectorFn));
}
function testSum() {
assertEquals('sum() must return 0 if there are no arguments',
0, goog.math.sum());
assertEquals('sum() must return its argument if there is only one',
17, goog.math.sum(17));
assertEquals('sum() must handle positive integers',
10, goog.math.sum(1, 2, 3, 4));
assertEquals('sum() must handle real numbers',
-2.5, goog.math.sum(1, -2, 3, -4.5));
assertTrue('sum() must return NaN if one of the arguments isn\'t numeric',
isNaN(goog.math.sum(1, 2, 'foo', 3)));
}
function testAverage() {
assertTrue('average() must return NaN if there are no arguments',
isNaN(goog.math.average()));
assertEquals('average() must return its argument if there is only one',
17, goog.math.average(17));
assertEquals('average() must handle positive integers',
3, goog.math.average(1, 2, 3, 4, 5));
assertEquals('average() must handle real numbers',
-0.625, goog.math.average(1, -2, 3, -4.5));
assertTrue('average() must return NaN if one of the arguments isn\'t ' +
'numeric', isNaN(goog.math.average(1, 2, 'foo', 3)));
}
function testSampleVariance() {
assertEquals('sampleVariance() must return 0 if there are no samples',
0, goog.math.sampleVariance());
assertEquals('sampleVariance() must return 0 if there is only one ' +
'sample', 0, goog.math.sampleVariance(17));
assertRoughlyEquals('sampleVariance() must handle positive integers',
48, goog.math.sampleVariance(3, 7, 7, 19),
0.0001);
assertRoughlyEquals('sampleVariance() must handle real numbers',
12.0138, goog.math.sampleVariance(1.23, -2.34, 3.14, -4.56),
0.0001);
}
function testStandardDeviation() {
assertEquals('standardDeviation() must return 0 if there are no samples',
0, goog.math.standardDeviation());
assertEquals('standardDeviation() must return 0 if there is only one ' +
'sample', 0, goog.math.standardDeviation(17));
assertRoughlyEquals('standardDeviation() must handle positive integers',
6.9282, goog.math.standardDeviation(3, 7, 7, 19),
0.0001);
assertRoughlyEquals('standardDeviation() must handle real numbers',
3.4660, goog.math.standardDeviation(1.23, -2.34, 3.14, -4.56),
0.0001);
}
function testIsInt() {
assertFalse(goog.math.isInt(12345.67));
assertFalse(goog.math.isInt(0.123));
assertFalse(goog.math.isInt(.1));
assertFalse(goog.math.isInt(-23.43));
assertFalse(goog.math.isInt(-.1));
assertFalse(goog.math.isInt(1e-1));
assertTrue(goog.math.isInt(1));
assertTrue(goog.math.isInt(0));
assertTrue(goog.math.isInt(-2));
assertTrue(goog.math.isInt(-2.0));
assertTrue(goog.math.isInt(10324231));
assertTrue(goog.math.isInt(1.));
assertTrue(goog.math.isInt(1e3));
}
function testIsFiniteNumber() {
assertFalse(goog.math.isFiniteNumber(NaN));
assertFalse(goog.math.isFiniteNumber(-Infinity));
assertFalse(goog.math.isFiniteNumber(+Infinity));
assertTrue(goog.math.isFiniteNumber(0));
assertTrue(goog.math.isFiniteNumber(1));
assertTrue(goog.math.isFiniteNumber(Math.PI));
}
function testLog10Floor() {
// The greatest floating point number that is less than 1.
var oneMinusEpsilon = 1 - Math.pow(2, -53);
for (var i = -30; i <= 30; i++) {
assertEquals(i, goog.math.log10Floor(parseFloat('1e' + i)));
assertEquals(i - 1,
goog.math.log10Floor(parseFloat('1e' + i) * oneMinusEpsilon));
}
assertEquals(-Infinity, goog.math.log10Floor(0));
assertTrue(isNaN(goog.math.log10Floor(-1)));
}
function testSafeFloor() {
assertEquals(0, goog.math.safeFloor(0));
assertEquals(0, goog.math.safeFloor(1e-15));
assertEquals(0, goog.math.safeFloor(-1e-15));
assertEquals(-1, goog.math.safeFloor(-3e-15));
assertEquals(4, goog.math.safeFloor(5 - 3e-15));
assertEquals(5, goog.math.safeFloor(5 - 1e-15));
assertEquals(-5, goog.math.safeFloor(-5 - 1e-15));
assertEquals(-6, goog.math.safeFloor(-5 - 3e-15));
assertEquals(3, goog.math.safeFloor(2.91, 0.1));
assertEquals(2, goog.math.safeFloor(2.89, 0.1));
// Tests some real life examples with the default epsilon value.
assertEquals(0, goog.math.safeFloor(Math.log(1000) / Math.LN10 - 3));
assertEquals(21, goog.math.safeFloor(Math.log(1e+21) / Math.LN10));
}
function testSafeCeil() {
assertEquals(0, goog.math.safeCeil(0));
assertEquals(0, goog.math.safeCeil(1e-15));
assertEquals(0, goog.math.safeCeil(-1e-15));
assertEquals(1, goog.math.safeCeil(3e-15));
assertEquals(6, goog.math.safeCeil(5 + 3e-15));
assertEquals(5, goog.math.safeCeil(5 + 1e-15));
assertEquals(-4, goog.math.safeCeil(-5 + 3e-15));
assertEquals(-5, goog.math.safeCeil(-5 + 1e-15));
assertEquals(3, goog.math.safeCeil(3.09, 0.1));
assertEquals(4, goog.math.safeCeil(3.11, 0.1));
}
@@ -0,0 +1,681 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Class for representing matrices and static helper functions.
*/
goog.provide('goog.math.Matrix');
goog.require('goog.array');
goog.require('goog.math');
goog.require('goog.math.Size');
goog.require('goog.string');
/**
* Class for representing and manipulating matrices.
*
* The entry that lies in the i-th row and the j-th column of a matrix is
* typically referred to as the i,j entry of the matrix.
*
* The m-by-n matrix A would have its entries referred to as:
* [ a0,0 a0,1 a0,2 ... a0,j ... a0,n ]
* [ a1,0 a1,1 a1,2 ... a1,j ... a1,n ]
* [ a2,0 a2,1 a2,2 ... a2,j ... a2,n ]
* [ . . . . . ]
* [ . . . . . ]
* [ . . . . . ]
* [ ai,0 ai,1 ai,2 ... ai,j ... ai,n ]
* [ . . . . . ]
* [ . . . . . ]
* [ . . . . . ]
* [ am,0 am,1 am,2 ... am,j ... am,n ]
*
* @param {!goog.math.Matrix|!Array<!Array<number>>|!goog.math.Size|number} m
* A matrix to copy, a 2D-array to take as a template, a size object for
* dimensions, or the number of rows.
* @param {number=} opt_n Number of columns of the matrix (only applicable if
* the first argument is also numeric).
* @struct
* @constructor
* @final
*/
goog.math.Matrix = function(m, opt_n) {
if (m instanceof goog.math.Matrix) {
this.array_ = m.toArray();
} else if (goog.isArrayLike(m) &&
goog.math.Matrix.isValidArray(
/** @type {!Array<!Array<number>>} */ (m))) {
this.array_ = goog.array.clone(/** @type {!Array<!Array<number>>} */ (m));
} else if (m instanceof goog.math.Size) {
this.array_ = goog.math.Matrix.createZeroPaddedArray_(m.height, m.width);
} else if (goog.isNumber(m) && goog.isNumber(opt_n) && m > 0 && opt_n > 0) {
this.array_ = goog.math.Matrix.createZeroPaddedArray_(
/** @type {number} */ (m), opt_n);
} else {
throw Error('Invalid argument(s) for Matrix contructor');
}
this.size_ = new goog.math.Size(this.array_[0].length, this.array_.length);
};
/**
* Creates a square identity matrix. i.e. for n = 3:
* <pre>
* [ 1 0 0 ]
* [ 0 1 0 ]
* [ 0 0 1 ]
* </pre>
* @param {number} n The size of the square identity matrix.
* @return {!goog.math.Matrix} Identity matrix of width and height {@code n}.
*/
goog.math.Matrix.createIdentityMatrix = function(n) {
var rv = [];
for (var i = 0; i < n; i++) {
rv[i] = [];
for (var j = 0; j < n; j++) {
rv[i][j] = i == j ? 1 : 0;
}
}
return new goog.math.Matrix(rv);
};
/**
* Calls a function for each cell in a matrix.
* @param {goog.math.Matrix} matrix The matrix to iterate over.
* @param {function(this:T, number, number, number, !goog.math.Matrix)} fn
* The function to call for every element. This function
* takes 4 arguments (value, i, j, and the matrix)
* and the return value is irrelevant.
* @param {T=} opt_obj The object to be used as the value of 'this'
* within {@code fn}.
* @template T
*/
goog.math.Matrix.forEach = function(matrix, fn, opt_obj) {
for (var i = 0; i < matrix.getSize().height; i++) {
for (var j = 0; j < matrix.getSize().width; j++) {
fn.call(opt_obj, matrix.array_[i][j], i, j, matrix);
}
}
};
/**
* Tests whether an array is a valid matrix. A valid array is an array of
* arrays where all arrays are of the same length and all elements are numbers.
* @param {!Array<!Array<number>>} arr An array to test.
* @return {boolean} Whether the array is a valid matrix.
*/
goog.math.Matrix.isValidArray = function(arr) {
var len = 0;
for (var i = 0; i < arr.length; i++) {
if (!goog.isArrayLike(arr[i]) || len > 0 && arr[i].length != len) {
return false;
}
for (var j = 0; j < arr[i].length; j++) {
if (!goog.isNumber(arr[i][j])) {
return false;
}
}
if (len == 0) {
len = arr[i].length;
}
}
return len != 0;
};
/**
* Calls a function for every cell in a matrix and inserts the result into a
* new matrix of equal dimensions.
* @param {!goog.math.Matrix} matrix The matrix to iterate over.
* @param {function(this:T, number, number, number, !goog.math.Matrix): number}
* fn The function to call for every element. This function
* takes 4 arguments (value, i, j and the matrix)
* and should return a number, which will be inserted into a new matrix.
* @param {T=} opt_obj The object to be used as the value of 'this'
* within {@code fn}.
* @return {!goog.math.Matrix} A new matrix with the results from {@code fn}.
* @template T
*/
goog.math.Matrix.map = function(matrix, fn, opt_obj) {
var m = new goog.math.Matrix(matrix.getSize());
goog.math.Matrix.forEach(matrix, function(value, i, j) {
m.array_[i][j] = fn.call(opt_obj, value, i, j, matrix);
});
return m;
};
/**
* Creates a new zero padded matix.
* @param {number} m Height of matrix.
* @param {number} n Width of matrix.
* @return {!Array<!Array<number>>} The new zero padded matrix.
* @private
*/
goog.math.Matrix.createZeroPaddedArray_ = function(m, n) {
var rv = [];
for (var i = 0; i < m; i++) {
rv[i] = [];
for (var j = 0; j < n; j++) {
rv[i][j] = 0;
}
}
return rv;
};
/**
* Internal array representing the matrix.
* @type {!Array<!Array<number>>}
* @private
*/
goog.math.Matrix.prototype.array_;
/**
* After construction the Matrix's size is constant and stored in this object.
* @type {!goog.math.Size}
* @private
*/
goog.math.Matrix.prototype.size_;
/**
* Returns a new matrix that is the sum of this and the provided matrix.
* @param {goog.math.Matrix} m The matrix to add to this one.
* @return {!goog.math.Matrix} Resultant sum.
*/
goog.math.Matrix.prototype.add = function(m) {
if (!goog.math.Size.equals(this.size_, m.getSize())) {
throw Error('Matrix summation is only supported on arrays of equal size');
}
return goog.math.Matrix.map(this, function(val, i, j) {
return val + m.array_[i][j];
});
};
/**
* Appends the given matrix to the right side of this matrix.
* @param {goog.math.Matrix} m The matrix to augment this matrix with.
* @return {!goog.math.Matrix} A new matrix with additional columns on the
* right.
*/
goog.math.Matrix.prototype.appendColumns = function(m) {
if (this.size_.height != m.getSize().height) {
throw Error('The given matrix has height ' + m.size_.height + ', but ' +
' needs to have height ' + this.size_.height + '.');
}
var result = new goog.math.Matrix(this.size_.height,
this.size_.width + m.size_.width);
goog.math.Matrix.forEach(this, function(value, i, j) {
result.array_[i][j] = value;
});
goog.math.Matrix.forEach(m, function(value, i, j) {
result.array_[i][this.size_.width + j] = value;
}, this);
return result;
};
/**
* Appends the given matrix to the bottom of this matrix.
* @param {goog.math.Matrix} m The matrix to augment this matrix with.
* @return {!goog.math.Matrix} A new matrix with added columns on the bottom.
*/
goog.math.Matrix.prototype.appendRows = function(m) {
if (this.size_.width != m.getSize().width) {
throw Error('The given matrix has width ' + m.size_.width + ', but ' +
' needs to have width ' + this.size_.width + '.');
}
var result = new goog.math.Matrix(this.size_.height + m.size_.height,
this.size_.width);
goog.math.Matrix.forEach(this, function(value, i, j) {
result.array_[i][j] = value;
});
goog.math.Matrix.forEach(m, function(value, i, j) {
result.array_[this.size_.height + i][j] = value;
}, this);
return result;
};
/**
* Returns whether the given matrix equals this matrix.
* @param {goog.math.Matrix} m The matrix to compare to this one.
* @param {number=} opt_tolerance The tolerance when comparing array entries.
* @return {boolean} Whether the given matrix equals this matrix.
*/
goog.math.Matrix.prototype.equals = function(m, opt_tolerance) {
if (this.size_.width != m.size_.width) {
return false;
}
if (this.size_.height != m.size_.height) {
return false;
}
var tolerance = opt_tolerance || 0;
for (var i = 0; i < this.size_.height; i++) {
for (var j = 0; j < this.size_.width; j++) {
if (!goog.math.nearlyEquals(this.array_[i][j], m.array_[i][j],
tolerance)) {
return false;
}
}
}
return true;
};
/**
* Returns the determinant of this matrix. The determinant of a matrix A is
* often denoted as |A| and can only be applied to a square matrix.
* @return {number} The determinant of this matrix.
*/
goog.math.Matrix.prototype.getDeterminant = function() {
if (!this.isSquare()) {
throw Error('A determinant can only be take on a square matrix');
}
return this.getDeterminant_();
};
/**
* Returns the inverse of this matrix if it exists or null if the matrix is
* not invertible.
* @return {goog.math.Matrix} A new matrix which is the inverse of this matrix.
*/
goog.math.Matrix.prototype.getInverse = function() {
if (!this.isSquare()) {
throw Error('An inverse can only be taken on a square matrix.');
}
if (this.getSize().width == 1) {
var a = this.getValueAt(0, 0);
return a == 0 ? null : new goog.math.Matrix([[1 / a]]);
}
var identity = goog.math.Matrix.createIdentityMatrix(this.size_.height);
var mi = this.appendColumns(identity).getReducedRowEchelonForm();
var i = mi.getSubmatrixByCoordinates_(
0, 0, identity.size_.width - 1, identity.size_.height - 1);
if (!i.equals(identity)) {
return null; // This matrix was not invertible
}
return mi.getSubmatrixByCoordinates_(0, identity.size_.width);
};
/**
* Transforms this matrix into reduced row echelon form.
* @return {!goog.math.Matrix} A new matrix reduced row echelon form.
*/
goog.math.Matrix.prototype.getReducedRowEchelonForm = function() {
var result = new goog.math.Matrix(this);
var col = 0;
// Each iteration puts one row in reduced row echelon form
for (var row = 0; row < result.size_.height; row++) {
if (col >= result.size_.width) {
return result;
}
// Scan each column starting from this row on down for a non-zero value
var i = row;
while (result.array_[i][col] == 0) {
i++;
if (i == result.size_.height) {
i = row;
col++;
if (col == result.size_.width) {
return result;
}
}
}
// Make the row we found the current row with a leading 1
this.swapRows_(i, row);
var divisor = result.array_[row][col];
for (var j = col; j < result.size_.width; j++) {
result.array_[row][j] = result.array_[row][j] / divisor;
}
// Subtract a multiple of this row from each other row
// so that all the other entries in this column are 0
for (i = 0; i < result.size_.height; i++) {
if (i != row) {
var multiple = result.array_[i][col];
for (var j = col; j < result.size_.width; j++) {
result.array_[i][j] -= multiple * result.array_[row][j];
}
}
}
// Move on to the next column
col++;
}
return result;
};
/**
* @return {!goog.math.Size} The dimensions of the matrix.
*/
goog.math.Matrix.prototype.getSize = function() {
return this.size_;
};
/**
* Return the transpose of this matrix. For an m-by-n matrix, the transpose
* is the n-by-m matrix which results from turning rows into columns and columns
* into rows
* @return {!goog.math.Matrix} A new matrix A^T.
*/
goog.math.Matrix.prototype.getTranspose = function() {
var m = new goog.math.Matrix(this.size_.width, this.size_.height);
goog.math.Matrix.forEach(this, function(value, i, j) {
m.array_[j][i] = value;
});
return m;
};
/**
* Retrieves the value of a particular coordinate in the matrix or null if the
* requested coordinates are out of range.
* @param {number} i The i index of the coordinate.
* @param {number} j The j index of the coordinate.
* @return {?number} The value at the specified coordinate.
*/
goog.math.Matrix.prototype.getValueAt = function(i, j) {
if (!this.isInBounds_(i, j)) {
return null;
}
return this.array_[i][j];
};
/**
* @return {boolean} Whether the horizontal and vertical dimensions of this
* matrix are the same.
*/
goog.math.Matrix.prototype.isSquare = function() {
return this.size_.width == this.size_.height;
};
/**
* Sets the value at a particular coordinate (if the coordinate is within the
* bounds of the matrix).
* @param {number} i The i index of the coordinate.
* @param {number} j The j index of the coordinate.
* @param {number} value The new value for the coordinate.
*/
goog.math.Matrix.prototype.setValueAt = function(i, j, value) {
if (!this.isInBounds_(i, j)) {
throw Error(
'Index out of bounds when setting matrix value, (' + i + ',' + j +
') in size (' + this.size_.height + ',' + this.size_.width + ')');
}
this.array_[i][j] = value;
};
/**
* Performs matrix or scalar multiplication on a matrix and returns the
* resultant matrix.
*
* Matrix multiplication is defined between two matrices only if the number of
* columns of the first matrix is the same as the number of rows of the second
* matrix. If A is an m-by-n matrix and B is an n-by-p matrix, then their
* product AB is an m-by-p matrix
*
* Scalar multiplication returns a matrix of the same size as the original,
* each value multiplied by the given value.
*
* @param {goog.math.Matrix|number} m Matrix/number to multiply the matrix by.
* @return {!goog.math.Matrix} Resultant product.
*/
goog.math.Matrix.prototype.multiply = function(m) {
if (m instanceof goog.math.Matrix) {
if (this.size_.width != m.getSize().height) {
throw Error('Invalid matrices for multiplication. Second matrix ' +
'should have the same number of rows as the first has columns.');
}
return this.matrixMultiply_(/** @type {!goog.math.Matrix} */ (m));
} else if (goog.isNumber(m)) {
return this.scalarMultiply_(/** @type {number} */ (m));
} else {
throw Error('A matrix can only be multiplied by' +
' a number or another matrix.');
}
};
/**
* Returns a new matrix that is the difference of this and the provided matrix.
* @param {goog.math.Matrix} m The matrix to subtract from this one.
* @return {!goog.math.Matrix} Resultant difference.
*/
goog.math.Matrix.prototype.subtract = function(m) {
if (!goog.math.Size.equals(this.size_, m.getSize())) {
throw Error(
'Matrix subtraction is only supported on arrays of equal size.');
}
return goog.math.Matrix.map(this, function(val, i, j) {
return val - m.array_[i][j];
});
};
/**
* @return {!Array<!Array<number>>} A 2D internal array representing this
* matrix. Not a clone.
*/
goog.math.Matrix.prototype.toArray = function() {
return this.array_;
};
if (goog.DEBUG) {
/**
* Returns a string representation of the matrix. e.g.
* <pre>
* [ 12 5 9 1 ]
* [ 4 16 0 17 ]
* [ 12 5 1 23 ]
* </pre>
*
* @return {string} A string representation of this matrix.
* @override
*/
goog.math.Matrix.prototype.toString = function() {
// Calculate correct padding for optimum display of matrix
var maxLen = 0;
goog.math.Matrix.forEach(this, function(val) {
var len = String(val).length;
if (len > maxLen) {
maxLen = len;
}
});
// Build the string
var sb = [];
goog.array.forEach(this.array_, function(row, x) {
sb.push('[ ');
goog.array.forEach(row, function(val, y) {
var strval = String(val);
sb.push(goog.string.repeat(' ', maxLen - strval.length) + strval + ' ');
});
sb.push(']\n');
});
return sb.join('');
};
}
/**
* Returns the signed minor.
* @param {number} i The row index.
* @param {number} j The column index.
* @return {number} The cofactor C[i,j] of this matrix.
* @private
*/
goog.math.Matrix.prototype.getCofactor_ = function(i, j) {
return (i + j % 2 == 0 ? 1 : -1) * this.getMinor_(i, j);
};
/**
* Returns the determinant of this matrix. The determinant of a matrix A is
* often denoted as |A| and can only be applied to a square matrix. Same as
* public method but without validation. Implemented using Laplace's formula.
* @return {number} The determinant of this matrix.
* @private
*/
goog.math.Matrix.prototype.getDeterminant_ = function() {
if (this.getSize().area() == 1) {
return this.array_[0][0];
}
// We might want to use matrix decomposition to improve running time
// For now we'll do a Laplace expansion along the first row
var determinant = 0;
for (var j = 0; j < this.size_.width; j++) {
determinant += (this.array_[0][j] * this.getCofactor_(0, j));
}
return determinant;
};
/**
* Returns the determinant of the submatrix resulting from the deletion of row i
* and column j.
* @param {number} i The row to delete.
* @param {number} j The column to delete.
* @return {number} The first minor M[i,j] of this matrix.
* @private
*/
goog.math.Matrix.prototype.getMinor_ = function(i, j) {
return this.getSubmatrixByDeletion_(i, j).getDeterminant_();
};
/**
* Returns a submatrix contained within this matrix.
* @param {number} i1 The upper row index.
* @param {number} j1 The left column index.
* @param {number=} opt_i2 The lower row index.
* @param {number=} opt_j2 The right column index.
* @return {!goog.math.Matrix} The submatrix contained within the given bounds.
* @private
*/
goog.math.Matrix.prototype.getSubmatrixByCoordinates_ =
function(i1, j1, opt_i2, opt_j2) {
var i2 = opt_i2 ? opt_i2 : this.size_.height - 1;
var j2 = opt_j2 ? opt_j2 : this.size_.width - 1;
var result = new goog.math.Matrix(i2 - i1 + 1, j2 - j1 + 1);
goog.math.Matrix.forEach(result, function(value, i, j) {
result.array_[i][j] = this.array_[i1 + i][j1 + j];
}, this);
return result;
};
/**
* Returns a new matrix equal to this one, but with row i and column j deleted.
* @param {number} i The row index of the coordinate.
* @param {number} j The column index of the coordinate.
* @return {!goog.math.Matrix} The value at the specified coordinate.
* @private
*/
goog.math.Matrix.prototype.getSubmatrixByDeletion_ = function(i, j) {
var m = new goog.math.Matrix(this.size_.width - 1, this.size_.height - 1);
goog.math.Matrix.forEach(m, function(value, x, y) {
m.setValueAt(x, y, this.array_[x >= i ? x + 1 : x][y >= j ? y + 1 : y]);
}, this);
return m;
};
/**
* Returns whether the given coordinates are contained within the bounds of the
* matrix.
* @param {number} i The i index of the coordinate.
* @param {number} j The j index of the coordinate.
* @return {boolean} The value at the specified coordinate.
* @private
*/
goog.math.Matrix.prototype.isInBounds_ = function(i, j) {
return i >= 0 && i < this.size_.height &&
j >= 0 && j < this.size_.width;
};
/**
* Matrix multiplication is defined between two matrices only if the number of
* columns of the first matrix is the same as the number of rows of the second
* matrix. If A is an m-by-n matrix and B is an n-by-p matrix, then their
* product AB is an m-by-p matrix
*
* @param {goog.math.Matrix} m Matrix to multiply the matrix by.
* @return {!goog.math.Matrix} Resultant product.
* @private
*/
goog.math.Matrix.prototype.matrixMultiply_ = function(m) {
var resultMatrix = new goog.math.Matrix(this.size_.height, m.getSize().width);
goog.math.Matrix.forEach(resultMatrix, function(val, x, y) {
var newVal = 0;
for (var i = 0; i < this.size_.width; i++) {
newVal += this.getValueAt(x, i) * m.getValueAt(i, y);
}
resultMatrix.setValueAt(x, y, newVal);
}, this);
return resultMatrix;
};
/**
* Scalar multiplication returns a matrix of the same size as the original,
* each value multiplied by the given value.
*
* @param {number} m number to multiply the matrix by.
* @return {!goog.math.Matrix} Resultant product.
* @private
*/
goog.math.Matrix.prototype.scalarMultiply_ = function(m) {
return goog.math.Matrix.map(this, function(val, x, y) {
return val * m;
});
};
/**
* Swaps two rows.
* @param {number} i1 The index of the first row to swap.
* @param {number} i2 The index of the second row to swap.
* @private
*/
goog.math.Matrix.prototype.swapRows_ = function(i1, i2) {
var tmp = this.array_[i1];
this.array_[i1] = this.array_[i2];
this.array_[i2] = tmp;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2007 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Matrix
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.MatrixTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,429 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.MatrixTest');
goog.setTestOnly('goog.math.MatrixTest');
goog.require('goog.math.Matrix');
goog.require('goog.testing.jsunit');
function testConstuctorWithGoodArray() {
var a1 = [[1, 2], [2, 3], [4, 5]];
var m1 = new goog.math.Matrix(a1);
assertArrayEquals('1. Internal array should be the same', m1.toArray(), a1);
assertEquals(3, m1.getSize().height);
assertEquals(2, m1.getSize().width);
var a2 = [[-61, 45, 123], [11112, 343, 1235]];
var m2 = new goog.math.Matrix(a2);
assertArrayEquals('2. Internal array should be the same', m2.toArray(), a2);
assertEquals(2, m2.getSize().height);
assertEquals(3, m2.getSize().width);
var a3 = [[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
var m3 = new goog.math.Matrix(a3);
assertArrayEquals('3. Internal array should be the same', m3.toArray(), a3);
assertEquals(4, m3.getSize().height);
assertEquals(4, m3.getSize().width);
}
function testConstructorWithBadArray() {
assertThrows('1. All arrays should be of equal length', function() {
new goog.math.Matrix([[1, 2, 3], [1, 2], [1]]);
});
assertThrows('2. All arrays should be of equal length', function() {
new goog.math.Matrix([[1, 2], [1, 2], [1, 2, 3, 4]]);
});
assertThrows('3. Arrays should contain only numeric values', function() {
new goog.math.Matrix([[1, 2], [1, 2], [1, 'a']]);
});
assertThrows('4. Arrays should contain only numeric values', function() {
new goog.math.Matrix([[1, 2], [1, 2], [1, {a: 3}]]);
});
assertThrows('5. Arrays should contain only numeric values', function() {
new goog.math.Matrix([[1, 2], [1, 2], [1, [1, 2, 3]]]);
});
}
function testConstructorWithGoodNumbers() {
var m1 = new goog.math.Matrix(2, 2);
assertEquals('Height should be 2', 2, m1.getSize().height);
assertEquals('Width should be 2', 2, m1.getSize().width);
var m2 = new goog.math.Matrix(4, 2);
assertEquals('Height should be 4', 4, m2.getSize().height);
assertEquals('Width should be 2', 2, m2.getSize().width);
var m3 = new goog.math.Matrix(4, 6);
assertEquals('Height should be 4', 4, m3.getSize().height);
assertEquals('Width should be 6', 6, m3.getSize().width);
}
function testConstructorWithBadNumbers() {
assertThrows('1. Negative argument should have errored', function() {
new goog.math.Matrix(-4, 6);
});
assertThrows('2. Negative argument should have errored', function() {
new goog.math.Matrix(4, -6);
});
assertThrows('3. Zero argument should have errored', function() {
new goog.math.Matrix(4, 0);
});
assertThrows('4. Zero argument should have errored', function() {
new goog.math.Matrix(0, 1);
});
}
function testConstructorWithMatrix() {
var a1 = [[1, 2], [2, 3], [4, 5]];
var m1 = new goog.math.Matrix(a1);
var m2 = new goog.math.Matrix(m1);
assertArrayEquals(
'Internal arrays should be the same', m1.toArray(), m2.toArray());
assertNotEquals(
'Should be different objects', goog.getUid(m1), goog.getUid(m2));
}
function testCreateIdentityMatrix() {
var m1 = goog.math.Matrix.createIdentityMatrix(3);
assertArrayEquals([[1, 0, 0], [0, 1, 0], [0, 0, 1]], m1.toArray());
var m2 = goog.math.Matrix.createIdentityMatrix(4);
assertArrayEquals(
[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]], m2.toArray());
}
function testIsValidArrayWithGoodArrays() {
var fn = goog.math.Matrix.isValidArray;
assertTrue('2x2 array should be fine', fn([[1, 2], [3, 5]]));
assertTrue('3x2 array should be fine', fn([[1, 2, 3], [3, 5, 6]]));
assertTrue(
'3x3 array should be fine', fn([[1, 2, 3], [3, 5, 6], [10, 10, 10]]));
assertTrue('[[1]] should be fine', fn([[1]]));
assertTrue('1D array should work', fn([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]));
assertTrue('Negs and decimals should be ok',
fn([[0], [-4], [-10], [1.2345], [123.53]]));
assertTrue('Hex, Es and decimals are ok', fn([[0x100, 10E-2], [1.213, 213]]));
}
function testIsValidArrayWithBadArrays() {
var fn = goog.math.Matrix.isValidArray;
assertFalse('Arrays should have same size', fn([[1, 2], [3]]));
assertFalse('Arrays should have same size 2', fn([[1, 2], [3, 4, 5]]));
assertFalse('2D arrays are ok', fn([[1, 2], [3, 4], []]));
assertFalse('Values should be numeric', fn([[1, 2], [3, 'a']]));
assertFalse('Values can not be strings', fn([['bah'], ['foo']]));
assertFalse('Flat array not supported', fn([1, 2, 3, 4, 5]));
}
function testForEach() {
var m = new goog.math.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
var count = 0, sum = 0, xs = '', ys = '';
goog.math.Matrix.forEach(m, function(val, x, y) {
count++;
sum += val;
xs += x;
ys += y;
});
assertEquals('forEach should have visited every item', 9, count);
assertEquals('forEach should have summed all values', 45, sum);
assertEquals('Xs should have been visited in order', '000111222', xs);
assertEquals('Ys should have been visited sequentially', '012012012', ys);
}
function testMap() {
var m1 = new goog.math.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
var m2 = goog.math.Matrix.map(m1, function(val, x, y) {
return val + 1;
});
assertArrayEquals([[2, 3, 4], [5, 6, 7], [8, 9, 10]], m2.toArray());
}
function testSetValueAt() {
var m = new goog.math.Matrix(3, 3);
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
m.setValueAt(x, y, 3 * x - y);
}
}
assertArrayEquals([[0, -1, -2], [3, 2, 1], [6, 5, 4]], m.toArray());
}
function testGetValueAt() {
var m = new goog.math.Matrix([[0, -1, -2], [3, 2, 1], [6, 5, 4]]);
for (var x = 0; x < 3; x++) {
for (var y = 0; y < 3; y++) {
assertEquals(
'Value at (x, y) should equal 3x - y',
3 * x - y, m.getValueAt(x, y));
}
}
assertNull('Out of bounds value should be null', m.getValueAt(-1, 2));
assertNull('Out of bounds value should be null', m.getValueAt(-1, 0));
assertNull('Out of bounds value should be null', m.getValueAt(0, 4));
}
function testSum1() {
var m1 = new goog.math.Matrix([[1, 1, 1], [2, 2, 2], [3, 3, 3]]);
var m2 = new goog.math.Matrix([[3, 3, 3], [2, 2, 2], [1, 1, 1]]);
assertArrayEquals('Sum should be all the 4s',
[[4, 4, 4], [4, 4, 4], [4, 4, 4]], m1.add(m2).toArray());
assertArrayEquals('Addition should be commutative',
m1.add(m2).toArray(), m2.add(m1).toArray());
}
function testSum2() {
var m1 = new goog.math.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
var m2 = new goog.math.Matrix([[-1, -2, -3], [-4, -5, -6], [-7, -8, -9]]);
assertArrayEquals('Sum should be all 0s',
[[0, 0, 0], [0, 0, 0], [0, 0, 0]], m1.add(m2).toArray());
assertArrayEquals('Addition should be commutative',
m1.add(m2).toArray(), m2.add(m1).toArray());
}
function testSubtract1() {
var m1 = new goog.math.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
var m2 = new goog.math.Matrix([[5, 5, 5], [5, 5, 5], [5, 5, 5]]);
assertArrayEquals([[-4, -3, -2], [-1, 0, 1], [2, 3, 4]],
m1.subtract(m2).toArray());
assertArrayEquals([[4, 3, 2], [1, 0, -1], [-2, -3, -4]],
m2.subtract(m1).toArray());
}
function testSubtract2() {
var m1 = new goog.math.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
var m2 = new goog.math.Matrix([[-1, -2, -3], [-4, -5, -6], [-7, -8, -9]]);
assertArrayEquals([[2, 4, 6], [8, 10, 12], [14, 16, 18]],
m1.subtract(m2).toArray());
assertArrayEquals([[-2, -4, -6], [-8, -10, -12], [-14, -16, -18]],
m2.subtract(m1).toArray());
}
function testScalarMultiplication() {
var m1 = new goog.math.Matrix([[1, 1, 1], [2, 2, 2], [3, 3, 3]]);
assertArrayEquals(
[[2, 2, 2], [4, 4, 4], [6, 6, 6]], m1.multiply(2).toArray());
assertArrayEquals(
[[3, 3, 3], [6, 6, 6], [9, 9, 9]], m1.multiply(3).toArray());
assertArrayEquals(
[[4, 4, 4], [8, 8, 8], [12, 12, 12]], m1.multiply(4).toArray());
var m2 = new goog.math.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
assertArrayEquals(
[[2, 4, 6], [8, 10, 12], [14, 16, 18]], m2.multiply(2).toArray());
}
function testMatrixMultiplication() {
var m1 = new goog.math.Matrix([[1, 2], [3, 4]]);
var m2 = new goog.math.Matrix([[3, 4], [5, 6]]);
// m1 * m2
assertArrayEquals([[1 * 3 + 2 * 5, 1 * 4 + 2 * 6],
[3 * 3 + 4 * 5, 3 * 4 + 4 * 6]],
m1.multiply(m2).toArray());
// m2 * m1 != m1 * m2
assertArrayEquals([[3 * 1 + 4 * 3, 3 * 2 + 4 * 4],
[5 * 1 + 6 * 3, 5 * 2 + 6 * 4]],
m2.multiply(m1).toArray());
var m3 = new goog.math.Matrix([[1, 2, 3, 4],
[5, 6, 7, 8]]);
var m4 = new goog.math.Matrix([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]]);
// m3 * m4
assertArrayEquals([[1 * 1 + 2 * 4 + 3 * 7 + 4 * 10,
1 * 2 + 2 * 5 + 3 * 8 + 4 * 11,
1 * 3 + 2 * 6 + 3 * 9 + 4 * 12],
[5 * 1 + 6 * 4 + 7 * 7 + 8 * 10,
5 * 2 + 6 * 5 + 7 * 8 + 8 * 11,
5 * 3 + 6 * 6 + 7 * 9 + 8 * 12]],
m3.multiply(m4).toArray());
assertThrows('Matrix dimensions should not line up.',
function() { m4.multiply(m3); });
}
function testMatrixMultiplicationIsAssociative() {
var A = new goog.math.Matrix([[1, 2], [3, 4]]);
var B = new goog.math.Matrix([[3, 4], [5, 6]]);
var C = new goog.math.Matrix([[2, 7], [9, 1]]);
assertArrayEquals('A(BC) == (AB)C',
A.multiply(B.multiply(C)).toArray(),
A.multiply(B).multiply(C).toArray());
}
function testMatrixMultiplicationIsDistributive() {
var A = new goog.math.Matrix([[1, 2], [3, 4]]);
var B = new goog.math.Matrix([[3, 4], [5, 6]]);
var C = new goog.math.Matrix([[2, 7], [9, 1]]);
assertArrayEquals('A(B + C) = AB + AC',
A.multiply(B.add(C)).toArray(),
A.multiply(B).add(A.multiply(C)).toArray());
assertArrayEquals('(A + B)C = AC + BC',
A.add(B).multiply(C).toArray(),
A.multiply(C).add(B.multiply(C)).toArray());
}
function testTranspose() {
var m = new goog.math.Matrix([[1, 3, 1], [0, -6, 0]]);
var t = [[1, 0], [3, -6], [1, 0]];
assertArrayEquals(t, m.getTranspose().toArray());
}
function testAppendColumns() {
var m = new goog.math.Matrix([[1, 3, 2], [2, 0, 1], [5, 2, 2]]);
var b = new goog.math.Matrix([[4], [3], [1]]);
var result = [[1, 3, 2, 4], [2, 0, 1, 3], [5, 2, 2, 1]];
assertArrayEquals(result, m.appendColumns(b).toArray());
}
function testAppendRows() {
var m = new goog.math.Matrix([[1, 3, 2], [2, 0, 1], [5, 2, 2]]);
var b = new goog.math.Matrix([[4, 3, 1]]);
var result = [[1, 3, 2], [2, 0, 1], [5, 2, 2], [4, 3, 1]];
assertArrayEquals(result, m.appendRows(b).toArray());
}
function testSubmatrixByDeletion() {
var m = new goog.math.Matrix([[1, 2, 3, 4], [5, 6, 7, 8],
[9, 10, 11, 12], [13, 14, 15, 16]]);
var arr = [[1, 2, 3], [5, 6, 7], [13, 14, 15]];
assertArrayEquals(arr, m.getSubmatrixByDeletion_(2, 3).toArray());
}
function testMinor() {
var m = new goog.math.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
assertEquals(-3, m.getMinor_(0, 0));
}
function testCofactor() {
var m = new goog.math.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
assertEquals(6, m.getCofactor_(0, 1));
}
function testDeterminantForOneByOneMatrix() {
var m = new goog.math.Matrix([[3]]);
assertEquals(3, m.getDeterminant());
}
function testDeterminant() {
var m = new goog.math.Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]);
assertEquals(0, m.getDeterminant());
}
function testGetSubmatrix() {
var m = new goog.math.Matrix([[2, -1, 0, 1, 0, 0],
[-1, 2, -1, 0, 1, 0],
[0, -1, 2, 0, 0, 1]]);
var sub1 = [[2, -1, 0], [-1, 2, -1], [0, -1, 2]];
assertArrayEquals(sub1,
m.getSubmatrixByCoordinates_(0, 0, 2, 2).toArray());
var sub2 = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
assertArrayEquals(sub2, m.getSubmatrixByCoordinates_(0, 3).toArray());
}
function testGetReducedRowEchelonForm() {
var m = new goog.math.Matrix([[2, -1, 0, 1, 0, 0],
[-1, 2, -1, 0, 1, 0],
[0, -1, 2, 0, 0, 1]]);
var expected = new goog.math.Matrix([[1, 0, 0, .75, .5, .25],
[0, 1, 0, .5, 1, .5],
[0, 0, 1, .25, .5, .75]]);
assertTrue(expected.equals(m.getReducedRowEchelonForm()));
}
function testInverse() {
var m1 = new goog.math.Matrix([[2, -1, 0],
[-1, 2, -1],
[0, -1, 2]]);
var expected1 = new goog.math.Matrix([[.75, .5, .25],
[.5, 1, .5],
[.25, .5, .75]]);
assertTrue(expected1.equals(m1.getInverse()));
var m2 = new goog.math.Matrix([[4, 8],
[7, -2]]);
var expected2 = new goog.math.Matrix([[.03125, .125],
[.10936, -.0625]]);
assertTrue(expected2.equals(m2.getInverse(), .0001));
var m3 = new goog.math.Matrix([[0, 0],
[0, 0]]);
assertNull(m3.getInverse());
var m4 = new goog.math.Matrix([[2]]);
var expected4 = new goog.math.Matrix([[.5]]);
assertTrue(expected4.equals(m4.getInverse(), .0001));
var m5 = new goog.math.Matrix([[0]]);
assertNull(m5.getInverse());
}
function testEquals() {
var a1 = new goog.math.Matrix([[1, 0, 0, .75, .5, .25],
[0, 1, 0, .5, 1, .5],
[0, 0, 1, .25, .5, .75]]);
var a2 = new goog.math.Matrix([[1, 0, 0, .75, .5, .25],
[0, 1, 0, .5, 1, .5],
[0, 0, 1, .25, .5, .75]]);
var a3 = new goog.math.Matrix([[1, 0, 0, .749, .5, .25],
[0, 1, 0, .5, 1, .5],
[0, 0, 1, .25, .5, .75]]);
assertTrue(a1.equals(a2));
assertTrue(a1.equals(a3, .01));
assertFalse(a1.equals(a3, .001));
}
@@ -0,0 +1,598 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Represents a path used with a Graphics implementation.
* @author arv@google.com (Erik Arvidsson)
*/
goog.provide('goog.math.Path');
goog.provide('goog.math.Path.Segment');
goog.require('goog.array');
goog.require('goog.math');
/**
* Creates a path object. A path is a sequence of segments and may be open or
* closed. Path uses the EVEN-ODD fill rule for determining the interior of the
* path. A path must start with a moveTo command.
*
* A "simple" path does not contain any arcs and may be transformed using
* the {@code transform} method.
*
* @struct
* @constructor
* @final
*/
goog.math.Path = function() {
/**
* The segment types that constitute this path.
* @private {!Array<goog.math.Path.Segment>}
*/
this.segments_ = [];
/**
* The number of repeated segments of the current type.
* @type {!Array<number>}
* @private
*/
this.count_ = [];
/**
* The arguments corresponding to each of the segments.
* @type {!Array<number>}
* @private
*/
this.arguments_ = [];
/**
* The coordinates of the point which closes the path (the point of the
* last moveTo command).
* @type {Array<number>?}
* @private
*/
this.closePoint_ = null;
/**
* The coordinates most recently added to the end of the path.
* @type {Array<number>?}
* @private
*/
this.currentPoint_ = null;
/**
* Flag for whether this is a simple path (contains no arc segments).
* @type {boolean}
* @private
*/
this.simple_ = true;
};
/**
* Path segment types.
* @enum {number}
*/
goog.math.Path.Segment = {
MOVETO: 0,
LINETO: 1,
CURVETO: 2,
ARCTO: 3,
CLOSE: 4
};
/**
* The number of points for each segment type.
* @type {!Array<number>}
* @private
*/
goog.math.Path.segmentArgCounts_ = (function() {
var counts = [];
counts[goog.math.Path.Segment.MOVETO] = 2;
counts[goog.math.Path.Segment.LINETO] = 2;
counts[goog.math.Path.Segment.CURVETO] = 6;
counts[goog.math.Path.Segment.ARCTO] = 6;
counts[goog.math.Path.Segment.CLOSE] = 0;
return counts;
})();
/**
* Returns an array of the segment types in this path, in the order of their
* appearance. Adjacent segments of the same type are collapsed into a single
* entry in the array. The returned array is a copy; modifications are not
* reflected in the Path object.
* @return {!Array<number>}
*/
goog.math.Path.prototype.getSegmentTypes = function() {
return this.segments_.concat();
};
/**
* Returns an array of the number of times each segment type repeats in this
* path, in order. The returned array is a copy; modifications are not reflected
* in the Path object.
* @return {!Array<number>}
*/
goog.math.Path.prototype.getSegmentCounts = function() {
return this.count_.concat();
};
/**
* Returns an array of all arguments for the segments of this path object, in
* order. The returned array is a copy; modifications are not reflected in the
* Path object.
* @return {!Array<number>}
*/
goog.math.Path.prototype.getSegmentArgs = function() {
return this.arguments_.concat();
};
/**
* Returns the number of points for a segment type.
*
* @param {number} segment The segment type.
* @return {number} The number of points.
*/
goog.math.Path.getSegmentCount = function(segment) {
return goog.math.Path.segmentArgCounts_[segment];
};
/**
* Appends another path to the end of this path.
*
* @param {!goog.math.Path} path The path to append.
* @return {!goog.math.Path} This path.
*/
goog.math.Path.prototype.appendPath = function(path) {
if (path.currentPoint_) {
Array.prototype.push.apply(this.segments_, path.segments_);
Array.prototype.push.apply(this.count_, path.count_);
Array.prototype.push.apply(this.arguments_, path.arguments_);
this.currentPoint_ = path.currentPoint_.concat();
this.closePoint_ = path.closePoint_.concat();
this.simple_ = this.simple_ && path.simple_;
}
return this;
};
/**
* Clears the path.
*
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.clear = function() {
this.segments_.length = 0;
this.count_.length = 0;
this.arguments_.length = 0;
this.closePoint_ = null;
this.currentPoint_ = null;
this.simple_ = true;
return this;
};
/**
* Adds a point to the path by moving to the specified point. Repeated moveTo
* commands are collapsed into a single moveTo.
*
* @param {number} x X coordinate of destination point.
* @param {number} y Y coordinate of destination point.
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.moveTo = function(x, y) {
if (goog.array.peek(this.segments_) == goog.math.Path.Segment.MOVETO) {
this.arguments_.length -= 2;
} else {
this.segments_.push(goog.math.Path.Segment.MOVETO);
this.count_.push(1);
}
this.arguments_.push(x, y);
this.currentPoint_ = this.closePoint_ = [x, y];
return this;
};
/**
* Adds points to the path by drawing a straight line to each point.
*
* @param {...number} var_args The coordinates of each destination point as x, y
* value pairs.
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.lineTo = function(var_args) {
return this.lineTo_(arguments);
};
/**
* Adds points to the path by drawing a straight line to each point.
*
* @param {!Array<number>} coordinates The coordinates of each
* destination point as x, y value pairs.
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.lineToFromArray = function(coordinates) {
return this.lineTo_(coordinates);
};
/**
* Adds points to the path by drawing a straight line to each point.
*
* @param {!Array<number>|Arguments} coordinates The coordinates of each
* destination point as x, y value pairs.
* @return {!goog.math.Path} The path itself.
* @private
*/
goog.math.Path.prototype.lineTo_ = function(coordinates) {
var lastSegment = goog.array.peek(this.segments_);
if (lastSegment == null) {
throw Error('Path cannot start with lineTo');
}
if (lastSegment != goog.math.Path.Segment.LINETO) {
this.segments_.push(goog.math.Path.Segment.LINETO);
this.count_.push(0);
}
for (var i = 0; i < coordinates.length; i += 2) {
var x = coordinates[i];
var y = coordinates[i + 1];
this.arguments_.push(x, y);
}
this.count_[this.count_.length - 1] += i / 2;
this.currentPoint_ = [x, y];
return this;
};
/**
* Adds points to the path by drawing cubic Bezier curves. Each curve is
* specified using 3 points (6 coordinates) - two control points and the end
* point of the curve.
*
* @param {...number} var_args The coordinates specifiying each curve in sets of
* 6 points: {@code [x1, y1]} the first control point, {@code [x2, y2]} the
* second control point and {@code [x, y]} the end point.
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.curveTo = function(var_args) {
return this.curveTo_(arguments);
};
/**
* Adds points to the path by drawing cubic Bezier curves. Each curve is
* specified using 3 points (6 coordinates) - two control points and the end
* point of the curve.
*
* @param {!Array<number>} coordinates The coordinates specifiying
* each curve in sets of 6 points: {@code [x1, y1]} the first control point,
* {@code [x2, y2]} the second control point and {@code [x, y]} the end
* point.
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.curveToFromArray = function(coordinates) {
return this.curveTo_(coordinates);
};
/**
* Adds points to the path by drawing cubic Bezier curves. Each curve is
* specified using 3 points (6 coordinates) - two control points and the end
* point of the curve.
*
* @param {!Array<number>|Arguments} coordinates The coordinates specifiying
* each curve in sets of 6 points: {@code [x1, y1]} the first control point,
* {@code [x2, y2]} the second control point and {@code [x, y]} the end
* point.
* @return {!goog.math.Path} The path itself.
* @private
*/
goog.math.Path.prototype.curveTo_ = function(coordinates) {
var lastSegment = goog.array.peek(this.segments_);
if (lastSegment == null) {
throw Error('Path cannot start with curve');
}
if (lastSegment != goog.math.Path.Segment.CURVETO) {
this.segments_.push(goog.math.Path.Segment.CURVETO);
this.count_.push(0);
}
for (var i = 0; i < coordinates.length; i += 6) {
var x = coordinates[i + 4];
var y = coordinates[i + 5];
this.arguments_.push(coordinates[i], coordinates[i + 1],
coordinates[i + 2], coordinates[i + 3], x, y);
}
this.count_[this.count_.length - 1] += i / 6;
this.currentPoint_ = [x, y];
return this;
};
/**
* Adds a path command to close the path by connecting the
* last point to the first point.
*
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.close = function() {
var lastSegment = goog.array.peek(this.segments_);
if (lastSegment == null) {
throw Error('Path cannot start with close');
}
if (lastSegment != goog.math.Path.Segment.CLOSE) {
this.segments_.push(goog.math.Path.Segment.CLOSE);
this.count_.push(1);
this.currentPoint_ = this.closePoint_;
}
return this;
};
/**
* Adds a path command to draw an arc centered at the point {@code (cx, cy)}
* with radius {@code rx} along the x-axis and {@code ry} along the y-axis from
* {@code startAngle} through {@code extent} degrees. Positive rotation is in
* the direction from positive x-axis to positive y-axis.
*
* @param {number} cx X coordinate of center of ellipse.
* @param {number} cy Y coordinate of center of ellipse.
* @param {number} rx Radius of ellipse on x axis.
* @param {number} ry Radius of ellipse on y axis.
* @param {number} fromAngle Starting angle measured in degrees from the
* positive x-axis.
* @param {number} extent The span of the arc in degrees.
* @param {boolean} connect If true, the starting point of the arc is connected
* to the current point.
* @return {!goog.math.Path} The path itself.
* @deprecated Use {@code arcTo} or {@code arcToAsCurves} instead.
*/
goog.math.Path.prototype.arc = function(cx, cy, rx, ry,
fromAngle, extent, connect) {
var startX = cx + goog.math.angleDx(fromAngle, rx);
var startY = cy + goog.math.angleDy(fromAngle, ry);
if (connect) {
if (!this.currentPoint_ || startX != this.currentPoint_[0] ||
startY != this.currentPoint_[1]) {
this.lineTo(startX, startY);
}
} else {
this.moveTo(startX, startY);
}
return this.arcTo(rx, ry, fromAngle, extent);
};
/**
* Adds a path command to draw an arc starting at the path's current point,
* with radius {@code rx} along the x-axis and {@code ry} along the y-axis from
* {@code startAngle} through {@code extent} degrees. Positive rotation is in
* the direction from positive x-axis to positive y-axis.
*
* This method makes the path non-simple.
*
* @param {number} rx Radius of ellipse on x axis.
* @param {number} ry Radius of ellipse on y axis.
* @param {number} fromAngle Starting angle measured in degrees from the
* positive x-axis.
* @param {number} extent The span of the arc in degrees.
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.arcTo = function(rx, ry, fromAngle, extent) {
var cx = this.currentPoint_[0] - goog.math.angleDx(fromAngle, rx);
var cy = this.currentPoint_[1] - goog.math.angleDy(fromAngle, ry);
var ex = cx + goog.math.angleDx(fromAngle + extent, rx);
var ey = cy + goog.math.angleDy(fromAngle + extent, ry);
this.segments_.push(goog.math.Path.Segment.ARCTO);
this.count_.push(1);
this.arguments_.push(rx, ry, fromAngle, extent, ex, ey);
this.simple_ = false;
this.currentPoint_ = [ex, ey];
return this;
};
/**
* Same as {@code arcTo}, but approximates the arc using bezier curves.
.* As a result, this method does not affect the simplified status of this path.
* The algorithm is adapted from {@code java.awt.geom.ArcIterator}.
*
* @param {number} rx Radius of ellipse on x axis.
* @param {number} ry Radius of ellipse on y axis.
* @param {number} fromAngle Starting angle measured in degrees from the
* positive x-axis.
* @param {number} extent The span of the arc in degrees.
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.arcToAsCurves = function(
rx, ry, fromAngle, extent) {
var cx = this.currentPoint_[0] - goog.math.angleDx(fromAngle, rx);
var cy = this.currentPoint_[1] - goog.math.angleDy(fromAngle, ry);
var extentRad = goog.math.toRadians(extent);
var arcSegs = Math.ceil(Math.abs(extentRad) / Math.PI * 2);
var inc = extentRad / arcSegs;
var angle = goog.math.toRadians(fromAngle);
for (var j = 0; j < arcSegs; j++) {
var relX = Math.cos(angle);
var relY = Math.sin(angle);
var z = 4 / 3 * Math.sin(inc / 2) / (1 + Math.cos(inc / 2));
var c0 = cx + (relX - z * relY) * rx;
var c1 = cy + (relY + z * relX) * ry;
angle += inc;
relX = Math.cos(angle);
relY = Math.sin(angle);
this.curveTo(c0, c1,
cx + (relX + z * relY) * rx,
cy + (relY - z * relX) * ry,
cx + relX * rx,
cy + relY * ry);
}
return this;
};
/**
* Iterates over the path calling the supplied callback once for each path
* segment. The arguments to the callback function are the segment type and
* an array of its arguments.
*
* The {@code LINETO} and {@code CURVETO} arrays can contain multiple
* segments of the same type. The number of segments is the length of the
* array divided by the segment length (2 for lines, 6 for curves).
*
* As a convenience the {@code ARCTO} segment also includes the end point as the
* last two arguments: {@code rx, ry, fromAngle, extent, x, y}.
*
* @param {function(!goog.math.Path.Segment, !Array<number>)} callback
* The function to call with each path segment.
*/
goog.math.Path.prototype.forEachSegment = function(callback) {
var points = this.arguments_;
var index = 0;
for (var i = 0, length = this.segments_.length; i < length; i++) {
var seg = this.segments_[i];
var n = goog.math.Path.segmentArgCounts_[seg] * this.count_[i];
callback(seg, points.slice(index, index + n));
index += n;
}
};
/**
* Returns the coordinates most recently added to the end of the path.
*
* @return {Array<number>?} An array containing the ending coordinates of the
* path of the form {@code [x, y]}.
*/
goog.math.Path.prototype.getCurrentPoint = function() {
return this.currentPoint_ && this.currentPoint_.concat();
};
/**
* @return {!goog.math.Path} A copy of this path.
*/
goog.math.Path.prototype.clone = function() {
var path = new goog.math.Path();
path.segments_ = this.segments_.concat();
path.count_ = this.count_.concat();
path.arguments_ = this.arguments_.concat();
path.closePoint_ = this.closePoint_ && this.closePoint_.concat();
path.currentPoint_ = this.currentPoint_ && this.currentPoint_.concat();
path.simple_ = this.simple_;
return path;
};
/**
* Returns true if this path contains no arcs. Simplified paths can be
* created using {@code createSimplifiedPath}.
*
* @return {boolean} True if the path contains no arcs.
*/
goog.math.Path.prototype.isSimple = function() {
return this.simple_;
};
/**
* A map from segment type to the path function to call to simplify a path.
* @private {!Object<goog.math.Path.Segment, function(this: goog.math.Path)>}
*/
goog.math.Path.simplifySegmentMap_ = (function() {
var map = {};
map[goog.math.Path.Segment.MOVETO] = goog.math.Path.prototype.moveTo;
map[goog.math.Path.Segment.LINETO] = goog.math.Path.prototype.lineTo;
map[goog.math.Path.Segment.CLOSE] = goog.math.Path.prototype.close;
map[goog.math.Path.Segment.CURVETO] =
goog.math.Path.prototype.curveTo;
map[goog.math.Path.Segment.ARCTO] =
goog.math.Path.prototype.arcToAsCurves;
return map;
})();
/**
* Creates a copy of the given path, replacing {@code arcTo} with
* {@code arcToAsCurves}. The resulting path is simplified and can
* be transformed.
*
* @param {!goog.math.Path} src The path to simplify.
* @return {!goog.math.Path} A new simplified path.
*/
goog.math.Path.createSimplifiedPath = function(src) {
if (src.isSimple()) {
return src.clone();
}
var path = new goog.math.Path();
src.forEachSegment(function(segment, args) {
goog.math.Path.simplifySegmentMap_[segment].apply(path, args);
});
return path;
};
// TODO(chrisn): Delete this method
/**
* Creates a transformed copy of this path. The path is simplified
* {@see #createSimplifiedPath} prior to transformation.
*
* @param {!goog.math.AffineTransform} tx The transformation to perform.
* @return {!goog.math.Path} A new, transformed path.
*/
goog.math.Path.prototype.createTransformedPath = function(tx) {
var path = goog.math.Path.createSimplifiedPath(this);
path.transform(tx);
return path;
};
/**
* Transforms the path. Only simple paths are transformable. Attempting
* to transform a non-simple path will throw an error.
*
* @param {!goog.math.AffineTransform} tx The transformation to perform.
* @return {!goog.math.Path} The path itself.
*/
goog.math.Path.prototype.transform = function(tx) {
if (!this.isSimple()) {
throw Error('Non-simple path');
}
tx.transform(this.arguments_, 0, this.arguments_, 0,
this.arguments_.length / 2);
if (this.closePoint_) {
tx.transform(this.closePoint_, 0, this.closePoint_, 0, 1);
}
if (this.currentPoint_ && this.closePoint_ != this.currentPoint_) {
tx.transform(this.currentPoint_, 0, this.currentPoint_, 0, 1);
}
return this;
};
/**
* @return {boolean} Whether the path is empty.
*/
goog.math.Path.prototype.isEmpty = function() {
return this.segments_.length == 0;
};
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Closure Unit Tests - goog.math.Path</title>
<script src="../base.js"></script>
<script>
goog.require('goog.math.PathTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,518 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.PathTest');
goog.require('goog.array');
goog.require('goog.math.AffineTransform');
goog.require('goog.math.Path');
goog.require('goog.testing.jsunit');
goog.setTestOnly('goog.math.PathTest');
/**
* Array mapping numeric segment constant to a descriptive character.
* @type {!Array<string>}
* @private
*/
var SEGMENT_NAMES_ = function() {
var arr = [];
arr[goog.math.Path.Segment.MOVETO] = 'M';
arr[goog.math.Path.Segment.LINETO] = 'L';
arr[goog.math.Path.Segment.CURVETO] = 'C';
arr[goog.math.Path.Segment.ARCTO] = 'A';
arr[goog.math.Path.Segment.CLOSE] = 'X';
return arr;
}();
/**
* Test if the given path matches the expected array of commands and parameters.
* @param {Array<string|number>} expected The expected array of commands and
* parameters.
* @param {goog.math.Path} path The path to test against.
*/
var assertPathEquals = function(expected, path) {
var actual = [];
path.forEachSegment(function(seg, args) {
actual.push(SEGMENT_NAMES_[seg]);
Array.prototype.push.apply(actual, args);
});
assertEquals(expected.length, actual.length);
for (var i = 0; i < expected.length; i++) {
if (goog.isNumber(expected[i])) {
assertTrue(goog.isNumber(actual[i]));
assertRoughlyEquals(expected[i], actual[i], 0.01);
} else {
assertEquals(expected[i], actual[i]);
}
}
};
function testConstructor() {
var path = new goog.math.Path();
assertTrue(path.isSimple());
assertNull(path.getCurrentPoint());
assertPathEquals([], path);
}
function testGetSegmentCount() {
assertArrayEquals([2, 2, 6, 6, 0], goog.array.map([
goog.math.Path.Segment.MOVETO,
goog.math.Path.Segment.LINETO,
goog.math.Path.Segment.CURVETO,
goog.math.Path.Segment.ARCTO,
goog.math.Path.Segment.CLOSE
], goog.math.Path.getSegmentCount));
}
function testSimpleMoveTo() {
var path = new goog.math.Path();
path.moveTo(30, 50);
assertTrue(path.isSimple());
assertObjectEquals([30, 50], path.getCurrentPoint());
assertPathEquals(['M', 30, 50], path);
}
function testRepeatedMoveTo() {
var path = new goog.math.Path();
path.moveTo(30, 50);
path.moveTo(40, 60);
assertTrue(path.isSimple());
assertObjectEquals([40, 60], path.getCurrentPoint());
assertPathEquals(['M', 40, 60], path);
}
function testSimpleLineTo_fromArgs() {
var path = new goog.math.Path();
var e = assertThrows(function() {
path.lineTo(30, 50);
});
assertEquals('Path cannot start with lineTo', e.message);
path.moveTo(0, 0);
path.lineTo(30, 50);
assertTrue(path.isSimple());
assertObjectEquals([30, 50], path.getCurrentPoint());
assertPathEquals(['M', 0, 0, 'L', 30, 50], path);
}
function testSimpleLineTo_fromArray() {
var path = new goog.math.Path();
var e = assertThrows(function() {
path.lineToFromArray([30, 50]);
});
assertEquals('Path cannot start with lineTo', e.message);
path.moveTo(0, 0);
path.lineToFromArray([30, 50]);
assertTrue(path.isSimple());
assertObjectEquals([30, 50], path.getCurrentPoint());
assertPathEquals(['M', 0, 0, 'L', 30, 50], path);
}
function testMultiArgLineTo_fromArgs() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.lineTo(30, 50, 40 , 60);
assertTrue(path.isSimple());
assertObjectEquals([40, 60], path.getCurrentPoint());
assertPathEquals(['M', 0, 0, 'L', 30, 50, 40, 60], path);
}
function testMultiArgLineTo_fromArray() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.lineToFromArray([30, 50, 40 , 60]);
assertTrue(path.isSimple());
assertObjectEquals([40, 60], path.getCurrentPoint());
assertPathEquals(['M', 0, 0, 'L', 30, 50, 40, 60], path);
}
function testRepeatedLineTo_fromArgs() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.lineTo(30, 50);
path.lineTo(40, 60);
assertTrue(path.isSimple());
assertObjectEquals([40, 60], path.getCurrentPoint());
assertPathEquals(['M', 0, 0, 'L', 30, 50, 40, 60], path);
}
function testRepeatedLineTo_fromArray() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.lineToFromArray([30, 50]);
path.lineToFromArray([40, 60]);
assertTrue(path.isSimple());
assertObjectEquals([40, 60], path.getCurrentPoint());
assertPathEquals(['M', 0, 0, 'L', 30, 50, 40, 60], path);
}
function testSimpleCurveTo_fromArgs() {
var path = new goog.math.Path();
var e = assertThrows(function() {
path.curveTo(10, 20, 30, 40, 50, 60);
});
assertEquals('Path cannot start with curve', e.message);
path.moveTo(0, 0);
path.curveTo(10, 20, 30, 40, 50, 60);
assertTrue(path.isSimple());
assertObjectEquals([50, 60], path.getCurrentPoint());
assertPathEquals(['M', 0, 0, 'C', 10, 20, 30, 40, 50, 60], path);
}
function testSimpleCurveTo_fromArray() {
var path = new goog.math.Path();
var e = assertThrows(function() {
path.curveToFromArray([10, 20, 30, 40, 50, 60]);
});
assertEquals('Path cannot start with curve', e.message);
path.moveTo(0, 0);
path.curveToFromArray([10, 20, 30, 40, 50, 60]);
assertTrue(path.isSimple());
assertObjectEquals([50, 60], path.getCurrentPoint());
assertPathEquals(['M', 0, 0, 'C', 10, 20, 30, 40, 50, 60], path);
}
function testMultiCurveTo_fromArgs() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.curveTo(10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120);
assertTrue(path.isSimple());
assertObjectEquals([110, 120], path.getCurrentPoint());
assertPathEquals(
['M', 0, 0, 'C', 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120],
path);
}
function testMultiCurveTo_fromArray() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.curveToFromArray([10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]);
assertTrue(path.isSimple());
assertObjectEquals([110, 120], path.getCurrentPoint());
assertPathEquals(
['M', 0, 0, 'C', 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120],
path);
}
function testRepeatedCurveTo_fromArgs() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.curveTo(10, 20, 30, 40, 50, 60);
path.curveTo(70, 80, 90, 100, 110, 120);
assertTrue(path.isSimple());
assertObjectEquals([110, 120], path.getCurrentPoint());
assertPathEquals(
['M', 0, 0, 'C', 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120],
path);
}
function testRepeatedCurveTo_fromArray() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.curveToFromArray([10, 20, 30, 40, 50, 60]);
path.curveToFromArray([70, 80, 90, 100, 110, 120]);
assertTrue(path.isSimple());
assertObjectEquals([110, 120], path.getCurrentPoint());
assertPathEquals(
['M', 0, 0, 'C', 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120],
path);
}
function testSimpleArc() {
var path = new goog.math.Path();
path.arc(50, 60, 10, 20, 30, 30, false);
assertFalse(path.isSimple());
var p = path.getCurrentPoint();
assertEquals(55, p[0]);
assertRoughlyEquals(77.32, p[1], 0.01);
assertPathEquals(
['M', 58.66, 70, 'A', 10, 20, 30, 30, 55, 77.32], path);
}
function testArcNonConnectClose() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.arc(10, 10, 10, 10, -90, 180, false);
assertObjectEquals([10, 20], path.getCurrentPoint());
path.close();
assertObjectEquals([10, 0], path.getCurrentPoint());
}
function testRepeatedArc() {
var path = new goog.math.Path();
path.arc(50, 60, 10, 20, 30, 30, false);
path.arc(50, 60, 10, 20, 60, 30, false);
assertFalse(path.isSimple());
assertObjectEquals([50, 80], path.getCurrentPoint());
assertPathEquals(['M', 58.66, 70,
'A', 10, 20, 30, 30, 55, 77.32,
'M', 55, 77.32,
'A', 10, 20, 60, 30, 50, 80], path);
}
function testRepeatedArc2() {
var path = new goog.math.Path();
path.arc(50, 60, 10, 20, 30, 30, false);
path.arc(50, 60, 10, 20, 60, 30, true);
assertPathEquals(['M', 58.66, 70,
'A', 10, 20, 30, 30, 55, 77.32,
'A', 10, 20, 60, 30, 50, 80], path);
}
function testCompleteCircle() {
var path = new goog.math.Path();
path.arc(0, 0, 10, 10, 0, 360, false);
assertFalse(path.isSimple());
var p = path.getCurrentPoint();
assertRoughlyEquals(10, p[0], 0.01);
assertRoughlyEquals(0, p[1], 0.01);
assertPathEquals(
['M', 10, 0, 'A', 10, 10, 0, 360, 10, 0], path);
}
function testClose() {
var path = new goog.math.Path();
assertThrows('Path cannot start with close',
function() {
path.close();
});
path.moveTo(0, 0);
path.lineTo(10, 20, 30, 40, 50, 60);
path.close();
assertTrue(path.isSimple());
assertObjectEquals([0, 0], path.getCurrentPoint());
assertPathEquals(
['M', 0, 0, 'L', 10, 20, 30, 40, 50, 60, 'X'], path);
}
function testClear() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.arc(50, 60, 10, 20, 30, 30, false);
path.clear();
assertTrue(path.isSimple());
assertNull(path.getCurrentPoint());
assertPathEquals([], path);
}
function testCreateSimplifiedPath() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.arc(50, 60, 10, 20, 30, 30, false);
assertFalse(path.isSimple());
path = goog.math.Path.createSimplifiedPath(path);
assertTrue(path.isSimple());
var p = path.getCurrentPoint();
assertEquals(55, p[0]);
assertRoughlyEquals(77.32, p[1], 0.01);
assertPathEquals(['M', 58.66, 70,
'C', 57.78, 73.04, 56.52, 75.57, 55, 77.32], path);
}
function testCreateSimplifiedPath2() {
var path = new goog.math.Path();
path.arc(50, 60, 10, 20, 30, 30, false);
path.arc(50, 60, 10, 20, 60, 30, false);
assertFalse(path.isSimple());
path = goog.math.Path.createSimplifiedPath(path);
assertTrue(path.isSimple());
assertPathEquals(['M', 58.66, 70,
'C', 57.78, 73.04, 56.52, 75.57, 55, 77.32,
'M', 55, 77.32,
'C', 53.48, 79.08, 51.76, 80, 50, 80], path);
}
function testCreateSimplifiedPath3() {
var path = new goog.math.Path();
path.arc(50, 60, 10, 20, 30, 30, false);
path.arc(50, 60, 10, 20, 60, 30, true);
path.close();
path = goog.math.Path.createSimplifiedPath(path);
assertPathEquals(['M', 58.66, 70,
'C', 57.78, 73.04, 56.52, 75.57, 55, 77.32,
53.48, 79.08, 51.76, 80, 50, 80, 'X'], path);
var p = path.getCurrentPoint();
assertRoughlyEquals(58.66, p[0], 0.01);
assertRoughlyEquals(70, p[1], 0.01);
}
function testArcToAsCurves() {
var path = new goog.math.Path();
path.moveTo(58.66, 70);
path.arcToAsCurves(10, 20, 30, 30);
assertPathEquals(['M', 58.66, 70,
'C', 57.78, 73.04, 56.52, 75.57, 55, 77.32], path);
}
function testCreateTransformedPath() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.lineTo(0, 10, 10, 10, 10, 0);
path.close();
var tx = new goog.math.AffineTransform(2, 0, 0, 3, 10, 20);
var path2 = path.createTransformedPath(tx);
assertPathEquals(
['M', 0, 0, 'L', 0, 10, 10, 10, 10, 0, 'X'], path);
assertPathEquals(
['M', 10, 20, 'L', 10, 50, 30, 50, 30, 20, 'X'], path2);
}
function testTransform() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.lineTo(0, 10, 10, 10, 10, 0);
path.close();
var tx = new goog.math.AffineTransform(2, 0, 0, 3, 10, 20);
var path2 = path.transform(tx);
assertTrue(path === path2);
assertPathEquals(
['M', 10, 20, 'L', 10, 50, 30, 50, 30, 20, 'X'], path2);
}
function testTransformCurrentAndClosePoints() {
var path = new goog.math.Path();
path.moveTo(0, 0);
assertObjectEquals([0, 0], path.getCurrentPoint());
path.transform(new goog.math.AffineTransform(1, 0, 0, 1, 10, 20));
assertObjectEquals([10, 20], path.getCurrentPoint());
path.lineTo(50, 50);
path.close();
assertObjectEquals([10, 20], path.getCurrentPoint());
}
function testTransformNonSimple() {
var path = new goog.math.Path();
path.arc(50, 60, 10, 20, 30, 30, false);
assertThrows(function() {
path.transform(new goog.math.AffineTransform(1, 0, 0, 1, 10, 20));
});
}
function testAppendPath() {
var path1 = new goog.math.Path();
path1.moveTo(0, 0);
path1.lineTo(0, 10, 10, 10, 10, 0);
path1.close();
var path2 = new goog.math.Path();
path2.arc(50, 60, 10, 20, 30, 30, false);
assertTrue(path1.isSimple());
path1.appendPath(path2);
assertFalse(path1.isSimple());
assertPathEquals([
'M', 0, 0, 'L', 0, 10, 10, 10, 10, 0, 'X',
'M', 58.66, 70, 'A', 10, 20, 30, 30, 55, 77.32
], path1);
}
function testIsEmpty() {
var path = new goog.math.Path();
assertTrue('Initially path is empty', path.isEmpty());
path.moveTo(0, 0);
assertFalse('After command addition, path is not empty', path.isEmpty());
path.clear();
assertTrue('After clear, path is empty again', path.isEmpty());
}
function testGetSegmentTypes() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.lineTo(10, 20, 30, 40);
path.close();
var Segment = goog.math.Path.Segment;
var segmentTypes = path.getSegmentTypes();
assertArrayEquals(
'The returned segment types do not match the expected values',
[Segment.MOVETO, Segment.LINETO, Segment.CLOSE], segmentTypes);
segmentTypes[2] = Segment.LINETO;
assertArrayEquals('Modifying the returned segment types changed the path',
[Segment.MOVETO, Segment.LINETO, Segment.CLOSE], path.getSegmentTypes());
}
function testGetSegmentCounts() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.lineTo(10, 20, 30, 40);
path.close();
var segmentTypes = path.getSegmentCounts();
assertArrayEquals(
'The returned segment counts do not match the expected values',
[1, 2, 1], segmentTypes);
segmentTypes[1] = 3;
assertArrayEquals('Modifying the returned segment counts changed the path',
[1, 2, 1], path.getSegmentCounts());
}
function testGetSegmentArgs() {
var path = new goog.math.Path();
path.moveTo(0, 0);
path.lineTo(10, 20, 30, 40);
path.close();
var segmentTypes = path.getSegmentArgs();
assertArrayEquals(
'The returned segment args do not match the expected values',
[0, 0, 10, 20, 30, 40], segmentTypes);
segmentTypes[1] = -10;
assertArrayEquals(
'Modifying the returned segment args changed the path',
[0, 0, 10, 20, 30, 40], path.getSegmentArgs());
}
@@ -0,0 +1,86 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Factories for common path types.
* @author nicksantos@google.com (Nick Santos)
*/
goog.provide('goog.math.paths');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Path');
/**
* Defines a regular n-gon by specifing the center, a vertex, and the total
* number of vertices.
* @param {goog.math.Coordinate} center The center point.
* @param {goog.math.Coordinate} vertex The vertex, which implicitly defines
* a radius as well.
* @param {number} n The number of vertices.
* @return {!goog.math.Path} The path.
*/
goog.math.paths.createRegularNGon = function(center, vertex, n) {
var path = new goog.math.Path();
path.moveTo(vertex.x, vertex.y);
var startAngle = Math.atan2(vertex.y - center.y, vertex.x - center.x);
var radius = goog.math.Coordinate.distance(center, vertex);
for (var i = 1; i < n; i++) {
var angle = startAngle + 2 * Math.PI * (i / n);
path.lineTo(center.x + radius * Math.cos(angle),
center.y + radius * Math.sin(angle));
}
path.close();
return path;
};
/**
* Defines an arrow.
* @param {goog.math.Coordinate} a Point A.
* @param {goog.math.Coordinate} b Point B.
* @param {?number} aHead The size of the arrow head at point A.
* 0 omits the head.
* @param {?number} bHead The size of the arrow head at point B.
* 0 omits the head.
* @return {!goog.math.Path} The path.
*/
goog.math.paths.createArrow = function(a, b, aHead, bHead) {
var path = new goog.math.Path();
path.moveTo(a.x, a.y);
path.lineTo(b.x, b.y);
var angle = Math.atan2(b.y - a.y, b.x - a.x);
if (aHead) {
path.appendPath(
goog.math.paths.createRegularNGon(
new goog.math.Coordinate(
a.x + aHead * Math.cos(angle),
a.y + aHead * Math.sin(angle)),
a, 3));
}
if (bHead) {
path.appendPath(
goog.math.paths.createRegularNGon(
new goog.math.Coordinate(
b.x + bHead * Math.cos(angle + Math.PI),
b.y + bHead * Math.sin(angle + Math.PI)),
b, 3));
}
return path;
};
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2010 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
Author: nicksantos@google.com (Nick Santos)
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>JsUnit tests for goog.math.paths</title>
<script src="../base.js"></script>
<script>
goog.require('goog.math.pathsTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,50 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Unit tests for goog.math.paths.
*/
goog.provide('goog.math.pathsTest');
goog.require('goog.math.Coordinate');
goog.require('goog.math.paths');
goog.require('goog.testing.jsunit');
goog.setTestOnly('goog.math.pathsTest');
var regularNGon = goog.math.paths.createRegularNGon;
var arrow = goog.math.paths.createArrow;
function testSquare() {
var square = regularNGon(
$coord(10, 10), $coord(0, 10), 4);
assertArrayRoughlyEquals(
[0, 10, 10, 0, 20, 10, 10, 20], square.arguments_, 0.05);
}
function assertArrayRoughlyEquals(expected, actual, delta) {
var message = 'Expected: ' + expected + ', Actual: ' + actual;
assertEquals('Wrong length. ' + message, expected.length, actual.length);
for (var i = 0; i < expected.length; i++) {
assertRoughlyEquals(
'Wrong item at ' + i + '. ' + message,
expected[i], actual[i], delta);
}
}
function $coord(x, y) {
return new goog.math.Coordinate(x, y);
}
@@ -0,0 +1,186 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing a numeric range.
*/
goog.provide('goog.math.Range');
goog.require('goog.asserts');
/**
* A number range.
* @param {number} a One end of the range.
* @param {number} b The other end of the range.
* @struct
* @constructor
*/
goog.math.Range = function(a, b) {
/**
* The lowest value in the range.
* @type {number}
*/
this.start = a < b ? a : b;
/**
* The highest value in the range.
* @type {number}
*/
this.end = a < b ? b : a;
};
/**
* Creates a goog.math.Range from an array of two numbers.
* @param {!Array<number>} pair
* @return {!goog.math.Range}
*/
goog.math.Range.fromPair = function(pair) {
goog.asserts.assert(pair.length == 2);
return new goog.math.Range(pair[0], pair[1]);
};
/**
* @return {!goog.math.Range} A clone of this Range.
*/
goog.math.Range.prototype.clone = function() {
return new goog.math.Range(this.start, this.end);
};
/**
* @return {number} Length of the range.
*/
goog.math.Range.prototype.getLength = function() {
return this.end - this.start;
};
/**
* Extends this range to include the given point.
* @param {number} point
*/
goog.math.Range.prototype.includePoint = function(point) {
this.start = Math.min(this.start, point);
this.end = Math.max(this.end, point);
};
/**
* Extends this range to include the given range.
* @param {!goog.math.Range} range
*/
goog.math.Range.prototype.includeRange = function(range) {
this.start = Math.min(this.start, range.start);
this.end = Math.max(this.end, range.end);
};
if (goog.DEBUG) {
/**
* Returns a string representing the range.
* @return {string} In the form [-3.5, 8.13].
* @override
*/
goog.math.Range.prototype.toString = function() {
return '[' + this.start + ', ' + this.end + ']';
};
}
/**
* Compares ranges for equality.
* @param {goog.math.Range} a A Range.
* @param {goog.math.Range} b A Range.
* @return {boolean} True iff both the starts and the ends of the ranges are
* equal, or if both ranges are null.
*/
goog.math.Range.equals = function(a, b) {
if (a == b) {
return true;
}
if (!a || !b) {
return false;
}
return a.start == b.start && a.end == b.end;
};
/**
* Given two ranges on the same dimension, this method returns the intersection
* of those ranges.
* @param {goog.math.Range} a A Range.
* @param {goog.math.Range} b A Range.
* @return {goog.math.Range} A new Range representing the intersection of two
* ranges, or null if there is no intersection. Ranges are assumed to
* include their end points, and the intersection can be a point.
*/
goog.math.Range.intersection = function(a, b) {
var c0 = Math.max(a.start, b.start);
var c1 = Math.min(a.end, b.end);
return (c0 <= c1) ? new goog.math.Range(c0, c1) : null;
};
/**
* Given two ranges on the same dimension, determines whether they intersect.
* @param {goog.math.Range} a A Range.
* @param {goog.math.Range} b A Range.
* @return {boolean} Whether they intersect.
*/
goog.math.Range.hasIntersection = function(a, b) {
return Math.max(a.start, b.start) <= Math.min(a.end, b.end);
};
/**
* Given two ranges on the same dimension, this returns a range that covers
* both ranges.
* @param {goog.math.Range} a A Range.
* @param {goog.math.Range} b A Range.
* @return {!goog.math.Range} A new Range representing the bounding
* range.
*/
goog.math.Range.boundingRange = function(a, b) {
return new goog.math.Range(Math.min(a.start, b.start),
Math.max(a.end, b.end));
};
/**
* Given two ranges, returns true if the first range completely overlaps the
* second.
* @param {goog.math.Range} a The first Range.
* @param {goog.math.Range} b The second Range.
* @return {boolean} True if b is contained inside a, false otherwise.
*/
goog.math.Range.contains = function(a, b) {
return a.start <= b.start && a.end >= b.end;
};
/**
* Given a range and a point, returns true if the range contains the point.
* @param {goog.math.Range} range The range.
* @param {number} p The point.
* @return {boolean} True if p is contained inside range, false otherwise.
*/
goog.math.Range.containsPoint = function(range, p) {
return range.start <= p && range.end >= p;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Range
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.RangeTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,142 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.RangeTest');
goog.setTestOnly('goog.math.RangeTest');
goog.require('goog.math.Range');
goog.require('goog.testing.jsunit');
/**
* Produce legible assertion results. If two ranges are not equal, the error
* message will be of the form
* "Expected <[1, 2]> (Object) but was <[3, 4]> (Object)"
*/
function assertRangesEqual(expected, actual) {
if (!goog.math.Range.equals(expected, actual)) {
assertEquals(expected, actual);
}
}
function createRange(a) {
return a ? new goog.math.Range(a[0], a[1]) : null;
}
function testFromPair() {
var range = goog.math.Range.fromPair([1, 2]);
assertEquals(1, range.start);
assertEquals(2, range.end);
range = goog.math.Range.fromPair([2, 1]);
assertEquals(1, range.start);
assertEquals(2, range.end);
}
function testRangeIntersection() {
var tests = [[[1, 2], [3, 4], null],
[[1, 3], [2, 4], [2, 3]],
[[1, 4], [2, 3], [2, 3]],
[[-1, 2], [-1, 2], [-1, 2]],
[[1, 2], [2, 3], [2, 2]],
[[1, 1], [1, 1], [1, 1]]];
for (var i = 0; i < tests.length; ++i) {
var t = tests[i];
var r0 = createRange(t[0]);
var r1 = createRange(t[1]);
var expected = createRange(t[2]);
assertRangesEqual(expected, goog.math.Range.intersection(r0, r1));
assertRangesEqual(expected, goog.math.Range.intersection(r1, r0));
assertEquals(expected != null, goog.math.Range.hasIntersection(r0, r1));
assertEquals(expected != null, goog.math.Range.hasIntersection(r1, r0));
}
}
function testBoundingRange() {
var tests = [[[1, 2], [3, 4], [1, 4]],
[[1, 3], [2, 4], [1, 4]],
[[1, 4], [2, 3], [1, 4]],
[[-1, 2], [-1, 2], [-1, 2]],
[[1, 2], [2, 3], [1, 3]],
[[1, 1], [1, 1], [1, 1]]];
for (var i = 0; i < tests.length; ++i) {
var t = tests[i];
var r0 = createRange(t[0]);
var r1 = createRange(t[1]);
var expected = createRange(t[2]);
assertRangesEqual(expected, goog.math.Range.boundingRange(r0, r1));
assertRangesEqual(expected, goog.math.Range.boundingRange(r1, r0));
}
}
function testRangeContains() {
var tests = [[[0, 4], [2, 1], true],
[[-4, -1], [-2, -3], true],
[[1, 3], [2, 4], false],
[[-1, 0], [0, 1], false],
[[0, 2], [3, 5], false]];
for (var i = 0; i < tests.length; ++i) {
var t = tests[i];
var r0 = createRange(t[0]);
var r1 = createRange(t[1]);
var expected = t[2];
assertEquals(expected, goog.math.Range.contains(r0, r1));
}
}
function testRangeClone() {
var r = new goog.math.Range(5.6, -3.4);
assertRangesEqual(r, r.clone());
}
function testGetLength() {
assertEquals(2, new goog.math.Range(1, 3).getLength());
assertEquals(2, new goog.math.Range(3, 1).getLength());
}
function testRangeContainsPoint() {
var r = new goog.math.Range(0, 1);
assert(goog.math.Range.containsPoint(r, 0));
assert(goog.math.Range.containsPoint(r, 1));
assertFalse(goog.math.Range.containsPoint(r, -1));
assertFalse(goog.math.Range.containsPoint(r, 2));
}
function testIncludePoint() {
var r = new goog.math.Range(0, 2);
r.includePoint(0);
assertObjectEquals(new goog.math.Range(0, 2), r);
r.includePoint(1);
assertObjectEquals(new goog.math.Range(0, 2), r);
r.includePoint(2);
assertObjectEquals(new goog.math.Range(0, 2), r);
r.includePoint(-1);
assertObjectEquals(new goog.math.Range(-1, 2), r);
r.includePoint(3);
assertObjectEquals(new goog.math.Range(-1, 3), r);
}
function testIncludeRange() {
var r = new goog.math.Range(0, 4);
r.includeRange(r);
assertObjectEquals(new goog.math.Range(0, 4), r);
r.includeRange(new goog.math.Range(1, 3));
assertObjectEquals(new goog.math.Range(0, 4), r);
r.includeRange(new goog.math.Range(-1, 2));
assertObjectEquals(new goog.math.Range(-1, 4), r);
r.includeRange(new goog.math.Range(2, 5));
assertObjectEquals(new goog.math.Range(-1, 5), r);
r.includeRange(new goog.math.Range(-2, 6));
assertObjectEquals(new goog.math.Range(-2, 6), r);
}
@@ -0,0 +1,396 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A RangeSet is a structure that manages a list of ranges.
* Numeric ranges may be added and removed from the RangeSet, and the set may
* be queried for the presence or absence of individual values or ranges of
* values.
*
* This may be used, for example, to track the availability of sparse elements
* in an array without iterating over the entire array.
*
* @author brenneman@google.com (Shawn Brenneman)
*/
goog.provide('goog.math.RangeSet');
goog.require('goog.array');
goog.require('goog.iter.Iterator');
goog.require('goog.iter.StopIteration');
goog.require('goog.math.Range');
/**
* Constructs a new RangeSet, which can store numeric ranges.
*
* Ranges are treated as half-closed: that is, they are exclusive of their end
* value [start, end).
*
* New ranges added to the set which overlap the values in one or more existing
* ranges will be merged.
*
* @struct
* @constructor
* @final
*/
goog.math.RangeSet = function() {
/**
* A sorted list of ranges that represent the values in the set.
* @type {!Array<!goog.math.Range>}
* @private
*/
this.ranges_ = [];
};
if (goog.DEBUG) {
/**
* @return {string} A debug string in the form [[1, 5], [8, 9], [15, 30]].
* @override
*/
goog.math.RangeSet.prototype.toString = function() {
return '[' + this.ranges_.join(', ') + ']';
};
}
/**
* Compares two sets for equality.
*
* @param {goog.math.RangeSet} a A range set.
* @param {goog.math.RangeSet} b A range set.
* @return {boolean} Whether both sets contain the same values.
*/
goog.math.RangeSet.equals = function(a, b) {
// Fast check for object equality. Also succeeds if a and b are both null.
return a == b || !!(a && b && goog.array.equals(a.ranges_, b.ranges_,
goog.math.Range.equals));
};
/**
* @return {!goog.math.RangeSet} A new RangeSet containing the same values as
* this one.
*/
goog.math.RangeSet.prototype.clone = function() {
var set = new goog.math.RangeSet();
for (var i = this.ranges_.length; i--;) {
set.ranges_[i] = this.ranges_[i].clone();
}
return set;
};
/**
* Adds a range to the set. If the new range overlaps existing values, those
* ranges will be merged.
*
* @param {goog.math.Range} a The range to add.
*/
goog.math.RangeSet.prototype.add = function(a) {
if (a.end <= a.start) {
// Empty ranges are ignored.
return;
}
a = a.clone();
// Find the insertion point.
for (var i = 0, b; b = this.ranges_[i]; i++) {
if (a.start <= b.end) {
a.start = Math.min(a.start, b.start);
break;
}
}
var insertionPoint = i;
for (; b = this.ranges_[i]; i++) {
if (a.end < b.start) {
break;
}
a.end = Math.max(a.end, b.end);
}
this.ranges_.splice(insertionPoint, i - insertionPoint, a);
};
/**
* Removes a range of values from the set.
*
* @param {goog.math.Range} a The range to remove.
*/
goog.math.RangeSet.prototype.remove = function(a) {
if (a.end <= a.start) {
// Empty ranges are ignored.
return;
}
// Find the insertion point.
for (var i = 0, b; b = this.ranges_[i]; i++) {
if (a.start < b.end) {
break;
}
}
if (!b || a.end < b.start) {
// The range being removed doesn't overlap any existing range. Exit early.
return;
}
var insertionPoint = i;
if (a.start > b.start) {
// There is an overlap with the nearest range. Modify it accordingly.
insertionPoint++;
if (a.end < b.end) {
goog.array.insertAt(this.ranges_,
new goog.math.Range(a.end, b.end),
insertionPoint);
}
b.end = a.start;
}
for (i = insertionPoint; b = this.ranges_[i]; i++) {
b.start = Math.max(a.end, b.start);
if (a.end < b.end) {
break;
}
}
this.ranges_.splice(insertionPoint, i - insertionPoint);
};
/**
* Determines whether a given range is in the set. Only succeeds if the entire
* range is available.
*
* @param {goog.math.Range} a The query range.
* @return {boolean} Whether the entire requested range is set.
*/
goog.math.RangeSet.prototype.contains = function(a) {
if (a.end <= a.start) {
return false;
}
for (var i = 0, b; b = this.ranges_[i]; i++) {
if (a.start < b.end) {
if (a.end >= b.start) {
return goog.math.Range.contains(b, a);
}
break;
}
}
return false;
};
/**
* Determines whether a given value is set in the RangeSet.
*
* @param {number} value The value to test.
* @return {boolean} Whether the given value is in the set.
*/
goog.math.RangeSet.prototype.containsValue = function(value) {
for (var i = 0, b; b = this.ranges_[i]; i++) {
if (value < b.end) {
if (value >= b.start) {
return true;
}
break;
}
}
return false;
};
/**
* Returns the union of this RangeSet with another.
*
* @param {goog.math.RangeSet} set Another RangeSet.
* @return {!goog.math.RangeSet} A new RangeSet containing all values from
* either set.
*/
goog.math.RangeSet.prototype.union = function(set) {
// TODO(brenneman): A linear-time merge would be preferable if it is ever a
// bottleneck.
set = set.clone();
for (var i = 0, a; a = this.ranges_[i]; i++) {
set.add(a);
}
return set;
};
/**
* Subtracts the ranges of another set from this one, returning the result
* as a new RangeSet.
*
* @param {!goog.math.RangeSet} set The RangeSet to subtract.
* @return {!goog.math.RangeSet} A new RangeSet containing all values in this
* set minus the values of the input set.
*/
goog.math.RangeSet.prototype.difference = function(set) {
var ret = this.clone();
for (var i = 0, a; a = set.ranges_[i]; i++) {
ret.remove(a);
}
return ret;
};
/**
* Intersects this RangeSet with another.
*
* @param {goog.math.RangeSet} set The RangeSet to intersect with.
* @return {!goog.math.RangeSet} A new RangeSet containing all values set in
* both this and the input set.
*/
goog.math.RangeSet.prototype.intersection = function(set) {
if (this.isEmpty() || set.isEmpty()) {
return new goog.math.RangeSet();
}
return this.difference(set.inverse(this.getBounds()));
};
/**
* Creates a subset of this set over the input range.
*
* @param {goog.math.Range} range The range to copy into the slice.
* @return {!goog.math.RangeSet} A new RangeSet with a copy of the values in the
* input range.
*/
goog.math.RangeSet.prototype.slice = function(range) {
var set = new goog.math.RangeSet();
if (range.start >= range.end) {
return set;
}
for (var i = 0, b; b = this.ranges_[i]; i++) {
if (b.end <= range.start) {
continue;
}
if (b.start > range.end) {
break;
}
set.add(new goog.math.Range(Math.max(range.start, b.start),
Math.min(range.end, b.end)));
}
return set;
};
/**
* Creates an inverted slice of this set over the input range.
*
* @param {goog.math.Range} range The range to copy into the slice.
* @return {!goog.math.RangeSet} A new RangeSet containing inverted values from
* the original over the input range.
*/
goog.math.RangeSet.prototype.inverse = function(range) {
var set = new goog.math.RangeSet();
set.add(range);
for (var i = 0, b; b = this.ranges_[i]; i++) {
if (range.start >= b.end) {
continue;
}
if (range.end < b.start) {
break;
}
set.remove(b);
}
return set;
};
/**
* @return {number} The sum of the lengths of ranges covered in the set.
*/
goog.math.RangeSet.prototype.coveredLength = function() {
return /** @type {number} */ (goog.array.reduce(
this.ranges_,
function(res, range) {
return res + range.end - range.start;
}, 0));
};
/**
* @return {goog.math.Range} The total range this set covers, ignoring any
* gaps between ranges.
*/
goog.math.RangeSet.prototype.getBounds = function() {
if (this.ranges_.length) {
return new goog.math.Range(this.ranges_[0].start,
goog.array.peek(this.ranges_).end);
}
return null;
};
/**
* @return {boolean} Whether any ranges are currently in the set.
*/
goog.math.RangeSet.prototype.isEmpty = function() {
return this.ranges_.length == 0;
};
/**
* Removes all values in the set.
*/
goog.math.RangeSet.prototype.clear = function() {
this.ranges_.length = 0;
};
/**
* Returns an iterator that iterates over the ranges in the RangeSet.
*
* @param {boolean=} opt_keys Ignored for RangeSets.
* @return {!goog.iter.Iterator} An iterator over the values in the set.
*/
goog.math.RangeSet.prototype.__iterator__ = function(opt_keys) {
var i = 0;
var list = this.ranges_;
var iterator = new goog.iter.Iterator();
iterator.next = function() {
if (i >= list.length) {
throw goog.iter.StopIteration;
}
return list[i++].clone();
};
return iterator;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2009 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.RangeSet
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.RangeSetTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,660 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.RangeSetTest');
goog.setTestOnly('goog.math.RangeSetTest');
goog.require('goog.iter');
goog.require('goog.math.Range');
goog.require('goog.math.RangeSet');
goog.require('goog.testing.jsunit');
/**
* Produce legible assertion results for comparing ranges. The expected range
* may be defined as a goog.math.Range or as a two-element array of numbers. If
* two ranges are not equal, the error message will be in the format:
* "Expected <[1, 2]> (Object) but was <[3, 4]> (Object)"
*
* @param {!goog.math.Range|!Array<number>|string} a A descriptive string or
* the expected range.
* @param {!goog.math.Range|!Array<number>} b The expected range when a
* descriptive string is present, or the range to compare.
* @param {goog.math.Range=} opt_c The range to compare when a descriptive
* string is present.
*/
function assertRangesEqual(a, b, opt_c) {
var message = opt_c ? a : '';
var expected = opt_c ? b : a;
var actual = opt_c ? opt_c : b;
if (goog.isArray(expected)) {
assertEquals(message + '\n' +
'Expected ranges must be specified as goog.math.Range ' +
'objects or as 2-element number arrays. Found [' +
expected.join(', ') + ']',
2, expected.length);
expected = new goog.math.Range(expected[0], expected[1]);
}
if (!goog.math.Range.equals(/** @type {!goog.math.Range} */ (expected),
/** @type {!goog.math.Range} */ (actual))) {
if (message) {
assertEquals(message, expected, actual);
} else {
assertEquals(expected, actual);
}
}
}
/**
* Produce legible assertion results for comparing two lists of ranges. Expected
* lists may be specified as a list of goog.math.Ranges, or as a list of
* two-element arrays of numbers.
*
* @param {Array<goog.math.Range|Array<number>>|string} a A help
* string or the list of expected ranges.
* @param {Array<goog.math.Range|Array<number>>} b The list of
* expected ranges when a descriptive string is present, or the list of
* ranges to compare.
* @param {Array<goog.math.Range>=} opt_c The list of ranges to compare when a
* descriptive string is present.
*/
function assertRangeListsEqual(a, b, opt_c) {
var message = opt_c ? a + '\n' : '';
var expected = opt_c ? b : a;
var actual = opt_c ? opt_c : b;
assertEquals(message + 'Array lengths unequal.',
expected.length, actual.length);
for (var i = 0; i < expected.length; i++) {
assertRangesEqual(message + 'Range ' + i + ' mismatch.',
expected[i], actual[i]);
}
}
function testClone() {
var r = new goog.math.RangeSet();
var test = new goog.math.RangeSet(r);
assertRangeListsEqual([], test.ranges_);
r.add(new goog.math.Range(-10, -2));
r.add(new goog.math.Range(2.72, 3.14));
r.add(new goog.math.Range(8, 11));
test = r.clone();
assertRangeListsEqual([[-10, -2], [2.72, 3.14], [8, 11]], test.ranges_);
var test2 = r.clone();
assertRangeListsEqual(test.ranges_, test2.ranges_);
assertNotEquals('The clones should not share the same list reference.',
test.ranges_, test2.ranges_);
for (var i = 0; i < test.ranges_.length; i++) {
assertNotEquals('The clones should not share references to ranges.',
test.ranges_[i], test2.ranges_[i]);
}
}
function testAddNoCorruption() {
var r = new goog.math.RangeSet();
var range = new goog.math.Range(1, 2);
r.add(range);
assertNotEquals('Only a copy of the input range should be stored.',
range, r.ranges_[0]);
range.end = 5;
assertRangeListsEqual('Modifying an input range after use should not ' +
'affect the set.',
[[1, 2]], r.ranges_);
}
function testAdd() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(7, 12));
assertRangeListsEqual([[7, 12]], r.ranges_);
r.add(new goog.math.Range(1, 3));
assertRangeListsEqual([[1, 3], [7, 12]], r.ranges_);
r.add(new goog.math.Range(13, 18));
assertRangeListsEqual([[1, 3], [7, 12], [13, 18]], r.ranges_);
r.add(new goog.math.Range(5, 5));
assertRangeListsEqual('Zero length ranges should be ignored.',
[[1, 3], [7, 12], [13, 18]], r.ranges_);
var badRange = new goog.math.Range(5, 5);
badRange.end = 4;
r.add(badRange);
assertRangeListsEqual('Negative length ranges should be ignored.',
[[1, 3], [7, 12], [13, 18]], r.ranges_);
r.add(new goog.math.Range(-22, -15));
assertRangeListsEqual('Negative ranges should work fine.',
[[-22, -15], [1, 3], [7, 12], [13, 18]], r.ranges_);
r.add(new goog.math.Range(3.1, 6.9));
assertRangeListsEqual('Non-integer ranges should work fine.',
[[-22, -15], [1, 3], [3.1, 6.9], [7, 12], [13, 18]],
r.ranges_);
}
function testAddWithOverlaps() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(7, 12));
r.add(new goog.math.Range(5, 8));
assertRangeListsEqual([[5, 12]], r.ranges_);
r.add(new goog.math.Range(15, 20));
r.add(new goog.math.Range(18, 25));
assertRangeListsEqual([[5, 12], [15, 25]], r.ranges_);
r.add(new goog.math.Range(10, 17));
assertRangeListsEqual([[5, 25]], r.ranges_);
r.add(new goog.math.Range(-4, 4.5));
assertRangeListsEqual([[-4, 4.5], [5, 25]], r.ranges_);
r.add(new goog.math.Range(4.2, 5.3));
assertRangeListsEqual([[-4, 25]], r.ranges_);
}
function testAddWithAdjacentSpans() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(7, 12));
r.add(new goog.math.Range(13, 19));
assertRangeListsEqual([[7, 12], [13, 19]], r.ranges_);
r.add(new goog.math.Range(4, 6));
assertRangeListsEqual([[4, 6], [7, 12], [13, 19]], r.ranges_);
r.add(new goog.math.Range(6, 7));
assertRangeListsEqual([[4, 12], [13, 19]], r.ranges_);
r.add(new goog.math.Range(12, 13));
assertRangeListsEqual([[4, 19]], r.ranges_);
r.add(new goog.math.Range(19.1, 22));
assertRangeListsEqual([[4, 19], [19.1, 22]], r.ranges_);
r.add(new goog.math.Range(19, 19.1));
assertRangeListsEqual([[4, 22]], r.ranges_);
r.add(new goog.math.Range(-3, -2));
assertRangeListsEqual([[-3, -2], [4, 22]], r.ranges_);
r.add(new goog.math.Range(-2, 4));
assertRangeListsEqual([[-3, 22]], r.ranges_);
}
function testAddWithSubsets() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(7, 12));
assertRangeListsEqual([[7, 12]], r.ranges_);
r.add(new goog.math.Range(7, 12));
assertRangeListsEqual([[7, 12]], r.ranges_);
r.add(new goog.math.Range(8, 11));
assertRangeListsEqual([[7, 12]], r.ranges_);
for (var i = 20; i < 30; i += 2) {
r.add(new goog.math.Range(i, i + 1));
}
assertRangeListsEqual(
[[7, 12], [20, 21], [22, 23], [24, 25], [26, 27], [28, 29]],
r.ranges_);
r.add(new goog.math.Range(1, 30));
assertRangeListsEqual([[1, 30]], r.ranges_);
}
function testRemove() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(1, 3));
r.add(new goog.math.Range(7, 8));
r.add(new goog.math.Range(10, 20));
r.remove(new goog.math.Range(3, 6));
assertRangeListsEqual([[1, 3], [7, 8], [10, 20]], r.ranges_);
r.remove(new goog.math.Range(7, 8));
assertRangeListsEqual([[1, 3], [10, 20]], r.ranges_);
r.remove(new goog.math.Range(1, 3));
assertRangeListsEqual([[10, 20]], r.ranges_);
r.remove(new goog.math.Range(8, 11));
assertRangeListsEqual([[11, 20]], r.ranges_);
r.remove(new goog.math.Range(18, 25));
assertRangeListsEqual([[11, 18]], r.ranges_);
r.remove(new goog.math.Range(15, 16));
assertRangeListsEqual([[11, 15], [16, 18]], r.ranges_);
r.remove(new goog.math.Range(11, 15));
assertRangeListsEqual([[16, 18]], r.ranges_);
r.remove(new goog.math.Range(16, 16));
assertRangeListsEqual('Empty ranges should be ignored.',
[[16, 18]], r.ranges_);
r.remove(new goog.math.Range(16, 17));
assertRangeListsEqual([[17, 18]], r.ranges_);
r.remove(new goog.math.Range(17, 18));
assertRangeListsEqual([], r.ranges_);
}
function testRemoveWithNonOverlappingRanges() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(10, 20));
r.remove(new goog.math.Range(5, 8));
assertRangeListsEqual('Non-overlapping ranges should be ignored.',
[[10, 20]], r.ranges_);
r.remove(new goog.math.Range(20, 30));
assertRangeListsEqual('Non-overlapping ranges should be ignored.',
[[10, 20]], r.ranges_);
r.remove(new goog.math.Range(15, 15));
assertRangeListsEqual('Zero-length ranges should be ignored.',
[[10, 20]], r.ranges_);
}
function testRemoveWithIdenticalRanges() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(10, 20));
r.add(new goog.math.Range(30, 40));
r.add(new goog.math.Range(50, 60));
assertRangeListsEqual([[10, 20], [30, 40], [50, 60]], r.ranges_);
r.remove(new goog.math.Range(30, 40));
assertRangeListsEqual([[10, 20], [50, 60]], r.ranges_);
r.remove(new goog.math.Range(50, 60));
assertRangeListsEqual([[10, 20]], r.ranges_);
r.remove(new goog.math.Range(10, 20));
assertRangeListsEqual([], r.ranges_);
}
function testRemoveWithOverlappingSubsets() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(1, 10));
r.remove(new goog.math.Range(1, 4));
assertRangeListsEqual([[4, 10]], r.ranges_);
r.remove(new goog.math.Range(8, 10));
assertRangeListsEqual([[4, 8]], r.ranges_);
}
function testRemoveMultiple() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(5, 8));
r.add(new goog.math.Range(10, 20));
r.add(new goog.math.Range(30, 35));
for (var i = 20; i < 30; i += 2) {
r.add(new goog.math.Range(i, i + 1));
}
assertRangeListsEqual(
'Setting up the test data seems to have failed, how embarrassing.',
[[5, 8], [10, 21], [22, 23], [24, 25], [26, 27], [28, 29], [30, 35]],
r.ranges_);
r.remove(new goog.math.Range(15, 32));
assertRangeListsEqual([[5, 8], [10, 15], [32, 35]],
r.ranges_);
}
function testRemoveWithRealNumbers() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(2, 4));
r.remove(new goog.math.Range(1.1, 2.72));
assertRangeListsEqual([[2.72, 4]], r.ranges_);
r.remove(new goog.math.Range(3.14, 5));
assertRangeListsEqual([[2.72, 3.14]], r.ranges_);
r.remove(new goog.math.Range(2.8, 3));
assertRangeListsEqual([[2.72, 2.8], [3, 3.14]], r.ranges_);
}
function testEquals() {
var a = new goog.math.RangeSet();
var b = new goog.math.RangeSet();
assertTrue(goog.math.RangeSet.equals(a, b));
a.add(new goog.math.Range(3, 9));
assertFalse(goog.math.RangeSet.equals(a, b));
b.add(new goog.math.Range(4, 9));
assertFalse(goog.math.RangeSet.equals(a, b));
b.add(new goog.math.Range(3, 4));
assertTrue(goog.math.RangeSet.equals(a, b));
a.add(new goog.math.Range(12, 14));
b.add(new goog.math.Range(11, 14));
assertFalse(goog.math.RangeSet.equals(a, b));
a.add(new goog.math.Range(11, 12));
assertTrue(goog.math.RangeSet.equals(a, b));
}
function testContains() {
var r = new goog.math.RangeSet();
assertFalse(r.contains(7, 9));
r.add(new goog.math.Range(5, 6));
r.add(new goog.math.Range(10, 20));
assertFalse(r.contains(new goog.math.Range(7, 9)));
assertFalse(r.contains(new goog.math.Range(9, 11)));
assertFalse(r.contains(new goog.math.Range(18, 22)));
assertTrue(r.contains(new goog.math.Range(17, 19)));
assertTrue(r.contains(new goog.math.Range(5, 6)));
assertTrue(r.contains(new goog.math.Range(5.9, 5.999)));
assertFalse('An empty input range should always return false.',
r.contains(new goog.math.Range(15, 15)));
var badRange = new goog.math.Range(15, 15);
badRange.end = 14;
assertFalse('An invalid range should always return false.',
r.contains(badRange));
}
function testContainsValue() {
var r = new goog.math.RangeSet();
assertFalse(r.containsValue(5));
r.add(new goog.math.Range(1, 4));
r.add(new goog.math.Range(10, 20));
assertFalse(r.containsValue(0));
assertFalse(r.containsValue(0.999));
assertFalse(r.containsValue(5));
assertFalse(r.containsValue(25));
assertFalse(r.containsValue(20));
assertTrue(r.containsValue(3));
assertTrue(r.containsValue(10));
assertTrue(r.containsValue(19));
assertTrue(r.containsValue(19.999));
}
function testUnion() {
var a = new goog.math.RangeSet();
a.add(new goog.math.Range(1, 5));
a.add(new goog.math.Range(10, 11));
a.add(new goog.math.Range(15, 20));
var b = new goog.math.RangeSet();
b.add(new goog.math.Range(0, 5));
b.add(new goog.math.Range(8, 18));
var test = a.union(b);
assertRangeListsEqual([[0, 5], [8, 20]], test.ranges_);
var test = b.union(a);
assertRangeListsEqual([[0, 5], [8, 20]], test.ranges_);
var test = a.union(a);
assertRangeListsEqual(a.ranges_, test.ranges_);
}
function testDifference() {
var a = new goog.math.RangeSet();
a.add(new goog.math.Range(1, 5));
a.add(new goog.math.Range(10, 11));
a.add(new goog.math.Range(15, 20));
var b = new goog.math.RangeSet();
b.add(new goog.math.Range(0, 5));
b.add(new goog.math.Range(8, 18));
var test = a.difference(b);
assertRangeListsEqual([[18, 20]], test.ranges_);
var test = b.difference(a);
assertRangeListsEqual([[0, 1], [8, 10], [11, 15]], test.ranges_);
var test = a.difference(a);
assertRangeListsEqual([], test.ranges_);
var test = b.difference(b);
assertRangeListsEqual([], test.ranges_);
}
function testIntersection() {
var a = new goog.math.RangeSet();
a.add(new goog.math.Range(1, 5));
a.add(new goog.math.Range(10, 11));
a.add(new goog.math.Range(15, 20));
var b = new goog.math.RangeSet();
b.add(new goog.math.Range(0, 5));
b.add(new goog.math.Range(8, 18));
var test = a.intersection(b);
assertRangeListsEqual([[1, 5], [10, 11], [15, 18]], test.ranges_);
var test = b.intersection(a);
assertRangeListsEqual([[1, 5], [10, 11], [15, 18]], test.ranges_);
var test = a.intersection(a);
assertRangeListsEqual(a.ranges_, test.ranges_);
}
function testSlice() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(2, 4));
r.add(new goog.math.Range(5, 6));
r.add(new goog.math.Range(9, 15));
var test = r.slice(new goog.math.Range(0, 2));
assertRangeListsEqual([], test.ranges_);
test = r.slice(new goog.math.Range(2, 4));
assertRangeListsEqual([[2, 4]], test.ranges_);
test = r.slice(new goog.math.Range(7, 20));
assertRangeListsEqual([[9, 15]], test.ranges_);
test = r.slice(new goog.math.Range(4, 30));
assertRangeListsEqual([[5, 6], [9, 15]], test.ranges_);
test = r.slice(new goog.math.Range(2, 15));
assertRangeListsEqual([[2, 4], [5, 6], [9, 15]], test.ranges_);
test = r.slice(new goog.math.Range(10, 10));
assertRangeListsEqual('An empty range should produce an empty set.',
[], test.ranges_);
var badRange = new goog.math.Range(10, 10);
badRange.end = 9;
test = r.slice(badRange);
assertRangeListsEqual('An invalid range should produce an empty set.',
[], test.ranges_);
}
function testInverse() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(1, 3));
r.add(new goog.math.Range(5, 6));
r.add(new goog.math.Range(8, 10));
var test = r.inverse(new goog.math.Range(10, 20));
assertRangeListsEqual([[10, 20]], test.ranges_);
test = r.inverse(new goog.math.Range(1, 3));
assertRangeListsEqual([], test.ranges_);
test = r.inverse(new goog.math.Range(0, 2));
assertRangeListsEqual([[0, 1]], test.ranges_);
test = r.inverse(new goog.math.Range(9, 12));
assertRangeListsEqual([[10, 12]], test.ranges_);
test = r.inverse(new goog.math.Range(2, 9));
assertRangeListsEqual([[3, 5], [6, 8]], test.ranges_);
test = r.inverse(new goog.math.Range(4, 9));
assertRangeListsEqual([[4, 5], [6, 8]], test.ranges_);
test = r.inverse(new goog.math.Range(9, 9));
assertRangeListsEqual('An empty range should produce an empty set.',
[], test.ranges_);
var badRange = new goog.math.Range(9, 9);
badRange.end = 8;
test = r.inverse(badRange);
assertRangeListsEqual('An invalid range should produce an empty set.',
[], test.ranges_);
}
function testCoveredLength() {
var r = new goog.math.RangeSet();
assertEquals(0, r.coveredLength());
r.add(new goog.math.Range(5, 9));
assertEquals(4, r.coveredLength());
r.add(new goog.math.Range(0, 3));
r.add(new goog.math.Range(12, 13));
assertEquals(8, r.coveredLength());
r.add(new goog.math.Range(-1, 13));
assertEquals(14, r.coveredLength());
r.add(new goog.math.Range(13, 13.5));
assertEquals(14.5, r.coveredLength());
}
function testGetBounds() {
var r = new goog.math.RangeSet();
assertNull(r.getBounds());
r.add(new goog.math.Range(12, 54));
assertRangesEqual([12, 54], r.getBounds());
r.add(new goog.math.Range(108, 139));
assertRangesEqual([12, 139], r.getBounds());
}
function testIsEmpty() {
var r = new goog.math.RangeSet();
assertTrue(r.isEmpty());
r.add(new goog.math.Range(0, 1));
assertFalse(r.isEmpty());
r.remove(new goog.math.Range(0, 1));
assertTrue(r.isEmpty());
}
function testClear() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(1, 2));
r.add(new goog.math.Range(3, 5));
r.add(new goog.math.Range(8, 13));
assertFalse(r.isEmpty());
r.clear();
assertTrue(r.isEmpty());
}
function testIter() {
var r = new goog.math.RangeSet();
r.add(new goog.math.Range(1, 3));
r.add(new goog.math.Range(5, 6));
r.add(new goog.math.Range(8, 10));
assertRangeListsEqual([[1, 3], [5, 6], [8, 10]], goog.iter.toArray(r));
var i = 0;
goog.iter.forEach(r, function(testRange) {
assertRangesEqual('Iterated set values should match the originals.',
r.ranges_[i], testRange);
assertNotEquals('Iterated range should not be a reference to the original.',
r.ranges_[i], testRange);
i++;
});
}
@@ -0,0 +1,464 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing rectangles.
*/
goog.provide('goog.math.Rect');
goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Size');
/**
* Class for representing rectangular regions.
* @param {number} x Left.
* @param {number} y Top.
* @param {number} w Width.
* @param {number} h Height.
* @struct
* @constructor
*/
goog.math.Rect = function(x, y, w, h) {
/** @type {number} */
this.left = x;
/** @type {number} */
this.top = y;
/** @type {number} */
this.width = w;
/** @type {number} */
this.height = h;
};
/**
* @return {!goog.math.Rect} A new copy of this Rectangle.
*/
goog.math.Rect.prototype.clone = function() {
return new goog.math.Rect(this.left, this.top, this.width, this.height);
};
/**
* Returns a new Box object with the same position and dimensions as this
* rectangle.
* @return {!goog.math.Box} A new Box representation of this Rectangle.
*/
goog.math.Rect.prototype.toBox = function() {
var right = this.left + this.width;
var bottom = this.top + this.height;
return new goog.math.Box(this.top,
right,
bottom,
this.left);
};
/**
* Creates a new Rect object with the same position and dimensions as a given
* Box. Note that this is only the inverse of toBox if left/top are defined.
* @param {goog.math.Box} box A box.
* @return {!goog.math.Rect} A new Rect initialized with the box's position
* and size.
*/
goog.math.Rect.createFromBox = function(box) {
return new goog.math.Rect(box.left, box.top,
box.right - box.left, box.bottom - box.top);
};
if (goog.DEBUG) {
/**
* Returns a nice string representing size and dimensions of rectangle.
* @return {string} In the form (50, 73 - 75w x 25h).
* @override
*/
goog.math.Rect.prototype.toString = function() {
return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' +
this.height + 'h)';
};
}
/**
* Compares rectangles for equality.
* @param {goog.math.Rect} a A Rectangle.
* @param {goog.math.Rect} b A Rectangle.
* @return {boolean} True iff the rectangles have the same left, top, width,
* and height, or if both are null.
*/
goog.math.Rect.equals = function(a, b) {
if (a == b) {
return true;
}
if (!a || !b) {
return false;
}
return a.left == b.left && a.width == b.width &&
a.top == b.top && a.height == b.height;
};
/**
* Computes the intersection of this rectangle and the rectangle parameter. If
* there is no intersection, returns false and leaves this rectangle as is.
* @param {goog.math.Rect} rect A Rectangle.
* @return {boolean} True iff this rectangle intersects with the parameter.
*/
goog.math.Rect.prototype.intersection = function(rect) {
var x0 = Math.max(this.left, rect.left);
var x1 = Math.min(this.left + this.width, rect.left + rect.width);
if (x0 <= x1) {
var y0 = Math.max(this.top, rect.top);
var y1 = Math.min(this.top + this.height, rect.top + rect.height);
if (y0 <= y1) {
this.left = x0;
this.top = y0;
this.width = x1 - x0;
this.height = y1 - y0;
return true;
}
}
return false;
};
/**
* Returns the intersection of two rectangles. Two rectangles intersect if they
* touch at all, for example, two zero width and height rectangles would
* intersect if they had the same top and left.
* @param {goog.math.Rect} a A Rectangle.
* @param {goog.math.Rect} b A Rectangle.
* @return {goog.math.Rect} A new intersection rect (even if width and height
* are 0), or null if there is no intersection.
*/
goog.math.Rect.intersection = function(a, b) {
// There is no nice way to do intersection via a clone, because any such
// clone might be unnecessary if this function returns null. So, we duplicate
// code from above.
var x0 = Math.max(a.left, b.left);
var x1 = Math.min(a.left + a.width, b.left + b.width);
if (x0 <= x1) {
var y0 = Math.max(a.top, b.top);
var y1 = Math.min(a.top + a.height, b.top + b.height);
if (y0 <= y1) {
return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);
}
}
return null;
};
/**
* Returns whether two rectangles intersect. Two rectangles intersect if they
* touch at all, for example, two zero width and height rectangles would
* intersect if they had the same top and left.
* @param {goog.math.Rect} a A Rectangle.
* @param {goog.math.Rect} b A Rectangle.
* @return {boolean} Whether a and b intersect.
*/
goog.math.Rect.intersects = function(a, b) {
return (a.left <= b.left + b.width && b.left <= a.left + a.width &&
a.top <= b.top + b.height && b.top <= a.top + a.height);
};
/**
* Returns whether a rectangle intersects this rectangle.
* @param {goog.math.Rect} rect A rectangle.
* @return {boolean} Whether rect intersects this rectangle.
*/
goog.math.Rect.prototype.intersects = function(rect) {
return goog.math.Rect.intersects(this, rect);
};
/**
* Computes the difference regions between two rectangles. The return value is
* an array of 0 to 4 rectangles defining the remaining regions of the first
* rectangle after the second has been subtracted.
* @param {goog.math.Rect} a A Rectangle.
* @param {goog.math.Rect} b A Rectangle.
* @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which
* together define the difference area of rectangle a minus rectangle b.
*/
goog.math.Rect.difference = function(a, b) {
var intersection = goog.math.Rect.intersection(a, b);
if (!intersection || !intersection.height || !intersection.width) {
return [a.clone()];
}
var result = [];
var top = a.top;
var height = a.height;
var ar = a.left + a.width;
var ab = a.top + a.height;
var br = b.left + b.width;
var bb = b.top + b.height;
// Subtract off any area on top where A extends past B
if (b.top > a.top) {
result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top));
top = b.top;
// If we're moving the top down, we also need to subtract the height diff.
height -= b.top - a.top;
}
// Subtract off any area on bottom where A extends past B
if (bb < ab) {
result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb));
height = bb - top;
}
// Subtract any area on left where A extends past B
if (b.left > a.left) {
result.push(new goog.math.Rect(a.left, top, b.left - a.left, height));
}
// Subtract any area on right where A extends past B
if (br < ar) {
result.push(new goog.math.Rect(br, top, ar - br, height));
}
return result;
};
/**
* Computes the difference regions between this rectangle and {@code rect}. The
* return value is an array of 0 to 4 rectangles defining the remaining regions
* of this rectangle after the other has been subtracted.
* @param {goog.math.Rect} rect A Rectangle.
* @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which
* together define the difference area of rectangle a minus rectangle b.
*/
goog.math.Rect.prototype.difference = function(rect) {
return goog.math.Rect.difference(this, rect);
};
/**
* Expand this rectangle to also include the area of the given rectangle.
* @param {goog.math.Rect} rect The other rectangle.
*/
goog.math.Rect.prototype.boundingRect = function(rect) {
// We compute right and bottom before we change left and top below.
var right = Math.max(this.left + this.width, rect.left + rect.width);
var bottom = Math.max(this.top + this.height, rect.top + rect.height);
this.left = Math.min(this.left, rect.left);
this.top = Math.min(this.top, rect.top);
this.width = right - this.left;
this.height = bottom - this.top;
};
/**
* Returns a new rectangle which completely contains both input rectangles.
* @param {goog.math.Rect} a A rectangle.
* @param {goog.math.Rect} b A rectangle.
* @return {goog.math.Rect} A new bounding rect, or null if either rect is
* null.
*/
goog.math.Rect.boundingRect = function(a, b) {
if (!a || !b) {
return null;
}
var clone = a.clone();
clone.boundingRect(b);
return clone;
};
/**
* Tests whether this rectangle entirely contains another rectangle or
* coordinate.
*
* @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or
* coordinate to test for containment.
* @return {boolean} Whether this rectangle contains given rectangle or
* coordinate.
*/
goog.math.Rect.prototype.contains = function(another) {
if (another instanceof goog.math.Rect) {
return this.left <= another.left &&
this.left + this.width >= another.left + another.width &&
this.top <= another.top &&
this.top + this.height >= another.top + another.height;
} else { // (another instanceof goog.math.Coordinate)
return another.x >= this.left &&
another.x <= this.left + this.width &&
another.y >= this.top &&
another.y <= this.top + this.height;
}
};
/**
* @param {!goog.math.Coordinate} point A coordinate.
* @return {number} The squared distance between the point and the closest
* point inside the rectangle. Returns 0 if the point is inside the
* rectangle.
*/
goog.math.Rect.prototype.squaredDistance = function(point) {
var dx = point.x < this.left ?
this.left - point.x : Math.max(point.x - (this.left + this.width), 0);
var dy = point.y < this.top ?
this.top - point.y : Math.max(point.y - (this.top + this.height), 0);
return dx * dx + dy * dy;
};
/**
* @param {!goog.math.Coordinate} point A coordinate.
* @return {number} The distance between the point and the closest point
* inside the rectangle. Returns 0 if the point is inside the rectangle.
*/
goog.math.Rect.prototype.distance = function(point) {
return Math.sqrt(this.squaredDistance(point));
};
/**
* @return {!goog.math.Size} The size of this rectangle.
*/
goog.math.Rect.prototype.getSize = function() {
return new goog.math.Size(this.width, this.height);
};
/**
* @return {!goog.math.Coordinate} A new coordinate for the top-left corner of
* the rectangle.
*/
goog.math.Rect.prototype.getTopLeft = function() {
return new goog.math.Coordinate(this.left, this.top);
};
/**
* @return {!goog.math.Coordinate} A new coordinate for the center of the
* rectangle.
*/
goog.math.Rect.prototype.getCenter = function() {
return new goog.math.Coordinate(
this.left + this.width / 2, this.top + this.height / 2);
};
/**
* @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner
* of the rectangle.
*/
goog.math.Rect.prototype.getBottomRight = function() {
return new goog.math.Coordinate(
this.left + this.width, this.top + this.height);
};
/**
* Rounds the fields to the next larger integer values.
* @return {!goog.math.Rect} This rectangle with ceil'd fields.
*/
goog.math.Rect.prototype.ceil = function() {
this.left = Math.ceil(this.left);
this.top = Math.ceil(this.top);
this.width = Math.ceil(this.width);
this.height = Math.ceil(this.height);
return this;
};
/**
* Rounds the fields to the next smaller integer values.
* @return {!goog.math.Rect} This rectangle with floored fields.
*/
goog.math.Rect.prototype.floor = function() {
this.left = Math.floor(this.left);
this.top = Math.floor(this.top);
this.width = Math.floor(this.width);
this.height = Math.floor(this.height);
return this;
};
/**
* Rounds the fields to nearest integer values.
* @return {!goog.math.Rect} This rectangle with rounded fields.
*/
goog.math.Rect.prototype.round = function() {
this.left = Math.round(this.left);
this.top = Math.round(this.top);
this.width = Math.round(this.width);
this.height = Math.round(this.height);
return this;
};
/**
* Translates this rectangle by the given offsets. If a
* {@code goog.math.Coordinate} is given, then the left and top values are
* translated by the coordinate's x and y values. Otherwise, top and left are
* translated by {@code tx} and {@code opt_ty} respectively.
* @param {number|goog.math.Coordinate} tx The value to translate left by or the
* the coordinate to translate this rect by.
* @param {number=} opt_ty The value to translate top by.
* @return {!goog.math.Rect} This rectangle after translating.
*/
goog.math.Rect.prototype.translate = function(tx, opt_ty) {
if (tx instanceof goog.math.Coordinate) {
this.left += tx.x;
this.top += tx.y;
} else {
this.left += tx;
if (goog.isNumber(opt_ty)) {
this.top += opt_ty;
}
}
return this;
};
/**
* Scales this rectangle by the given scale factors. The left and width values
* are scaled by {@code sx} and the top and height values are scaled by
* {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled
* by {@code sx}.
* @param {number} sx The scale factor to use for the x dimension.
* @param {number=} opt_sy The scale factor to use for the y dimension.
* @return {!goog.math.Rect} This rectangle after scaling.
*/
goog.math.Rect.prototype.scale = function(sx, opt_sy) {
var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
this.left *= sx;
this.width *= sx;
this.top *= sy;
this.height *= sy;
return this;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Rect
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.RectTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,441 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.RectTest');
goog.setTestOnly('goog.math.RectTest');
goog.require('goog.math.Box');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Rect');
goog.require('goog.math.Size');
goog.require('goog.testing.jsunit');
/**
* Produce legible assertion results. If two rects are not equal, the error
* message will be of the form
* "Expected <(1, 2 - 10 x 10)> (Object) but was <(3, 4 - 20 x 20)> (Object)"
*/
function assertRectsEqual(expected, actual) {
if (!goog.math.Rect.equals(expected, actual)) {
assertEquals(expected, actual);
}
}
function createRect(a) {
return a ? new goog.math.Rect(a[0], a[1], a[2] - a[0], a[3] - a[1]) : null;
}
function testRectClone() {
var r = new goog.math.Rect(0, 0, 0, 0);
assertRectsEqual(r, r.clone());
r.left = -10;
r.top = -20;
r.width = 10;
r.height = 20;
assertRectsEqual(r, r.clone());
}
function testRectIntersection() {
var tests = [[[10, 10, 20, 20], [15, 15, 25, 25], [15, 15, 20, 20]],
[[10, 10, 20, 20], [20, 0, 30, 10], [20, 10, 20, 10]],
[[0, 0, 1, 1], [10, 11, 12, 13], null],
[[11, 12, 98, 99], [22, 23, 34, 35], [22, 23, 34, 35]]];
for (var i = 0; i < tests.length; ++i) {
var t = tests[i];
var r0 = createRect(t[0]);
var r1 = createRect(t[1]);
var expected = createRect(t[2]);
assertRectsEqual(expected, goog.math.Rect.intersection(r0, r1));
assertRectsEqual(expected, goog.math.Rect.intersection(r1, r0));
// Test in place methods.
var clone = r0.clone();
assertRectsEqual(expected, clone.intersection(r1) ? clone : null);
assertRectsEqual(expected, r1.intersection(r0) ? r1 : null);
}
}
function testRectIntersects() {
var r0 = createRect([10, 10, 20, 20]);
var r1 = createRect([15, 15, 25, 25]);
var r2 = createRect([0, 0, 1, 1]);
assertTrue(goog.math.Rect.intersects(r0, r1));
assertTrue(goog.math.Rect.intersects(r1, r0));
assertTrue(r0.intersects(r1));
assertTrue(r1.intersects(r0));
assertFalse(goog.math.Rect.intersects(r0, r2));
assertFalse(goog.math.Rect.intersects(r2, r0));
assertFalse(r0.intersects(r2));
assertFalse(r2.intersects(r0));
}
function testRectBoundingRect() {
var tests = [[[10, 10, 20, 20], [15, 15, 25, 25], [10, 10, 25, 25]],
[[10, 10, 20, 20], [20, 0, 30, 10], [10, 0, 30, 20]],
[[0, 0, 1, 1], [10, 11, 12, 13], [0, 0, 12, 13]],
[[11, 12, 98, 99], [22, 23, 34, 35], [11, 12, 98, 99]]];
for (var i = 0; i < tests.length; ++i) {
var t = tests[i];
var r0 = createRect(t[0]);
var r1 = createRect(t[1]);
var expected = createRect(t[2]);
assertRectsEqual(expected, goog.math.Rect.boundingRect(r0, r1));
assertRectsEqual(expected, goog.math.Rect.boundingRect(r1, r0));
// Test in place methods.
var clone = r0.clone();
clone.boundingRect(r1);
assertRectsEqual(expected, clone);
r1.boundingRect(r0);
assertRectsEqual(expected, r1);
}
}
function testRectDifference() {
// B is the same as A.
assertDifference([10, 10, 20, 20], [10, 10, 20, 20], []);
// B does not touch A.
assertDifference([10, 10, 20, 20], [0, 0, 5, 5], [[10, 10, 20, 20]]);
// B overlaps top half of A.
assertDifference([10, 10, 20, 20], [5, 15, 25, 25],
[[10, 10, 20, 15]]);
// B overlaps bottom half of A.
assertDifference([10, 10, 20, 20], [5, 5, 25, 15],
[[10, 15, 20, 20]]);
// B overlaps right half of A.
assertDifference([10, 10, 20, 20], [15, 5, 25, 25],
[[10, 10, 15, 20]]);
// B overlaps left half of A.
assertDifference([10, 10, 20, 20], [5, 5, 15, 25],
[[15, 10, 20, 20]]);
// B touches A at its bottom right corner
assertDifference([10, 10, 20, 20], [20, 20, 30, 30],
[[10, 10, 20, 20]]);
// B touches A at its top left corner
assertDifference([10, 10, 20, 20], [5, 5, 10, 10],
[[10, 10, 20, 20]]);
// B touches A along its bottom edge
assertDifference([10, 10, 20, 20], [12, 20, 17, 25],
[[10, 10, 20, 20]]);
// B splits A horizontally.
assertDifference([10, 10, 20, 20], [5, 12, 25, 18],
[[10, 10, 20, 12], [10, 18, 20, 20]]);
// B splits A vertically.
assertDifference([10, 10, 20, 20], [12, 5, 18, 25],
[[10, 10, 12, 20], [18, 10, 20, 20]]);
// B subtracts a notch from the top of A.
assertDifference([10, 10, 20, 20], [12, 5, 18, 15],
[[10, 15, 20, 20], [10, 10, 12, 15], [18, 10, 20, 15]]);
// B subtracts a notch from the bottom left of A
assertDifference([1, 6, 3, 9], [1, 7, 2, 9],
[[1, 6, 3, 7], [2, 7, 3, 9]]);
// B subtracts a notch from the bottom right of A
assertDifference([1, 6, 3, 9], [2, 7, 3, 9],
[[1, 6, 3, 7], [1, 7, 2, 9]]);
// B subtracts a notch from the top left of A
assertDifference([1, 6, 3, 9], [1, 6, 2, 8],
[[1, 8, 3, 9], [2, 6, 3, 8]]);
// B subtracts a notch from the top left of A (no coinciding edge)
assertDifference([1, 6, 3, 9], [0, 5, 2, 8],
[[1, 8, 3, 9], [2, 6, 3, 8]]);
// B subtracts a hole from the center of A.
assertDifference([-20, -20, -10, -10], [-18, -18, -12, -12],
[[-20, -20, -10, -18], [-20, -12, -10, -10],
[-20, -18, -18, -12], [-12, -18, -10, -12]]);
}
function assertDifference(a, b, expected) {
var r0 = createRect(a);
var r1 = createRect(b);
var diff = goog.math.Rect.difference(r0, r1);
assertEquals('Wrong number of rectangles in difference ',
expected.length, diff.length);
for (var j = 0; j < expected.length; ++j) {
var e = createRect(expected[j]);
if (!goog.math.Rect.equals(e, diff[j])) {
alert(j + ': ' + e + ' != ' + diff[j]);
}
assertRectsEqual(e, diff[j]);
}
// Test in place version
var diff = r0.difference(r1);
assertEquals('Wrong number of rectangles in in-place difference ',
expected.length, diff.length);
for (var j = 0; j < expected.length; ++j) {
var e = createRect(expected[j]);
if (!goog.math.Rect.equals(e, diff[j])) {
alert(j + ': ' + e + ' != ' + diff[j]);
}
assertRectsEqual(e, diff[j]);
}
}
function testRectToBox() {
var r = new goog.math.Rect(0, 0, 0, 0);
assertObjectEquals(new goog.math.Box(0, 0, 0, 0), r.toBox());
r.top = 10;
r.left = 10;
r.width = 20;
r.height = 20;
assertObjectEquals(new goog.math.Box(10, 30, 30, 10), r.toBox());
r.top = -10;
r.left = 0;
r.width = 10;
r.height = 10;
assertObjectEquals(new goog.math.Box(-10, 10, 0, 0), r.toBox());
}
function testBoxToRect() {
var box = new goog.math.Box(0, 0, 0, 0);
assertObjectEquals(new goog.math.Rect(0, 0, 0, 0),
goog.math.Rect.createFromBox(box));
box.top = 10;
box.left = 15;
box.right = 23;
box.bottom = 27;
assertObjectEquals(new goog.math.Rect(15, 10, 8, 17),
goog.math.Rect.createFromBox(box));
box.top = -10;
box.left = 3;
box.right = 12;
box.bottom = 7;
assertObjectEquals(new goog.math.Rect(3, -10, 9, 17),
goog.math.Rect.createFromBox(box));
}
function testBoxToRectAndBack() {
rectToBoxAndBackTest(new goog.math.Rect(8, 11, 20, 23));
rectToBoxAndBackTest(new goog.math.Rect(9, 13, NaN, NaN));
rectToBoxAndBackTest(new goog.math.Rect(10, 13, NaN, 21));
rectToBoxAndBackTest(new goog.math.Rect(5, 7, 14, NaN));
}
function rectToBoxAndBackTest(rect) {
var box = rect.toBox();
var rect2 = goog.math.Rect.createFromBox(box);
// Use toString for this test since otherwise NaN != NaN.
assertObjectEquals(rect.toString(), rect2.toString());
}
function testRectToBoxAndBack() {
// This doesn't work if left or top is undefined.
boxToRectAndBackTest(new goog.math.Box(11, 13, 20, 17));
boxToRectAndBackTest(new goog.math.Box(10, NaN, NaN, 11));
boxToRectAndBackTest(new goog.math.Box(9, 14, NaN, 11));
boxToRectAndBackTest(new goog.math.Box(10, NaN, 22, 15));
}
function boxToRectAndBackTest(box) {
var rect = goog.math.Rect.createFromBox(box);
var box2 = rect.toBox();
// Use toString for this test since otherwise NaN != NaN.
assertEquals(box.toString(), box2.toString());
}
function testRectContainsRect() {
var r = new goog.math.Rect(-10, 0, 20, 10);
assertTrue(r.contains(r));
assertFalse(r.contains(new goog.math.Rect(NaN, NaN, NaN, NaN)));
var r2 = new goog.math.Rect(0, 2, 5, 5);
assertTrue(r.contains(r2));
assertFalse(r2.contains(r));
r2.left = -11;
assertFalse(r.contains(r2));
r2.left = 0;
r2.width = 15;
assertFalse(r.contains(r2));
r2.width = 5;
r2.height = 10;
assertFalse(r.contains(r2));
r2.top = 0;
assertTrue(r.contains(r2));
}
function testRectContainsCoordinate() {
var r = new goog.math.Rect(20, 40, 60, 80);
// Test middle.
assertTrue(r.contains(new goog.math.Coordinate(50, 80)));
// Test edges.
assertTrue(r.contains(new goog.math.Coordinate(20, 40)));
assertTrue(r.contains(new goog.math.Coordinate(50, 40)));
assertTrue(r.contains(new goog.math.Coordinate(80, 40)));
assertTrue(r.contains(new goog.math.Coordinate(80, 80)));
assertTrue(r.contains(new goog.math.Coordinate(80, 120)));
assertTrue(r.contains(new goog.math.Coordinate(50, 120)));
assertTrue(r.contains(new goog.math.Coordinate(20, 120)));
assertTrue(r.contains(new goog.math.Coordinate(20, 80)));
// Test outside.
assertFalse(r.contains(new goog.math.Coordinate(0, 0)));
assertFalse(r.contains(new goog.math.Coordinate(50, 0)));
assertFalse(r.contains(new goog.math.Coordinate(100, 0)));
assertFalse(r.contains(new goog.math.Coordinate(100, 80)));
assertFalse(r.contains(new goog.math.Coordinate(100, 160)));
assertFalse(r.contains(new goog.math.Coordinate(50, 160)));
assertFalse(r.contains(new goog.math.Coordinate(0, 160)));
assertFalse(r.contains(new goog.math.Coordinate(0, 80)));
}
function testGetSize() {
assertObjectEquals(new goog.math.Size(60, 80),
new goog.math.Rect(20, 40, 60, 80).getSize());
}
function testGetBottomRight() {
assertObjectEquals(new goog.math.Coordinate(40, 60),
new goog.math.Rect(10, 20, 30, 40).getBottomRight());
}
function testGetCenter() {
assertObjectEquals(new goog.math.Coordinate(25, 40),
new goog.math.Rect(10, 20, 30, 40).getCenter());
}
function testGetTopLeft() {
assertObjectEquals(new goog.math.Coordinate(10, 20),
new goog.math.Rect(10, 20, 30, 40).getTopLeft());
}
function testRectCeil() {
var rect = new goog.math.Rect(11.4, 26.6, 17.8, 9.2);
assertEquals('The function should return the target instance',
rect, rect.ceil());
assertRectsEqual(new goog.math.Rect(12, 27, 18, 10), rect);
}
function testRectFloor() {
var rect = new goog.math.Rect(11.4, 26.6, 17.8, 9.2);
assertEquals('The function should return the target instance',
rect, rect.floor());
assertRectsEqual(new goog.math.Rect(11, 26, 17, 9), rect);
}
function testRectRound() {
var rect = new goog.math.Rect(11.4, 26.6, 17.8, 9.2);
assertEquals('The function should return the target instance',
rect, rect.round());
assertRectsEqual(new goog.math.Rect(11, 27, 18, 9), rect);
}
function testRectTranslateCoordinate() {
var rect = new goog.math.Rect(10, 40, 30, 20);
var c = new goog.math.Coordinate(10, 5);
assertEquals('The function should return the target instance',
rect, rect.translate(c));
assertRectsEqual(new goog.math.Rect(20, 45, 30, 20), rect);
}
function testRectTranslateXY() {
var rect = new goog.math.Rect(10, 20, 40, 35);
assertEquals('The function should return the target instance',
rect, rect.translate(15, 10));
assertRectsEqual(new goog.math.Rect(25, 30, 40, 35), rect);
}
function testRectTranslateX() {
var rect = new goog.math.Rect(12, 34, 113, 88);
assertEquals('The function should return the target instance',
rect, rect.translate(10));
assertRectsEqual(new goog.math.Rect(22, 34, 113, 88), rect);
}
function testRectScaleXY() {
var rect = new goog.math.Rect(10, 30, 100, 60);
assertEquals('The function should return the target instance',
rect, rect.scale(2, 5));
assertRectsEqual(new goog.math.Rect(20, 150, 200, 300), rect);
}
function testRectScaleFactor() {
var rect = new goog.math.Rect(12, 34, 113, 88);
assertEquals('The function should return the target instance',
rect, rect.scale(10));
assertRectsEqual(new goog.math.Rect(120, 340, 1130, 880), rect);
}
function testSquaredDistance() {
var rect = new goog.math.Rect(-10, -20, 15, 25);
// Test regions:
// 1 2 3
// +-+
// 4 |5| 6
// +-+
// 7 8 9
// Region 5 (inside the rectangle).
assertEquals(0, rect.squaredDistance(new goog.math.Coordinate(-10, 5)));
assertEquals(0, rect.squaredDistance(new goog.math.Coordinate(5, -20)));
// 1, 2, and 3.
assertEquals(25, rect.squaredDistance(new goog.math.Coordinate(9, 8)));
assertEquals(36, rect.squaredDistance(new goog.math.Coordinate(2, 11)));
assertEquals(53, rect.squaredDistance(new goog.math.Coordinate(12, 7)));
// 4 and 6.
assertEquals(81, rect.squaredDistance(new goog.math.Coordinate(-19, -10)));
assertEquals(64, rect.squaredDistance(new goog.math.Coordinate(13, 0)));
// 7, 8, and 9.
assertEquals(20, rect.squaredDistance(new goog.math.Coordinate(-12, -24)));
assertEquals(9, rect.squaredDistance(new goog.math.Coordinate(0, -23)));
assertEquals(34, rect.squaredDistance(new goog.math.Coordinate(8, -25)));
}
function testDistance() {
var rect = new goog.math.Rect(2, 4, 8, 16);
// Region 5 (inside the rectangle).
assertEquals(0, rect.distance(new goog.math.Coordinate(2, 4)));
assertEquals(0, rect.distance(new goog.math.Coordinate(10, 20)));
// 1, 2, and 3.
assertRoughlyEquals(
Math.sqrt(8), rect.distance(new goog.math.Coordinate(0, 22)), .0001);
assertEquals(8, rect.distance(new goog.math.Coordinate(9, 28)));
assertRoughlyEquals(
Math.sqrt(50), rect.distance(new goog.math.Coordinate(15, 25)), .0001);
// 4 and 6.
assertEquals(7, rect.distance(new goog.math.Coordinate(-5, 6)));
assertEquals(10, rect.distance(new goog.math.Coordinate(20, 10)));
// 7, 8, and 9.
assertEquals(5, rect.distance(new goog.math.Coordinate(-2, 1)));
assertEquals(2, rect.distance(new goog.math.Coordinate(5, 2)));
assertRoughlyEquals(
Math.sqrt(10), rect.distance(new goog.math.Coordinate(1, 1)), .0001);
}
@@ -0,0 +1,208 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A utility class for representing two-dimensional sizes.
* @author brenneman@google.com (Shawn Brenneman)
*/
goog.provide('goog.math.Size');
/**
* Class for representing sizes consisting of a width and height. Undefined
* width and height support is deprecated and results in compiler warning.
* @param {number} width Width.
* @param {number} height Height.
* @struct
* @constructor
*/
goog.math.Size = function(width, height) {
/**
* Width
* @type {number}
*/
this.width = width;
/**
* Height
* @type {number}
*/
this.height = height;
};
/**
* Compares sizes for equality.
* @param {goog.math.Size} a A Size.
* @param {goog.math.Size} b A Size.
* @return {boolean} True iff the sizes have equal widths and equal
* heights, or if both are null.
*/
goog.math.Size.equals = function(a, b) {
if (a == b) {
return true;
}
if (!a || !b) {
return false;
}
return a.width == b.width && a.height == b.height;
};
/**
* @return {!goog.math.Size} A new copy of the Size.
*/
goog.math.Size.prototype.clone = function() {
return new goog.math.Size(this.width, this.height);
};
if (goog.DEBUG) {
/**
* Returns a nice string representing size.
* @return {string} In the form (50 x 73).
* @override
*/
goog.math.Size.prototype.toString = function() {
return '(' + this.width + ' x ' + this.height + ')';
};
}
/**
* @return {number} The longer of the two dimensions in the size.
*/
goog.math.Size.prototype.getLongest = function() {
return Math.max(this.width, this.height);
};
/**
* @return {number} The shorter of the two dimensions in the size.
*/
goog.math.Size.prototype.getShortest = function() {
return Math.min(this.width, this.height);
};
/**
* @return {number} The area of the size (width * height).
*/
goog.math.Size.prototype.area = function() {
return this.width * this.height;
};
/**
* @return {number} The perimeter of the size (width + height) * 2.
*/
goog.math.Size.prototype.perimeter = function() {
return (this.width + this.height) * 2;
};
/**
* @return {number} The ratio of the size's width to its height.
*/
goog.math.Size.prototype.aspectRatio = function() {
return this.width / this.height;
};
/**
* @return {boolean} True if the size has zero area, false if both dimensions
* are non-zero numbers.
*/
goog.math.Size.prototype.isEmpty = function() {
return !this.area();
};
/**
* Clamps the width and height parameters upward to integer values.
* @return {!goog.math.Size} This size with ceil'd components.
*/
goog.math.Size.prototype.ceil = function() {
this.width = Math.ceil(this.width);
this.height = Math.ceil(this.height);
return this;
};
/**
* @param {!goog.math.Size} target The target size.
* @return {boolean} True if this Size is the same size or smaller than the
* target size in both dimensions.
*/
goog.math.Size.prototype.fitsInside = function(target) {
return this.width <= target.width && this.height <= target.height;
};
/**
* Clamps the width and height parameters downward to integer values.
* @return {!goog.math.Size} This size with floored components.
*/
goog.math.Size.prototype.floor = function() {
this.width = Math.floor(this.width);
this.height = Math.floor(this.height);
return this;
};
/**
* Rounds the width and height parameters to integer values.
* @return {!goog.math.Size} This size with rounded components.
*/
goog.math.Size.prototype.round = function() {
this.width = Math.round(this.width);
this.height = Math.round(this.height);
return this;
};
/**
* Scales this size by the given scale factors. The width and height are scaled
* by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not
* given, then {@code sx} is used for both the width and height.
* @param {number} sx The scale factor to use for the width.
* @param {number=} opt_sy The scale factor to use for the height.
* @return {!goog.math.Size} This Size object after scaling.
*/
goog.math.Size.prototype.scale = function(sx, opt_sy) {
var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
this.width *= sx;
this.height *= sy;
return this;
};
/**
* Uniformly scales the size to fit inside the dimensions of a given size. The
* original aspect ratio will be preserved.
*
* This function assumes that both Sizes contain strictly positive dimensions.
* @param {!goog.math.Size} target The target size.
* @return {!goog.math.Size} This Size object, after optional scaling.
*/
goog.math.Size.prototype.scaleToFit = function(target) {
var s = this.aspectRatio() > target.aspectRatio() ?
target.width / this.width :
target.height / this.height;
return this.scale(s);
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Size
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.SizeTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,175 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.SizeTest');
goog.setTestOnly('goog.math.SizeTest');
goog.require('goog.math.Size');
goog.require('goog.testing.jsunit');
function testSize1() {
var s = new goog.math.Size(undefined, undefined);
assertUndefined(s.width);
assertUndefined(s.height);
assertEquals('(undefined x undefined)', s.toString());
}
function testSize3() {
var s = new goog.math.Size(10, 20);
assertEquals(10, s.width);
assertEquals(20, s.height);
assertEquals('(10 x 20)', s.toString());
}
function testSize4() {
var s = new goog.math.Size(10.5, 20.897);
assertEquals(10.5, s.width);
assertEquals(20.897, s.height);
assertEquals('(10.5 x 20.897)', s.toString());
}
function testSizeClone() {
var s = new goog.math.Size(undefined, undefined);
assertEquals(s.toString(), s.clone().toString());
s.width = 4;
s.height = 5;
assertEquals(s.toString(), s.clone().toString());
}
function testSizeEquals() {
var a = new goog.math.Size(4, 5);
assertTrue(goog.math.Size.equals(a, a));
assertFalse(goog.math.Size.equals(a, null));
assertFalse(goog.math.Size.equals(null, a));
var b = new goog.math.Size(4, 5);
var c = new goog.math.Size(4, 6);
assertTrue(goog.math.Size.equals(a, b));
assertFalse(goog.math.Size.equals(a, c));
}
function testSizeArea() {
var s = new goog.math.Size(4, 5);
assertEquals(20, s.area());
}
function testSizePerimeter() {
var s = new goog.math.Size(4, 5);
assertEquals(18, s.perimeter());
}
function testSizeAspectRatio() {
var s = new goog.math.Size(undefined, undefined);
assertNaN(s.aspectRatio());
s.width = 4;
s.height = 0;
assertEquals(Infinity, s.aspectRatio());
s.height = 5;
assertEquals(0.8, s.aspectRatio());
}
function testSizeFitsInside() {
var target = new goog.math.Size(10, 10);
var a = new goog.math.Size(5, 8);
var b = new goog.math.Size(5, 12);
var c = new goog.math.Size(19, 7);
assertTrue(a.fitsInside(target));
assertFalse(b.fitsInside(target));
assertFalse(c.fitsInside(target));
}
function testSizeScaleToFit() {
var target = new goog.math.Size(512, 640);
var a = new goog.math.Size(1600, 1200);
var b = new goog.math.Size(1200, 1600);
var c = new goog.math.Size(400, 300);
var d = new goog.math.Size(undefined, undefined);
assertEquals('(512 x 384)', a.scaleToFit(target).toString());
assertEquals('(480 x 640)', b.scaleToFit(target).toString());
assertEquals('(512 x 384)', c.scaleToFit(target).toString());
assertEquals('(512 x 640)', target.scaleToFit(target).toString());
assertEquals('(NaN x NaN)', d.scaleToFit(target).toString());
assertEquals('(NaN x NaN)', a.scaleToFit(d).toString());
}
function testSizeIsEmpty() {
var s = new goog.math.Size(undefined, undefined);
assertTrue(s.isEmpty());
s.width = 0;
s.height = 5;
assertTrue(s.isEmpty());
s.width = 4;
assertFalse(s.isEmpty());
}
function testSizeScaleFactor() {
var s = new goog.math.Size(4, 5);
assertEquals('(8 x 10)', s.scale(2).toString());
assertEquals('(0.8 x 1)', s.scale(0.1).toString());
}
function testSizeCeil() {
var s = new goog.math.Size(2.3, 4.7);
assertEquals('(3 x 5)', s.ceil().toString());
}
function testSizeFloor() {
var s = new goog.math.Size(2.3, 4.7);
assertEquals('(2 x 4)', s.floor().toString());
}
function testSizeRound() {
var s = new goog.math.Size(2.3, 4.7);
assertEquals('(2 x 5)', s.round().toString());
}
function testSizeGetLongest() {
var s = new goog.math.Size(3, 4);
assertEquals(4, s.getLongest());
s.height = 3;
assertEquals(3, s.getLongest());
s.height = 2;
assertEquals(3, s.getLongest());
assertNaN(new goog.math.Size(undefined, undefined).getLongest());
}
function testSizeGetShortest() {
var s = new goog.math.Size(3, 4);
assertEquals(3, s.getShortest());
s.height = 3;
assertEquals(3, s.getShortest());
s.height = 2;
assertEquals(2, s.getShortest());
assertNaN(new goog.math.Size(undefined, undefined).getShortest());
}
function testSizeScaleXY() {
var s = new goog.math.Size(5, 10);
assertEquals('(20 x 30)', s.scale(4, 3).toString());
}
@@ -0,0 +1,73 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview The Tridiagonal matrix algorithm solver solves a special
* version of a sparse linear system Ax = b where A is tridiagonal.
*
* See http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm
*
*/
goog.provide('goog.math.tdma');
/**
* Solves a linear system where the matrix is square tri-diagonal. That is,
* given a system of equations:
*
* A * result = vecRight,
*
* this class computes result = inv(A) * vecRight, where A has the special form
* of a tri-diagonal matrix:
*
* |dia(0) sup(0) 0 0 ... 0|
* |sub(0) dia(1) sup(1) 0 ... 0|
* A =| ... |
* |0 ... 0 sub(n-2) dia(n-1) sup(n-1)|
* |0 ... 0 0 sub(n-1) dia(n)|
*
* @param {!Array<number>} subDiag The sub diagonal of the matrix.
* @param {!Array<number>} mainDiag The main diagonal of the matrix.
* @param {!Array<number>} supDiag The super diagonal of the matrix.
* @param {!Array<number>} vecRight The right vector of the system
* of equations.
* @param {Array<number>=} opt_result The optional array to store the result.
* @return {!Array<number>} The vector that is the solution to the system.
*/
goog.math.tdma.solve = function(
subDiag, mainDiag, supDiag, vecRight, opt_result) {
// Make a local copy of the main diagonal and the right vector.
mainDiag = mainDiag.slice();
vecRight = vecRight.slice();
// The dimension of the matrix.
var nDim = mainDiag.length;
// Construct a modified linear system of equations with the same solution
// as the input one.
for (var i = 1; i < nDim; ++i) {
var m = subDiag[i - 1] / mainDiag[i - 1];
mainDiag[i] = mainDiag[i] - m * supDiag[i - 1];
vecRight[i] = vecRight[i] - m * vecRight[i - 1];
}
// Solve the new system of equations by simple back-substitution.
var result = opt_result || new Array(vecRight.length);
result[nDim - 1] = vecRight[nDim - 1] / mainDiag[nDim - 1];
for (i = nDim - 2; i >= 0; --i) {
result[i] = (vecRight[i] - supDiag[i] * result[i + 1]) / mainDiag[i];
}
return result;
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2011 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.tdma
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.tdmaTest');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,30 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.tdmaTest');
goog.setTestOnly('goog.math.tdmaTest');
goog.require('goog.math.tdma');
goog.require('goog.testing.jsunit');
function testTdmaSolver() {
var supDiag = [1, 1, 1, 1, 1];
var mainDiag = [-1, -2, -2, -2, -2, -2];
var subDiag = [1, 1, 1, 1, 1];
var vecRight = [1, 2, 3, 4, 5, 6];
var expected = [-56, -55, -52, -46, -36, -21];
var result = [];
goog.math.tdma.solve(subDiag, mainDiag, supDiag, vecRight, result);
assertElementsEquals(expected, result);
}
@@ -0,0 +1,284 @@
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines a 2-element vector class that can be used for
* coordinate math, useful for animation systems and point manipulation.
*
* Vec2 objects inherit from goog.math.Coordinate and may be used wherever a
* Coordinate is required. Where appropriate, Vec2 functions accept both Vec2
* and Coordinate objects as input.
*
* @author brenneman@google.com (Shawn Brenneman)
*/
goog.provide('goog.math.Vec2');
goog.require('goog.math');
goog.require('goog.math.Coordinate');
/**
* Class for a two-dimensional vector object and assorted functions useful for
* manipulating points.
*
* @param {number} x The x coordinate for the vector.
* @param {number} y The y coordinate for the vector.
* @struct
* @constructor
* @extends {goog.math.Coordinate}
*/
goog.math.Vec2 = function(x, y) {
/**
* X-value
* @type {number}
*/
this.x = x;
/**
* Y-value
* @type {number}
*/
this.y = y;
};
goog.inherits(goog.math.Vec2, goog.math.Coordinate);
/**
* @return {!goog.math.Vec2} A random unit-length vector.
*/
goog.math.Vec2.randomUnit = function() {
var angle = Math.random() * Math.PI * 2;
return new goog.math.Vec2(Math.cos(angle), Math.sin(angle));
};
/**
* @return {!goog.math.Vec2} A random vector inside the unit-disc.
*/
goog.math.Vec2.random = function() {
var mag = Math.sqrt(Math.random());
var angle = Math.random() * Math.PI * 2;
return new goog.math.Vec2(Math.cos(angle) * mag, Math.sin(angle) * mag);
};
/**
* Returns a new Vec2 object from a given coordinate.
* @param {!goog.math.Coordinate} a The coordinate.
* @return {!goog.math.Vec2} A new vector object.
*/
goog.math.Vec2.fromCoordinate = function(a) {
return new goog.math.Vec2(a.x, a.y);
};
/**
* @return {!goog.math.Vec2} A new vector with the same coordinates as this one.
* @override
*/
goog.math.Vec2.prototype.clone = function() {
return new goog.math.Vec2(this.x, this.y);
};
/**
* Returns the magnitude of the vector measured from the origin.
* @return {number} The length of the vector.
*/
goog.math.Vec2.prototype.magnitude = function() {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
/**
* Returns the squared magnitude of the vector measured from the origin.
* NOTE(brenneman): Leaving out the square root is not a significant
* optimization in JavaScript.
* @return {number} The length of the vector, squared.
*/
goog.math.Vec2.prototype.squaredMagnitude = function() {
return this.x * this.x + this.y * this.y;
};
/**
* @return {!goog.math.Vec2} This coordinate after scaling.
* @override
*/
goog.math.Vec2.prototype.scale =
/** @type {function(number, number=):!goog.math.Vec2} */
(goog.math.Coordinate.prototype.scale);
/**
* Reverses the sign of the vector. Equivalent to scaling the vector by -1.
* @return {!goog.math.Vec2} The inverted vector.
*/
goog.math.Vec2.prototype.invert = function() {
this.x = -this.x;
this.y = -this.y;
return this;
};
/**
* Normalizes the current vector to have a magnitude of 1.
* @return {!goog.math.Vec2} The normalized vector.
*/
goog.math.Vec2.prototype.normalize = function() {
return this.scale(1 / this.magnitude());
};
/**
* Adds another vector to this vector in-place.
* @param {!goog.math.Coordinate} b The vector to add.
* @return {!goog.math.Vec2} This vector with {@code b} added.
*/
goog.math.Vec2.prototype.add = function(b) {
this.x += b.x;
this.y += b.y;
return this;
};
/**
* Subtracts another vector from this vector in-place.
* @param {!goog.math.Coordinate} b The vector to subtract.
* @return {!goog.math.Vec2} This vector with {@code b} subtracted.
*/
goog.math.Vec2.prototype.subtract = function(b) {
this.x -= b.x;
this.y -= b.y;
return this;
};
/**
* Rotates this vector in-place by a given angle, specified in radians.
* @param {number} angle The angle, in radians.
* @return {!goog.math.Vec2} This vector rotated {@code angle} radians.
*/
goog.math.Vec2.prototype.rotate = function(angle) {
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var newX = this.x * cos - this.y * sin;
var newY = this.y * cos + this.x * sin;
this.x = newX;
this.y = newY;
return this;
};
/**
* Rotates a vector by a given angle, specified in radians, relative to a given
* axis rotation point. The returned vector is a newly created instance - no
* in-place changes are done.
* @param {!goog.math.Vec2} v A vector.
* @param {!goog.math.Vec2} axisPoint The rotation axis point.
* @param {number} angle The angle, in radians.
* @return {!goog.math.Vec2} The rotated vector in a newly created instance.
*/
goog.math.Vec2.rotateAroundPoint = function(v, axisPoint, angle) {
var res = v.clone();
return res.subtract(axisPoint).rotate(angle).add(axisPoint);
};
/**
* Compares this vector with another for equality.
* @param {!goog.math.Vec2} b The other vector.
* @return {boolean} Whether this vector has the same x and y as the given
* vector.
*/
goog.math.Vec2.prototype.equals = function(b) {
return this == b || !!b && this.x == b.x && this.y == b.y;
};
/**
* Returns the distance between two vectors.
* @param {!goog.math.Coordinate} a The first vector.
* @param {!goog.math.Coordinate} b The second vector.
* @return {number} The distance.
*/
goog.math.Vec2.distance = goog.math.Coordinate.distance;
/**
* Returns the squared distance between two vectors.
* @param {!goog.math.Coordinate} a The first vector.
* @param {!goog.math.Coordinate} b The second vector.
* @return {number} The squared distance.
*/
goog.math.Vec2.squaredDistance = goog.math.Coordinate.squaredDistance;
/**
* Compares vectors for equality.
* @param {!goog.math.Coordinate} a The first vector.
* @param {!goog.math.Coordinate} b The second vector.
* @return {boolean} Whether the vectors have the same x and y coordinates.
*/
goog.math.Vec2.equals = goog.math.Coordinate.equals;
/**
* Returns the sum of two vectors as a new Vec2.
* @param {!goog.math.Coordinate} a The first vector.
* @param {!goog.math.Coordinate} b The second vector.
* @return {!goog.math.Vec2} The sum vector.
*/
goog.math.Vec2.sum = function(a, b) {
return new goog.math.Vec2(a.x + b.x, a.y + b.y);
};
/**
* Returns the difference between two vectors as a new Vec2.
* @param {!goog.math.Coordinate} a The first vector.
* @param {!goog.math.Coordinate} b The second vector.
* @return {!goog.math.Vec2} The difference vector.
*/
goog.math.Vec2.difference = function(a, b) {
return new goog.math.Vec2(a.x - b.x, a.y - b.y);
};
/**
* Returns the dot-product of two vectors.
* @param {!goog.math.Coordinate} a The first vector.
* @param {!goog.math.Coordinate} b The second vector.
* @return {number} The dot-product of the two vectors.
*/
goog.math.Vec2.dot = function(a, b) {
return a.x * b.x + a.y * b.y;
};
/**
* Returns a new Vec2 that is the linear interpolant between vectors a and b at
* scale-value x.
* @param {!goog.math.Coordinate} a Vector a.
* @param {!goog.math.Coordinate} b Vector b.
* @param {number} x The proportion between a and b.
* @return {!goog.math.Vec2} The interpolated vector.
*/
goog.math.Vec2.lerp = function(a, b, x) {
return new goog.math.Vec2(goog.math.lerp(a.x, b.x, x),
goog.math.lerp(a.y, b.y, x));
};
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2006 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Vec2
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.Vec2Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,220 @@
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.Vec2Test');
goog.setTestOnly('goog.math.Vec2Test');
goog.require('goog.math.Vec2');
goog.require('goog.testing.jsunit');
function assertVectorEquals(a, b) {
assertTrue(b + ' should be equal to ' + a, goog.math.Vec2.equals(a, b));
}
function testVec2() {
var v = new goog.math.Vec2(3.14, 2.78);
assertEquals(3.14, v.x);
assertEquals(2.78, v.y);
}
function testRandomUnit() {
var a = goog.math.Vec2.randomUnit();
assertRoughlyEquals(1.0, a.magnitude(), 1e-10);
}
function testRandom() {
var a = goog.math.Vec2.random();
assertTrue(a.magnitude() <= 1.0);
}
function testClone() {
var a = new goog.math.Vec2(1, 2);
var b = a.clone();
assertEquals(a.x, b.x);
assertEquals(a.y, b.y);
}
function testMagnitude() {
var a = new goog.math.Vec2(0, 10);
var b = new goog.math.Vec2(3, 4);
assertEquals(10, a.magnitude());
assertEquals(5, b.magnitude());
}
function testSquaredMagnitude() {
var a = new goog.math.Vec2(-3, -4);
assertEquals(25, a.squaredMagnitude());
}
function testScaleFactor() {
var a = new goog.math.Vec2(1, 2);
var scaled = a.scale(0.5);
assertTrue('The type of the return value should be goog.math.Vec2',
scaled instanceof goog.math.Vec2);
assertVectorEquals(new goog.math.Vec2(0.5, 1), a);
}
function testScaleXY() {
var a = new goog.math.Vec2(10, 15);
var scaled = a.scale(2, 3);
assertEquals('The function should return the target instance', a, scaled);
assertTrue('The type of the return value should be goog.math.Vec2',
scaled instanceof goog.math.Vec2);
assertVectorEquals(new goog.math.Vec2(20, 45), a);
}
function testInvert() {
var a = new goog.math.Vec2(3, 4);
a.invert();
assertEquals(-3, a.x);
assertEquals(-4, a.y);
}
function testNormalize() {
var a = new goog.math.Vec2(5, 5);
a.normalize();
assertRoughlyEquals(1.0, a.magnitude(), 1e-10);
}
function testAdd() {
var a = new goog.math.Vec2(1, -1);
a.add(new goog.math.Vec2(3, 3));
assertVectorEquals(new goog.math.Vec2(4, 2), a);
}
function testSubtract() {
var a = new goog.math.Vec2(1, -1);
a.subtract(new goog.math.Vec2(3, 3));
assertVectorEquals(new goog.math.Vec2(-2, -4), a);
}
function testRotate() {
var a = new goog.math.Vec2(1, -1);
a.rotate(Math.PI / 2);
assertRoughlyEquals(1, a.x, 0.000001);
assertRoughlyEquals(1, a.y, 0.000001);
a.rotate(-Math.PI);
assertRoughlyEquals(-1, a.x, 0.000001);
assertRoughlyEquals(-1, a.y, 0.000001);
}
function testRotateAroundPoint() {
var a = goog.math.Vec2.rotateAroundPoint(
new goog.math.Vec2(1, -1), new goog.math.Vec2(1, 0), Math.PI / 2);
assertRoughlyEquals(2, a.x, 0.000001);
assertRoughlyEquals(0, a.y, 0.000001);
}
function testEquals() {
var a = new goog.math.Vec2(1, 2);
assertFalse(a.equals(null));
assertFalse(a.equals(new goog.math.Vec2(1, 3)));
assertFalse(a.equals(new goog.math.Vec2(2, 2)));
assertTrue(a.equals(a));
assertTrue(a.equals(new goog.math.Vec2(1, 2)));
}
function testSum() {
var a = new goog.math.Vec2(0.5, 0.25);
var b = new goog.math.Vec2(0.5, 0.75);
var c = goog.math.Vec2.sum(a, b);
assertVectorEquals(new goog.math.Vec2(1, 1), c);
}
function testDifference() {
var a = new goog.math.Vec2(0.5, 0.25);
var b = new goog.math.Vec2(0.5, 0.75);
var c = goog.math.Vec2.difference(a, b);
assertVectorEquals(new goog.math.Vec2(0, -0.5), c);
}
function testDistance() {
var a = new goog.math.Vec2(3, 4);
var b = new goog.math.Vec2(-3, -4);
assertEquals(10, goog.math.Vec2.distance(a, b));
}
function testSquaredDistance() {
var a = new goog.math.Vec2(3, 4);
var b = new goog.math.Vec2(-3, -4);
assertEquals(100, goog.math.Vec2.squaredDistance(a, b));
}
function testVec2Equals() {
assertTrue(goog.math.Vec2.equals(null, null));
assertFalse(goog.math.Vec2.equals(null, new goog.math.Vec2()));
var a = new goog.math.Vec2(1, 3);
assertTrue(goog.math.Vec2.equals(a, a));
assertTrue(goog.math.Vec2.equals(a, new goog.math.Vec2(1, 3)));
assertFalse(goog.math.Vec2.equals(1, new goog.math.Vec2(3, 1)));
}
function testDot() {
var a = new goog.math.Vec2(0, 5);
var b = new goog.math.Vec2(3, 0);
assertEquals(0, goog.math.Vec2.dot(a, b));
var c = new goog.math.Vec2(-5, -5);
var d = new goog.math.Vec2(0, 7);
assertEquals(-35, goog.math.Vec2.dot(c, d));
}
function testLerp() {
var a = new goog.math.Vec2(0, 0);
var b = new goog.math.Vec2(10, 10);
for (var i = 0; i <= 10; i++) {
var c = goog.math.Vec2.lerp(a, b, i / 10);
assertEquals(i, c.x);
assertEquals(i, c.y);
}
}
function testToString() {
testEquals('(0, 0)', new goog.math.Vec2(0, 0).toString());
}
@@ -0,0 +1,310 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Defines a 3-element vector class that can be used for
* coordinate math, useful for animation systems and point manipulation.
*
* Based heavily on code originally by:
* @author brenneman@google.com (Shawn Brenneman)
*/
goog.provide('goog.math.Vec3');
goog.require('goog.math');
goog.require('goog.math.Coordinate3');
/**
* Class for a three-dimensional vector object and assorted functions useful for
* manipulation.
*
* Inherits from goog.math.Coordinate3 so that a Vec3 may be passed in to any
* function that requires a Coordinate.
*
* @param {number} x The x value for the vector.
* @param {number} y The y value for the vector.
* @param {number} z The z value for the vector.
* @struct
* @constructor
* @extends {goog.math.Coordinate3}
*/
goog.math.Vec3 = function(x, y, z) {
/**
* X-value
* @type {number}
*/
this.x = x;
/**
* Y-value
* @type {number}
*/
this.y = y;
/**
* Z-value
* @type {number}
*/
this.z = z;
};
goog.inherits(goog.math.Vec3, goog.math.Coordinate3);
/**
* Generates a random unit vector.
*
* http://mathworld.wolfram.com/SpherePointPicking.html
* Using (6), (7), and (8) to generate coordinates.
* @return {!goog.math.Vec3} A random unit-length vector.
*/
goog.math.Vec3.randomUnit = function() {
var theta = Math.random() * Math.PI * 2;
var phi = Math.random() * Math.PI * 2;
var z = Math.cos(phi);
var x = Math.sqrt(1 - z * z) * Math.cos(theta);
var y = Math.sqrt(1 - z * z) * Math.sin(theta);
return new goog.math.Vec3(x, y, z);
};
/**
* Generates a random vector inside the unit sphere.
*
* @return {!goog.math.Vec3} A random vector.
*/
goog.math.Vec3.random = function() {
return goog.math.Vec3.randomUnit().scale(Math.random());
};
/**
* Returns a new Vec3 object from a given coordinate.
*
* @param {goog.math.Coordinate3} a The coordinate.
* @return {!goog.math.Vec3} A new vector object.
*/
goog.math.Vec3.fromCoordinate3 = function(a) {
return new goog.math.Vec3(a.x, a.y, a.z);
};
/**
* Creates a new copy of this Vec3.
*
* @return {!goog.math.Vec3} A new vector with the same coordinates as this one.
* @override
*/
goog.math.Vec3.prototype.clone = function() {
return new goog.math.Vec3(this.x, this.y, this.z);
};
/**
* Returns the magnitude of the vector measured from the origin.
*
* @return {number} The length of the vector.
*/
goog.math.Vec3.prototype.magnitude = function() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
};
/**
* Returns the squared magnitude of the vector measured from the origin.
* NOTE(brenneman): Leaving out the square root is not a significant
* optimization in JavaScript.
*
* @return {number} The length of the vector, squared.
*/
goog.math.Vec3.prototype.squaredMagnitude = function() {
return this.x * this.x + this.y * this.y + this.z * this.z;
};
/**
* Scales the current vector by a constant.
*
* @param {number} s The scale factor.
* @return {!goog.math.Vec3} This vector, scaled.
*/
goog.math.Vec3.prototype.scale = function(s) {
this.x *= s;
this.y *= s;
this.z *= s;
return this;
};
/**
* Reverses the sign of the vector. Equivalent to scaling the vector by -1.
*
* @return {!goog.math.Vec3} This vector, inverted.
*/
goog.math.Vec3.prototype.invert = function() {
this.x = -this.x;
this.y = -this.y;
this.z = -this.z;
return this;
};
/**
* Normalizes the current vector to have a magnitude of 1.
*
* @return {!goog.math.Vec3} This vector, normalized.
*/
goog.math.Vec3.prototype.normalize = function() {
return this.scale(1 / this.magnitude());
};
/**
* Adds another vector to this vector in-place.
*
* @param {goog.math.Vec3} b The vector to add.
* @return {!goog.math.Vec3} This vector with {@code b} added.
*/
goog.math.Vec3.prototype.add = function(b) {
this.x += b.x;
this.y += b.y;
this.z += b.z;
return this;
};
/**
* Subtracts another vector from this vector in-place.
*
* @param {goog.math.Vec3} b The vector to subtract.
* @return {!goog.math.Vec3} This vector with {@code b} subtracted.
*/
goog.math.Vec3.prototype.subtract = function(b) {
this.x -= b.x;
this.y -= b.y;
this.z -= b.z;
return this;
};
/**
* Compares this vector with another for equality.
*
* @param {goog.math.Vec3} b The other vector.
* @return {boolean} True if this vector's x, y and z equal the given vector's
* x, y, and z, respectively.
*/
goog.math.Vec3.prototype.equals = function(b) {
return this == b || !!b && this.x == b.x && this.y == b.y && this.z == b.z;
};
/**
* Returns the distance between two vectors.
*
* @param {goog.math.Vec3} a The first vector.
* @param {goog.math.Vec3} b The second vector.
* @return {number} The distance.
*/
goog.math.Vec3.distance = goog.math.Coordinate3.distance;
/**
* Returns the squared distance between two vectors.
*
* @param {goog.math.Vec3} a The first vector.
* @param {goog.math.Vec3} b The second vector.
* @return {number} The squared distance.
*/
goog.math.Vec3.squaredDistance = goog.math.Coordinate3.squaredDistance;
/**
* Compares vectors for equality.
*
* @param {goog.math.Vec3} a The first vector.
* @param {goog.math.Vec3} b The second vector.
* @return {boolean} True if the vectors have equal x, y, and z coordinates.
*/
goog.math.Vec3.equals = goog.math.Coordinate3.equals;
/**
* Returns the sum of two vectors as a new Vec3.
*
* @param {goog.math.Vec3} a The first vector.
* @param {goog.math.Vec3} b The second vector.
* @return {!goog.math.Vec3} The sum vector.
*/
goog.math.Vec3.sum = function(a, b) {
return new goog.math.Vec3(a.x + b.x, a.y + b.y, a.z + b.z);
};
/**
* Returns the difference of two vectors as a new Vec3.
*
* @param {goog.math.Vec3} a The first vector.
* @param {goog.math.Vec3} b The second vector.
* @return {!goog.math.Vec3} The difference vector.
*/
goog.math.Vec3.difference = function(a, b) {
return new goog.math.Vec3(a.x - b.x, a.y - b.y, a.z - b.z);
};
/**
* Returns the dot-product of two vectors.
*
* @param {goog.math.Vec3} a The first vector.
* @param {goog.math.Vec3} b The second vector.
* @return {number} The dot-product of the two vectors.
*/
goog.math.Vec3.dot = function(a, b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
};
/**
* Returns the cross-product of two vectors.
*
* @param {goog.math.Vec3} a The first vector.
* @param {goog.math.Vec3} b The second vector.
* @return {!goog.math.Vec3} The cross-product of the two vectors.
*/
goog.math.Vec3.cross = function(a, b) {
return new goog.math.Vec3(a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x);
};
/**
* Returns a new Vec3 that is the linear interpolant between vectors a and b at
* scale-value x.
*
* @param {goog.math.Vec3} a Vector a.
* @param {goog.math.Vec3} b Vector b.
* @param {number} x The proportion between a and b.
* @return {!goog.math.Vec3} The interpolated vector.
*/
goog.math.Vec3.lerp = function(a, b, x) {
return new goog.math.Vec3(goog.math.lerp(a.x, b.x, x),
goog.math.lerp(a.y, b.y, x),
goog.math.lerp(a.z, b.z, x));
};
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<!--
Copyright 2008 The Closure Library Authors. All Rights Reserved.
Use of this source code is governed by the Apache License, Version 2.0.
See the COPYING file for details.
-->
<!--
Vec3 Unit Tests
-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>
Closure Unit Tests - goog.math.Vec3
</title>
<script src="../base.js">
</script>
<script>
goog.require('goog.math.Vec3Test');
</script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,212 @@
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
goog.provide('goog.math.Vec3Test');
goog.setTestOnly('goog.math.Vec3Test');
goog.require('goog.math.Coordinate3');
goog.require('goog.math.Vec3');
goog.require('goog.testing.jsunit');
function assertVec3Equals(a, b) {
assertTrue(b + ' should be equal to ' + a, goog.math.Vec3.equals(a, b));
}
function testVec3() {
var v = new goog.math.Vec3(3.14, 2.78, -7.21);
assertEquals(3.14, v.x);
assertEquals(2.78, v.y);
assertEquals(-7.21, v.z);
}
function testRandomUnit() {
var a = goog.math.Vec3.randomUnit();
assertRoughlyEquals(1.0, a.magnitude(), 1e-10);
}
function testRandom() {
var a = goog.math.Vec3.random();
assertTrue(a.magnitude() <= 1.0);
}
function testFromCoordinate3() {
var a = new goog.math.Coordinate3(-2, 10, 4);
var b = goog.math.Vec3.fromCoordinate3(a);
assertEquals(-2, b.x);
assertEquals(10, b.y);
assertEquals(4, b.z);
}
function testClone() {
var a = new goog.math.Vec3(1, 2, 5);
var b = a.clone();
assertEquals(a.x, b.x);
assertEquals(a.y, b.y);
assertEquals(a.z, b.z);
}
function testMagnitude() {
var a = new goog.math.Vec3(0, 10, 0);
var b = new goog.math.Vec3(3, 4, 5);
var c = new goog.math.Vec3(-4, 3, 8);
assertEquals(10, a.magnitude());
assertEquals(Math.sqrt(50), b.magnitude());
assertEquals(Math.sqrt(89), c.magnitude());
}
function testSquaredMagnitude() {
var a = new goog.math.Vec3(-3, -4, -5);
assertEquals(50, a.squaredMagnitude());
}
function testScale() {
var a = new goog.math.Vec3(1, 2, 3);
a.scale(0.5);
assertEquals(0.5, a.x);
assertEquals(1, a.y);
assertEquals(1.5, a.z);
}
function testInvert() {
var a = new goog.math.Vec3(3, 4, 5);
a.invert();
assertEquals(-3, a.x);
assertEquals(-4, a.y);
assertEquals(-5, a.z);
}
function testNormalize() {
var a = new goog.math.Vec3(5, 5, 5);
a.normalize();
assertRoughlyEquals(1.0, a.magnitude(), 1e-10);
}
function testAdd() {
var a = new goog.math.Vec3(1, -1, 7);
a.add(new goog.math.Vec3(3, 3, 3));
assertVec3Equals(new goog.math.Vec3(4, 2, 10), a);
}
function testSubtract() {
var a = new goog.math.Vec3(1, -1, 4);
a.subtract(new goog.math.Vec3(3, 3, 3));
assertVec3Equals(new goog.math.Vec3(-2, -4, 1), a);
}
function testEquals() {
var a = new goog.math.Vec3(1, 2, 5);
assertFalse(a.equals(null));
assertFalse(a.equals(new goog.math.Vec3(1, 3, 5)));
assertFalse(a.equals(new goog.math.Vec3(2, 2, 2)));
assertTrue(a.equals(a));
assertTrue(a.equals(new goog.math.Vec3(1, 2, 5)));
}
function testSum() {
var a = new goog.math.Vec3(0.5, 0.25, 1.2);
var b = new goog.math.Vec3(0.5, 0.75, -0.6);
var c = goog.math.Vec3.sum(a, b);
assertVec3Equals(new goog.math.Vec3(1, 1, 0.6), c);
}
function testDifference() {
var a = new goog.math.Vec3(0.5, 0.25, 3);
var b = new goog.math.Vec3(0.5, 0.75, 5);
var c = goog.math.Vec3.difference(a, b);
assertVec3Equals(new goog.math.Vec3(0, -0.5, -2), c);
}
function testDistance() {
var a = new goog.math.Vec3(3, 4, 5);
var b = new goog.math.Vec3(-3, -4, 5);
assertEquals(10, goog.math.Vec3.distance(a, b));
}
function testSquaredDistance() {
var a = new goog.math.Vec3(3, 4, 5);
var b = new goog.math.Vec3(-3, -4, 5);
assertEquals(100, goog.math.Vec3.squaredDistance(a, b));
}
function testVec3Equals() {
assertTrue(goog.math.Vec3.equals(null, null, null));
assertFalse(goog.math.Vec3.equals(null, new goog.math.Vec3()));
var a = new goog.math.Vec3(1, 3, 5);
assertTrue(goog.math.Vec3.equals(a, a));
assertTrue(goog.math.Vec3.equals(a, new goog.math.Vec3(1, 3, 5)));
assertFalse(goog.math.Vec3.equals(1, new goog.math.Vec3(3, 1, 5)));
}
function testDot() {
var a = new goog.math.Vec3(0, 5, 2);
var b = new goog.math.Vec3(3, 0, 5);
assertEquals(10, goog.math.Vec3.dot(a, b));
var c = new goog.math.Vec3(-5, -5, 5);
var d = new goog.math.Vec3(0, 7, -2);
assertEquals(-45, goog.math.Vec3.dot(c, d));
}
function testCross() {
var a = new goog.math.Vec3(3, 0, 0);
var b = new goog.math.Vec3(0, 2, 0);
assertVec3Equals(new goog.math.Vec3(0, 0, 6), goog.math.Vec3.cross(a, b));
var c = new goog.math.Vec3(1, 2, 3);
var d = new goog.math.Vec3(4, 5, 6);
assertVec3Equals(new goog.math.Vec3(-3, 6, -3), goog.math.Vec3.cross(c, d));
}
function testLerp() {
var a = new goog.math.Vec3(0, 0, 0);
var b = new goog.math.Vec3(10, 10, 10);
for (var i = 0; i <= 10; i++) {
var c = goog.math.Vec3.lerp(a, b, i / 10);
assertEquals(i, c.x);
assertEquals(i, c.y);
assertEquals(i, c.z);
}
}