diff --git a/packages/math/arrays.js b/packages/math/array.js similarity index 68% rename from packages/math/arrays.js rename to packages/math/array.js index dbbb8bf..289e860 100644 --- a/packages/math/arrays.js +++ b/packages/math/array.js @@ -1,9 +1,15 @@ -jsio('import .util'); +jsio('import math.util as util'); /** - * @namespace + * @package math.array; + * + * Functions to manipulate one or more arrays in tandem. */ +/** + * Returns the weighted average + */ + exports.weightedAverage = function (a, w, n) { n = n || a.length; var s = 0; @@ -13,14 +19,22 @@ exports.weightedAverage = function (a, w, n) { return s / n; } -exports.subtract = function(a, b) { +/** + * Subtract two arrays, (a - b) + */ + +exports.subtract = function (a, b) { var length = a.length, - diff = new Array(length); + diff = new Array(length); for (var i = 0; i < length; ++i) { - diff[i] = b[i] - a[i]; + diff[i] = a[i] - b[i]; } return diff; -} +}; + +/** + * Average an array. + */ exports.average = function (a, n) { n = n || a.length; @@ -31,6 +45,10 @@ exports.average = function (a, n) { return s / n; } +/** + * Return the standard deviation of an array. + */ + exports.stddev = function (a, n) { var avg = exports.average(a, n); n = n || a.length; @@ -42,6 +60,10 @@ exports.stddev = function (a, n) { return Math.sqrt(s / (n - 1)); } +/** + * Shuffle an array. Takes an optional random seed. + */ + exports.shuffle = function(a, randGen) { var len = a.length; for (var i = 0; i < len; ++i) { @@ -53,6 +75,10 @@ exports.shuffle = function(a, randGen) { return a; } +/** + * Rotate an array's elements left. + */ + exports.rotate = function(a, count) { var len = a.length, b = new Array(len), diff --git a/packages/math/geom/Circle.js b/packages/math/geom/Circle.js new file mode 100644 index 0000000..021a464 --- /dev/null +++ b/packages/math/geom/Circle.js @@ -0,0 +1,40 @@ +import math.geom.Point as Point; + +/** + * @extends math.geom.Point + * Models a circle given a radius. + * Circle(x, y, radius) + * Circle({x: default 0, y: default 0, radius: default 0}) + */ +exports = Class(Point, function(supr) { + this.init = function(a, b, c) { + switch(arguments.length) { + case 0: + this.x = 0; + this.y = 0; + this.radius = 0; + break; + case 1: + case 2: + this.x = a.x || 0; + this.y = a.y || 0; + this.radius = a.radius || 0; + break; + case 3: + this.x = a; + this.y = b; + this.radius = c; + break; + } + } + + /** + * Scale the position and radius of this circle by a percentage. + */ + + this.scale = function(s) { + supr(this, 'scale', arguments); + this.radius *= s; + return this; + } +}); \ No newline at end of file diff --git a/packages/math/geom/Line.js b/packages/math/geom/Line.js new file mode 100644 index 0000000..4c46120 --- /dev/null +++ b/packages/math/geom/Line.js @@ -0,0 +1,37 @@ +import math.geom.Point as Point; + +exports = Class(function() { + this.init = function(a, b, c, d) { + switch(arguments.length) { + case 0: + this.start = new Point(); + this.end = new Point(); + break; + case 1: + this.start = new Point(a.start); + this.end = new Point(a.end); + break; + case 2: + this.start = new Point(a); + this.end = new Point(b); + break; + case 3: + this.start = new Point(a); + this.end = new Point(b, c); + break; + case 4: + default: + this.start = new Point(a, b); + this.end = new Point(c, d); + break; + } + } + + this.getMagnitude = + this.getLength = function() { + var dx = this.end.x - this.start.x, + dy = this.end.y - this.start.y; + + return Math.sqrt(dx * dx + dy * dy); + } +}); diff --git a/packages/math/geom/Point.js b/packages/math/geom/Point.js new file mode 100644 index 0000000..184d830 --- /dev/null +++ b/packages/math/geom/Point.js @@ -0,0 +1,179 @@ +/** + * @package math.geom.Point; + * Models a Point in 2D space. + */ + +var Point = exports = Class(function() { + this.init = function(a, b) { + switch(arguments.length) { + case 0: + this.x = 0; + this.y = 0; + break; + case 1: + this.x = a.x || 0; + this.y = a.y || 0; + break; + case 2: + this.x = a || 0; + this.y = b || 0; + break; + } + } + + /** + * Rotates this point around the origin by a value in radians. + */ + + this.rotate = function(r) { + var x = this.x, + y = this.y, + cosr = Math.cos(r), + sinr = Math.sin(r); + + this.x = x * cosr - y * sinr; + this.y = x * sinr + y * cosr; + + return this; + } + + /** + * Translate this point by two scalars or by another point. + */ + + this.translate = this.add = function(x, y) { + if (typeof x == 'number') { + this.x += x; + this.y += y; + } else { + this.x += x.x; + this.y += x.y; + } + return this; + } + + /** + * Subtract this point by two scalars or by another point. + */ + + this.subtract = function(x, y) { + if (typeof x == 'number') { + this.x -= x; + this.y -= y; + } else { + this.x -= x.x; + this.y -= x.y; + } + return this; + } + + /** + * Scale this number. + */ + + this.scale = function(s) { + this.x *= s; + this.y *= s; + return this; + } + + /** + * Set the magnitude of this point at a constant angle. + */ + + this.setMagnitude = function(m) { + var theta = this.getAngle(); + this.x = m * Math.cos(theta); + this.y = m * Math.sin(theta); + return this; + } + + /** + * Normalize this point to the unit circle. + */ + + this.normalize = function() { + var m = this.getMagnitude(); + this.x /= m; + this.y /= m; + return this; + }; + + /** + * Add magnitude to this point. + */ + + this.addMagnitude = function(m) { + return this.setMagnitude(this.getMagnitude() + m); + }; + + this.getMagnitude = function() { + return Math.sqrt(this.x * this.x + this.y * this.y); + }; + + this.getSquaredMagnitude = function() { + return this.x * this.x + this.y * this.y; + }; + + this.getDirection = this.getAngle = function() { + return Math.atan2(this.y, this.x); + }; + +}); + +Point.getPolarR = function(x, y) { + throw "notImplemented"; +} + +Point.getPolarTheta = function(x, y) { + var val = Math.atan2(y,x) + (Math.PI * 2); + return val > Math.PI * 2 ? val % (Math.PI * 2) : val; +} + +Point.add = Point.translate = function(a, b, c, d) { + switch(arguments.length) { + case 2: return new Point(a).add(b); + case 3: return new Point(a).add(b, c); + case 4: return new Point(a, b).add(c, d); + } +} + +Point.subtract = function(a, b, c, d) { + switch(arguments.length) { + case 2: return new Point(a).subtract(b); + case 3: return new Point(a).subtract(b, c); + case 4: return new Point(a, b).subtract(c, d); + } +} + +Point.scale = function(a, b, c) { + switch(arguments.length) { + case 2: return new Point(a).scale(b); + case 3: return new Point(a, b).scale(c); + } +} + +Point.setMagnitude = function(a, b, c) { + switch(arguments.length) { + case 2: return new Point(a).setMagnitude(c); + case 3: return new Point(a, b).setMagnitude(c); + } +} + +Point.addMagnitude = function(a, b, c) { + switch(arguments.length) { + case 2: pt = new Point(a); break; + case 3: pt = new Point(a, b); b = c; break; + } + + return pt.addMagnitude(b); +} + +Point.getMagnitude = function(a, b) { return new Point(a, b).getMagnitude(); } + +Point.rotate = function(a, b, c) { + switch(arguments.length) { + case 2: return new Point(a).rotate(b); + case 3: return new Point(a, b).rotate(c); + } +} diff --git a/packages/math/geom/Polygon.js b/packages/math/geom/Polygon.js new file mode 100644 index 0000000..c9934d5 --- /dev/null +++ b/packages/math/geom/Polygon.js @@ -0,0 +1,210 @@ +exports = Class(function() { + + this.init = function(opts) { + console.log(opts); + this.x = opts.x; + this.y = opts.y; + if (opts.hasOwnProperty('convex')) this.convex = opts.convex; + } + + /** + * + * Polygon representation : { + * x : [x1, x2, ... , xn], + * y : [y1, y2, ... , yn], + * convex : boolean + * } + * + **/ + var k, center; + + /* + * Check for convexity using Jarvis's march (aka giftwrapping) + */ + this.isConvex = function () { + var x = this.x, + y = this.y, + length = x.length; + // Polygon is non-degenerate + if (length < 3) { return false; } + + var ax = x[0] - x[length-1], + ay = y[0] - y[length-1], + bx = x[1] - x[0], + by = y[1] - y[0]; + + var theta = Math.asin((by*ax-bx*ay) / (norm([ax,ay])*norm([bx,by]))), + orientation = sign(theta); + + console.log('theta : ' + theta + ', orientation : ' + orientation); + + for (k = 1; k < length-1; k++) { + ax = x[k]-x[k-1]; + ay = y[k]-y[k-1]; + bx = x[k+1]-x[k]; + by = y[k+1]-y[k]; + + theta = Math.asin((by*ax-bx*ay) / (norm([ax,ay])*norm([bx,by]))); + + console.log('theta : ' + theta + ', sign of turn : ' + sign(theta)); + + if (theta != 0 && orientation + sign(theta) == 0) { + this.convex = false; + return false; + } + } + + ax = x[length-1]-x[length-2]; + ay = y[length-1]-y[length-2]; + bx = x[0]-x[length-1]; + by = y[0]-y[length-1]; + theta = Math.asin((by*ax-bx*ay) / (norm([ax,ay])*norm([bx,by]))); + + console.log('theta : ' + theta + ', sign of final turn : ' + sign(theta)); + + if (theta != 0 && orientation + sign(theta) == 0) { + this.convex = false; + return false; + } + this.convex = true; + return true; + }; + + + this.getCenter = function() { + var x = this.x, + y = this.y; + center = [array_sum(x) / x.length, array_sum(y) / y.length]; + return center; + } + + this.containsPoint = function (point) { + var x = point[0], + y = point[1], + j = 0, + polyx = this.x, polyy = this.y, + x1, x2, y1, y2, + v1x, v1y, v2x, v2y, + theta=0, + l = this.x.length; + + for (var i = 0; i < l; i++) { + j = (i === l - 1) ? 0 : i + 1; + x1 = polyx[i]; + y1 = polyy[i]; + x2 = polyx[j]; + y2 = polyy[j]; + + v1x = x1-x; + v1y = y1-y; + v2x = x2-x; + v2y = y2-y; + + theta += Math.asin((v1x*v2y - v1y*v2x) / (norm([v1x,v1y]) * norm([v2x,v2y]))); + } + theta = Math.abs(theta); + return approx(theta, 2*Math.PI, 1e-6); + }; + + /* + * Scale the polygon about the 'center of mass' + * @scalar : the scaling factor + */ + this.scale = function (scalar) { + var x = this.x, + y = this.y; + center = [array_sum(x) / x.length, array_sum(y) / y.length]; + + for (k = 0; k < x.length; k++) { + x[k] = center[0] + (x[k]-center[0])*scalar; + y[k] = center[1] + (y[k]-center[1])*scalar; + } + }; + + /* + * Translate the polygon along the given vector + * @displacement : vector specifying translation + */ + this.translate = function (displacement) { + for (k = 0; k < this.x.length; k++){ + this.x[k] += displacement[0], this.y[k] += displacement[1]; + } + }; + + /* + * Rotate the polygon about the center* + * @theta : angle which to rotate counterclockwise + */ + this.rotate = function (theta) { + var x = this.x, + y = this.y; + + center = [array_sum(x) / x.length, array_sum(y) / y.length]; + + console.log('__________________' + array_sum(x)); + + var cos = Math.cos(theta), + sin = Math.sin(theta); + + for (k = 0; k < x.length; k++){ + var offset = [ x[k] - center[0], y[k]-center[1] ]; + this.x[k] = cos * offset[0] - sin * offset[1]; + this.y[k] = sin * offset[0] - cos * offset[1]; + } + }; + + /* + * Find the smallest element in an array, wrt compare (avoids sorting) + * @compare : return -1 || 0 || 1 + */ + function smallest (arr, compare) { + if (arr.length < 1) { return undefined; } + var min = arr[0]; + for (k = 1; k0) { + return 1; + } + else return 0; + } + + function approx(a,b,epsilon) { + return (Math.abs(a-b) rx2 ? x2 : rx2) - this.x; + this.height = (y2 > ry2 ? y2 : ry2) - this.y; + }; + + /** + * Get a point for the given corner. + */ + + this.getCorner = function(i) { + switch(i) { + case CORNER.TOP_LEFT: + return new Point(this.x, this.y); + case CORNER.TOP_RIGHT: + return new Point(this.x + this.width, this.y); + case CORNER.BOTTOM_LEFT: + return new Point(this.x, this.y + this.height); + case CORNER.BOTTOM_RIGHT: + return new Point(this.x + this.width, this.y + this.height); + } + } + + /** + * Return a line corresponding to the given side. + */ + + this.getSide = function(i) { + switch(i) { + case SIDE.TOP: + return new Line(this.getCorner(CORNER.TOP_LEFT), this.getCorner(CORNER.TOP_RIGHT)); + case SIDE.RIGHT: + return new Line(this.getCorner(CORNER.TOP_RIGHT), this.getCorner(CORNER.BOTTOM_RIGHT)); + case SIDE.BOTTOM: + return new Line(this.getCorner(CORNER.BOTTOM_RIGHT), this.getCorner(CORNER.BOTTOM_LEFT)); + case SIDE.LEFT: + return new Line(this.getCorner(CORNER.BOTTOM_LEFT), this.getCorner(CORNER.TOP_LEFT)); + } + } + + /** + * Return the center point of a rectangle. + */ + + this.getCenter = function() { + return new Point(this.x + this.width / 2, this.y + this.height / 2); + } +}); + +var SIDE = Rect.SIDE = lib.Enum('TOP', 'BOTTOM', 'LEFT', 'RIGHT'), + CORNER = Rect.CORNER = lib.Enum('TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_RIGHT', 'BOTTOM_LEFT'); diff --git a/packages/math/geom/Vec2D.js b/packages/math/geom/Vec2D.js new file mode 100644 index 0000000..466a9bc --- /dev/null +++ b/packages/math/geom/Vec2D.js @@ -0,0 +1,96 @@ +/** + * Model a vector in two-dimensional space. + * Pass an "angle" option in radians to this function to initialize an angle. + */ + +var Vec2D = exports = Class(function() { + this.init = function(opts) { + if ('angle' in opts) { + this.x = opts.magnitude * Math.cos(opts.angle); + this.y = opts.magnitude * Math.sin(opts.angle); + } else { + this.x = opts.x; + this.y = opts.y; + } + } + + /** + * Add a force vector to this vector. + */ + + this.addForce = function(f) { + this.x += f.x; this.y += f.y; + }; + + /** + * Return the angle of this vector. + */ + + this.getAngle = function() { + return Math.atan2(this.y, this.x); + }; + + /** + * Return the magnitude of this vector. + */ + + this.getMagnitude = function() { + return Math.sqrt(this.x * this.x + this.y * this.y); + } + + /** + * Get a unit vector corresponding to this vector's angle. + */ + + this.getUnitVector = function() { + return new Vec2D({ + magnitude:1, + angle: this.getAngle() + }); + }; + + /** + * Return the dot product of this and another vector. + */ + + this.dot = function(vec) { + return (this.x * vec.x) + (this.y * vec.y); + }; + + /** + * Returns a vector that is the addition of this and another vector. + */ + + this.add = function(vec) { + return new Vec2D({x:this.x + vec.x, y:this.y+vec.y}); + }; + + /** + * Returns a vector that is this vector subtracted by another. + */ + + this.minus = function(vec) { + return new Vec2D({x: this.x-vec.x, y: this.y-vec.y}); + }; + + /** + * Returns a vector that would negate this vector when added. + */ + + this.negate = function() { + return new Vec2D({x: -this.x, y: -this.y}); + }; + + /** + * Returns a vector that multiplies this vector's magnitude by a scalar. + */ + + this.multiply = function(scalar) { + return new Vec2D({ + angle: this.getAngle(), + magnitude: this.getMagnitude()*scalar + }); + }; + +}); + diff --git a/packages/math/geom/angle.js b/packages/math/geom/angle.js new file mode 100644 index 0000000..b1fb41d --- /dev/null +++ b/packages/math/geom/angle.js @@ -0,0 +1,42 @@ +/** + * @namespace + */ + +var PI = Math.PI, + TWOPI = Math.PI * 2; + +exports.average = function(a, b, weight) { + if (weight === undefined) { weight = 0.5; } + var r1 = exports.getRange(a, b); + var avg = r1 < PI + ? a + r1 * (1 - weight) + : b + (2 * PI - r1) * weight; + + return avg > PI ? avg - 2 * PI : avg < -PI ? avg + 2 * PI : avg; +} + +// between -PI and PI +exports.normalize = function(a) { + + // TODO: don't use loops + while(a < -PI) { a += 2 * PI; } + while(a > PI) { a -= 2 * PI; } + return a; +} + +exports.add = function(a, b) { + var sum = a + b; + return sum > PI ? sum - TWOPI : sum < -PI ? sum + TWOPI : sum; +} + +// smaller of two angles a - b, b - a +exports.difference = function(a, b) { + var diff = exports.getRange(a, b); + return diff > PI ? diff - TWOPI : diff; +} + +// angular range from a to b, returns float between [0, 2PI] +exports.range = function(a, b) { + var r = b - a; + return r < 0 ? r + TWOPI : r; +} diff --git a/packages/math/geom/intersect.js b/packages/math/geom/intersect.js new file mode 100644 index 0000000..42e38b2 --- /dev/null +++ b/packages/math/geom/intersect.js @@ -0,0 +1,202 @@ +import math.geom.Point as Point; +import math.geom.Line as Line; +import math.geom.Rect as Rect; + +/** + * @package math.geom.intersect + */ +var intersect = exports; + +intersect.pointAndRect = intersect.ptAndRect = function (pt, rect) { + var x = pt.x, + y = pt.y; + return (x >= rect.x && + x <= rect.x + rect.width && + y >= rect.y && + y <= rect.y + rect.height); +}; + +intersect.rectAndPoint = intersect.rectAndPt = function (rect, pt) { + return intersect.pointAndRect(pt, rect); +}; + +intersect.pointAndCircle = intersect.ptAndCirc = function(pt, circ) { + var dx = pt.x - circ.x, + dy = pt.y - circ.y; + return dx * dx + dy * dy < circ.radius * circ.radius; +}; + +intersect.circleAndPoint = intersect.circAndPt = function (circ, pt) { + return intersect.pointAndCircle(pt, circ); +}; + +intersect.rectAndRect = function (rect1, rect2) { + return !((rect1.y + rect1.height < rect2.y) || + (rect2.y + rect2.height < rect1.y) || + (rect1.x + rect1.width < rect2.x) || + (rect2.x + rect2.width < rect1.x)); +}; + +var SIDE = Rect.SIDE; + +intersect.circleAndRect = function(circle, rect) { + if (intersect.pointAndRect(circle, rect)) { + return true; + } + return (intersect.lineAndCircle(rect.getSide(1), circle) || + intersect.lineAndCircle(rect.getSide(2), circle) || + intersect.lineAndCircle(rect.getSide(3), circle) || + intersect.lineAndCircle(rect.getSide(4), circle)); +}; + +intersect.rectAndCircle = function(rect, circle) { + return intersect.circleAndRect(circle, rect); +}; + +intersect.lineAndCircle = function (line, circle) { + var vec = intersect.pointToLine(circle, line); + return vec.getMagnitude() < circle.radius; +}; + +intersect.circleAndLine = function (circle, line) { + return intersect.lineAndCircle(line, circle); +}; + +// Return the minimum displacement vector in case of collision +intersect.polyAndPoly = function(poly1, poly2) { + var x1 = poly1.x, + y1 = poly1.y, + x2 = poly2.x, + y2 = poly2.y, + i = 0, l = x1.length, + j, k = x2.length, + normal = [0,0], + length, + min1, min2, max1, max2, + MTV, MTV2 = null, + MN = null, + dot, + nextX, nexyY, + currentX, currentY, + m; + // loop through the edges of polygon 1 + for (; i < l; i++) { + var m = (i == l-1 ? 0 : i+1); + currentX = x1[i]; + currentY = y1[i]; + nextX = x1[m]; + nextY = y1[m]; + + //generate the normal for current edge + normal = [currentY - nextY, nextX - currentX]; + length = Math.sqrt(normal[0]*normal[0] + normal[1]*normal[1]) + normal[0] /= length; + normal[1] /= length; + min1 = min2 = max1 = max2 = -1; + //project vertices from poly1 onto axis + for (j=0; j max1 || max1 === -1) max1 = dot; + if (dot < min1 || min1 === -1) min1 = dot; + } + // project all vertices from poly2 onto axis + for (j=0; jmax2 || max2 == -1) max2 = dot; + if (dot=0) { + return false; + } + if (MTV === null || interval > MTV) { + MTV = interval; + MN = normal; + } + } + // loop throught the edges of polygon 2 + for (i=0; i max1 || max1 === -1) max1 = dot; + if (dot < min1 || min1 === -1) min1 = dot; + } + //project all vertices from poly2 onto axis + for (j=0; j max2 || max2 === -1) max2 = dot; + if (dot < min2 || min2 === -1) min2 = dot; + } + //calculate the minimum translation vector should be negative + if(min1 < min2) { + interval = min2-max1; + normal[0] *= -1; + normal[1] *= -1; + } else { + interval = min1-max2; + } + //exit early if positive + if (interval >= 0) { + return false; + } + if (MTV === null || interval > MTV) MTV = interval; + if (interval > MTV2 || MTV2 === null) { + MTV2 = interval; + MN = normal; + } + } + + return {overlap: MTV2, normal: MN}; +}; + + +// returns line from pt to nearest pt on line +intersect.pointToLine = intersect.ptToLine = function (pt, line) { + var dx = (line.end.x - line.start.x), + dy = (line.end.y - line.start.y), + u = ((pt.x - line.start.x) * dx // TODO can we abstract this from 2D to 2D/3D? + + (pt.y - line.start.y) * dy) / (dx * dx + dy * dy); + + var i; + if (u < 0) { + i = new Point(line.start); + } else if (u > 1) { + i = new Point(line.end); + } else { + i = new Point(line.start.x + u * dx, line.start.y + u * dy); + } + return new Line(i, pt); +}; + +// returns rectangle of intersection +intersect.rectAndRect = function(rect1, rect2) { + if (rect1 === true) { return new Rect(rect2); } + if (rect2 === true) { return new Rect(rect2); } + + if (intersect.rectAndRect(rect1, rect2)) { + var x1 = Math.max(rect1.x, rect2.x), + y1 = Math.max(rect1.y, rect2.y), + x2 = Math.min(rect1.x + rect1.width, rect2.x + rect2.width), + y2 = Math.min(rect1.y + rect1.height, rect2.y + rect2.height); + return new Rect(x1, y1, x2 - x1, y2 - y1); + } + return null; +}; diff --git a/packages/math/util.js b/packages/math/util.js index e71c39f..2d131f6 100644 --- a/packages/math/util.js +++ b/packages/math/util.js @@ -1,24 +1,48 @@ jsio('import lib.Enum as Enum'); /** - * @namespace + * @package math.util; + */ + +/** + * Interpolate between values a and b at the point x in the interval. */ exports.interpolate = function(a, b, x) { return a * (1 - x) + b * x; } +/** + * Return a random integer between a and b. Optionally, a random seed can be + * given. + */ + exports.random = function(a, b, rand) { return a + ((rand || Math.random)() * (b - a) | 0); } +/** + * Return a random integer between a and b, inclusive. Optionally, a random seed + * can be given. + */ + exports.randomInclusive = function(a, b, rand) { return exports.random(a, b+1, rand); } -exports.rand = Math.random; -//FIXME integer is a reserved word XXX -exports.integer = exports.truncate = function(a) { return a | 0; } + +/** + * Return a value where min <= num <= max. + */ exports.clip = function(num, min, max) { return Math.max(Math.min(num, max), min); } +/** + * Return the sign of a number. + */ + exports.sign = function (num) { return num && num / Math.abs(num); }; +/** + * Rounding a value with the given precision, given the provided rounding + * method. + */ + var round = exports.round = function(a, precision, method) { if (!method || method == round.ROUND_HALF_AWAY_FROM_ZERO) { return a.toFixed(precision); @@ -52,6 +76,14 @@ var round = exports.round = function(a, precision, method) { return (integer + round(frac * p, 0, method) / p).toFixed(precision); } -round.alt = true; +/** + * Available rounding possibilities. + */ + +Enum.call(round, 'ROUND_HALF_UP', 'ROUND_HALF_AWAY_FROM_ZERO', 'ROUND_HALF_TO_EVEN', 'ROUND_HALF_TO_ODD', 'ROUND_HALF_STOCHASTIC', 'ROUND_HALF_ALTERNATE'); + +/** + * Alternating flag for rounding strategy ROUND_HALF_ALTERNATE. + */ -Enum.call(round, 'ROUND_HALF_UP', 'ROUND_HALF_AWAY_FROM_ZERO', 'ROUND_HALF_TO_EVEN', 'ROUND_HALF_STOCHASTIC', 'ROUND_HALF_ALTERNATE'); +round.alt = true; diff --git a/packages/math2D/Circle.js b/packages/math2D/Circle.js index 12fc120..5f2b5a1 100644 --- a/packages/math2D/Circle.js +++ b/packages/math2D/Circle.js @@ -1,35 +1,6 @@ -"use import"; - -import .Point; - -/** - * @extends math2D.Point - */ -exports = Class(Point, function(supr) { - this.init = function(a, b, c) { - switch(arguments.length) { - case 0: - this.x = 0; - this.y = 0; - this.radius = 0; - break; - case 1: - case 2: - this.x = a.x || 0; - this.y = a.y || 0; - this.radius = a.radius || 0; - break; - case 3: - this.x = a; - this.y = b; - this.radius = c; - break; - } - } - - this.scale = function(s) { - supr(this, 'scale', arguments); - this.radius *= s; - return this; - } -}); \ No newline at end of file +/* +* @shim +*/ +import math.geom.Circle; +exports = math.geom.Circle; +logger.log("Warning: math2D.Circle is deprecated"); diff --git a/packages/math2D/Line.js b/packages/math2D/Line.js index 7b6b40b..0bf1d3a 100644 --- a/packages/math2D/Line.js +++ b/packages/math2D/Line.js @@ -1,39 +1,6 @@ -"use import"; - -import .Point; - -exports = Class(function() { - this.init = function(a, b, c, d) { - switch(arguments.length) { - case 0: - this.start = new Point(); - this.end = new Point(); - break; - case 1: - this.start = new Point(a.start); - this.end = new Point(a.end); - break; - case 2: - this.start = new Point(a); - this.end = new Point(b); - break; - case 3: - this.start = new Point(a); - this.end = new Point(b, c); - break; - case 4: - default: - this.start = new Point(a, b); - this.end = new Point(c, d); - break; - } - } - - this.getMagnitude = - this.getLength = function() { - var dx = this.end.x - this.start.x, - dy = this.end.y - this.start.y; - - return Math.sqrt(dx * dx + dy * dy); - } -}); +/* +* @shim +*/ +import math.geom.Line; +exports = math.geom.Line; +logger.log("Warning: math2D.Line is deprecated"); diff --git a/packages/math2D/Point.js b/packages/math2D/Point.js index 6c2970d..9e891c9 100644 --- a/packages/math2D/Point.js +++ b/packages/math2D/Point.js @@ -1,137 +1,6 @@ -var Point = exports = Class(function() { - this.init = function(a, b) { - switch(arguments.length) { - case 0: - this.x = 0; - this.y = 0; - break; - case 1: - this.x = a.x || 0; - this.y = a.y || 0; - break; - case 2: - this.x = a || 0; - this.y = b || 0; - break; - } - } - - this.rotate = function(r) { - var x = this.x, - y = this.y, - cosr = Math.cos(r), - sinr = Math.sin(r); - - this.x = x * cosr - y * sinr; - this.y = x * sinr + y * cosr; - - return this; - } - - this.translate = - this.add = function(x, y) { - if (typeof x == 'number') { - this.x += x; - this.y += y; - } else { - this.x += x.x; - this.y += x.y; - } - return this; - } - - this.subtract = function(x, y) { - if (typeof x == 'number') { - this.x -= x; - this.y -= y; - } else { - this.x -= x.x; - this.y -= x.y; - } - return this; - } - - this.scale = function(s) { - this.x *= s; - this.y *= s; - return this; - } - - this.setMagnitude = function(m) { - var theta = Math.atan2(this.y, this.x); - this.x = m * Math.cos(theta); - this.y = m * Math.sin(theta); - return this; - } - - this.normalize = function() { - var m = this.getMagnitude(); - this.x /= m; - this.y /= m; - return this; - } - - this.addMagnitude = function(m) { return this.setMagnitude(this.getMagnitude() + m); } - this.getMagnitude = function() { return Math.sqrt(this.x * this.x + this.y * this.y); } - this.getSquaredMagnitude = function() { return this.x * this.x + this.y * this.y; } - this.getDirection = - this.getAngle = function() { return Math.atan2(this.y, this.x); } - -}); - -Point.getPolarR = function(x, y) { - throw "notImplemented"; -} - -Point.getPolarTheta = function(x, y) { - var val = Math.atan2(y,x) + (Math.PI * 2); - return val > Math.PI * 2 ? val % (Math.PI * 2) : val; -} - -Point.add = Point.translate = function(a, b, c, d) { - switch(arguments.length) { - case 2: return new Point(a).add(b); - case 3: return new Point(a).add(b, c); - case 4: return new Point(a, b).add(c, d); - } -} - -Point.subtract = function(a, b, c, d) { - switch(arguments.length) { - case 2: return new Point(a).subtract(b); - case 3: return new Point(a).subtract(b, c); - case 4: return new Point(a, b).subtract(c, d); - } -} - -Point.scale = function(a, b, c) { - switch(arguments.length) { - case 2: return new Point(a).scale(b); - case 3: return new Point(a, b).scale(c); - } -} - -Point.setMagnitude = function(a, b, c) { - switch(arguments.length) { - case 2: return new Point(a).setMagnitude(c); - case 3: return new Point(a, b).setMagnitude(c); - } -} - -Point.addMagnitude = function(a, b, c) { - switch(arguments.length) { - case 2: pt = new Point(a); break; - case 3: pt = new Point(a, b); b = c; break; - } - - return pt.addMagnitude(b); -} - -Point.getMagnitude = function(a, b) { return new Point(a, b).getMagnitude(); } - -Point.rotate = function(a, b, c) { - switch(arguments.length) { - case 2: return new Point(a).rotate(b); - case 3: return new Point(a, b).rotate(c); - } -} +/* +* @shim +*/ +import math.geom.Point; +exports = math.geom.Point; +logger.log("Warning: math2D.Point is deprecated"); diff --git a/packages/math2D/Polygon.js b/packages/math2D/Polygon.js new file mode 100644 index 0000000..3e18508 --- /dev/null +++ b/packages/math2D/Polygon.js @@ -0,0 +1,6 @@ +/* +* @shim +*/ +import math.geom.Polygon; +exports = math.geom.Polygon; +logger.log("Warning: math2D.Polygon is deprecated"); diff --git a/packages/math2D/Rect.js b/packages/math2D/Rect.js index 51a78c6..f7f0152 100644 --- a/packages/math2D/Rect.js +++ b/packages/math2D/Rect.js @@ -1,120 +1,6 @@ -"use import"; - -import .intersect; -import lib.Enum; -import .Point; -import .Line; - -var Rect = exports = Class(function() { - this.init = function(a, b, c, d) { - switch(arguments.length) { - case 0: // init - this.width = this.height = this.x = this.y = 0; - break; - case 1: // copy - this.width = a.width; - this.height = a.height; - this.x = a.x; - this.y = a.y; - break; - case 2: // (x, y), (width, height) - this.x = a.x; - this.y = a.y; - this.width = b.x; - this.height = b.y; - break; - case 3: // (x, y), width, height - this.x = a.x; - this.y = a.y; - this.width = b; - this.height = c; - break; - case 4: // x, y, width, height - this.x = a; - this.y = b; - this.width = c; - this.height = d; - break; - } - } - - this.normalize = function() { - if (this.width < 0) { - this.x -= this.width; - this.width = -this.width; - } - - if (this.height < 0) { - this.y -= this.height; - this.height = -this.height; - } - return this; - } - - this.intersectRect = function (rect) { - if (intersect.rectAndRect(this, rect)) { - var x1 = this.x; - var y1 = this.y; - var x2 = this.x + this.width; - var y2 = this.y + this.height; - - this.x = Math.max(x1, rect.x), - this.y = Math.max(y1, rect.y), - this.width = Math.min(x2, rect.x + rect.width) - this.x; - this.height = Math.min(y2, rect.y + rect.height) - this.y; - } else { - this.width = 0; - this.height = 0; - } - } - - this.unionRect = function(rect) { - this.normalize(); - if (rect.normalize) { rect.normalize(); } - - var x2 = this.x + this.width, - y2 = this.y + this.height; - - var rx2 = rect.x + rect.width, - ry2 = rect.y + rect.height; - - this.x = this.x < rect.x ? this.x : rect.x; - this.y = this.y < rect.y ? this.y : rect.y; - - this.width = (x2 > rx2 ? x2 : rx2) - this.x; - this.height = (y2 > ry2 ? y2 : ry2) - this.y; - } - - this.getCorner = function(i) { - switch(i) { - case CORNERS.TOP_LEFT: - return new Point(this.x, this.y); - case CORNERS.TOP_RIGHT: - return new Point(this.x + this.width, this.y); - case CORNERS.BOTTOM_LEFT: - return new Point(this.x, this.y + this.height); - case CORNERS.BOTTOM_RIGHT: - return new Point(this.x + this.width, this.y + this.height); - } - } - - this.getSide = function(i) { - switch(i) { - case SIDES.TOP: - return new Line(this.getCorner(CORNERS.TOP_LEFT), this.getCorner(CORNERS.TOP_RIGHT)); - case SIDES.RIGHT: - return new Line(this.getCorner(CORNERS.TOP_RIGHT), this.getCorner(CORNERS.BOTTOM_RIGHT)); - case SIDES.BOTTOM: - return new Line(this.getCorner(CORNERS.BOTTOM_RIGHT), this.getCorner(CORNERS.BOTTOM_LEFT)); - case SIDES.LEFT: - return new Line(this.getCorner(CORNERS.BOTTOM_LEFT), this.getCorner(CORNERS.TOP_LEFT)); - } - } - - this.getCenter = function() { - return new Point(this.x + this.width / 2, this.y + this.height / 2); - } -}); - -var SIDES = Rect.SIDES = lib.Enum('TOP', 'BOTTOM', 'LEFT', 'RIGHT'), - CORNERS = Rect.CORNERS = lib.Enum('TOP_LEFT', 'TOP_RIGHT', 'BOTTOM_RIGHT', 'BOTTOM_LEFT'); +/* +* @shim +*/ +import math.geom.Rect; +exports = math.geom.Rect; +logger.log("Warning: math2D.Rect is deprecated"); diff --git a/packages/math2D/Vec2D.js b/packages/math2D/Vec2D.js index 9ba45e4..914ca24 100644 --- a/packages/math2D/Vec2D.js +++ b/packages/math2D/Vec2D.js @@ -1,42 +1,6 @@ -var Vec2D = exports = Class(function() { - this.init = function(opts) { - if ('angle' in opts) { - this.x = opts.magnitude * Math.cos(opts.angle); - this.y = opts.magnitude * Math.sin(opts.angle); - } else { - this.x = opts.x; - this.y = opts.y; - } - } - - this.addForce = function(f) { this.x += f.x; this.y += f.y; } - this.getAngle = function() { return Math.atan2(this.y, this.x); } - this.getMagnitude = function() { return Math.sqrt(this.x * this.x + this.y * this.y); } - this.getUnitVector = function() { - var angle = this.getAngle(); - return new Vec2D({ - magnitude:1, - angle: angle});} - - this.dot = function(vec) { - return (this.x * vec.x) + (this.y * vec.y); - } - - this.add = function(vec) { - return new Vec2D({x:this.x + vec.x, y:this.y+vec.y}); - } - - this.minus = function(vec) { - return new Vec2D({x:this.x-vec.x, y:this.y-vec.y}); - } - - this.negate = function() { - return new Vec2D({x:-this.x, y:-this.y}); - } - - this.multiply = function(scalar) { - return new Vec2D({angle:this.getAngle(), magnitude:this.getMagnitude()*scalar}); - } - -}); - +/* +* @shim +*/ +import math.geom.Vec2D; +exports = math.geom.Vec2D; +logger.log("Warning: math2D.Vec2D is deprecated"); diff --git a/packages/math2D/angle.js b/packages/math2D/angle.js index d0759f7..3b9de2d 100644 --- a/packages/math2D/angle.js +++ b/packages/math2D/angle.js @@ -1,42 +1,6 @@ -/** - * @namespace - */ - -var PI = Math.PI, - TWOPI = Math.PI * 2; - -exports.average = function(a, b, weight) { - if (weight === undefined) { weight = 0.5; } - var r1 = exports.getRange(a, b); - var avg = r1 < PI - ? a + r1 * (1 - weight) - : b + (2 * PI - r1) * weight; - - return avg > PI ? avg - 2 * PI : avg < -PI ? avg + 2 * PI : avg; -} - -// between -PI and PI -exports.normalize = function(a) { - - // TODO: don't use loops - while(a < -PI) { a += 2 * PI; } - while(a > PI) { a -= 2 * PI; } - return a; -} - -exports.add = function(a, b) { - var sum = a + b; - return sum > PI ? sum - TWOPI : sum < -PI ? sum + TWOPI : sum; -} - -// smaller of two angles a - b, b - a -exports.difference = function(a, b) { - var diff = exports.getRange(a, b); - return diff > PI ? diff - TWOPI : diff; -} - -// angular range from a to b, returns float between [0, 2PI] -exports.getRange = function(a, b) { - var r = b - a; - return r < 0 ? r + TWOPI : r; -} +/* +* @shim +*/ +import math.geom.angle; +exports = math.geom.angle; +logger.log("Warning: math2D.angle is deprecated"); diff --git a/packages/math2D/intersect.js b/packages/math2D/intersect.js index 032691c..d5495c8 100644 --- a/packages/math2D/intersect.js +++ b/packages/math2D/intersect.js @@ -1,92 +1,6 @@ -"use import"; - -import .Point; -import .Line; -import .Rect; - -/** - * @namespace - */ -var intersect = exports; - -intersect.rectAndPt = function(rect, pt) { return intersect.ptAndRect(pt, rect); } -intersect.ptAndRect = function(pt, rect) { - var x = pt.x, - y = pt.y; - - return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height; -} - -intersect.circAndPt = function(circ, pt) { return intersect.ptAndCirc(pt, circ); } -intersect.ptAndCirc = function(pt, circ) { - var dx = pt.x - circ.x, - dy = pt.y - circ.y; - return dx * dx + dy * dy < circ.radius * circ.radius; -} - -intersect.rectAndRect = function(rect1, rect2) { - return !( - (rect1.y + rect1.height < rect2.y) - || (rect2.y + rect2.height < rect1.y) - || (rect1.x + rect1.width < rect2.x) - || (rect2.x + rect2.width < rect1.x) - ); -} - -var SIDES = Rect.SIDES; - -intersect.rectAndCircle = function(rect, circle) { return intersect.rectAndCircle(circle, circ); } -intersect.circleAndRect = function(circle, rect) { - if (intersect.ptAndRect(circle, rect)) { - return true; - } - - return intersect.lineAndCircle(rect.getSide(1), circle) - || intersect.lineAndCircle(rect.getSide(2), circle) - || intersect.lineAndCircle(rect.getSide(3), circle) - || intersect.lineAndCircle(rect.getSide(4), circle); -} - -intersect.circleAndLine = function(circle, line) { return intersect.lineAndCircle(line, circle); } -intersect.lineAndCircle = function(line, circle) { - var vec = intersect.util.ptToLine(circle, line); - return vec.getMagnitude() < circle.radius; -} - -// util -- does not return a true/false intersection - -intersect.util = {}; - -// returns line from pt to nearest pt on line -intersect.util.ptToLine = function(pt, line) { - var dx = (line.end.x - line.start.x), - dy = (line.end.y - line.start.y), - u = ((pt.x - line.start.x) * dx // TODO can we abstract this from 2D to 2D/3D? - + (pt.y - line.start.y) * dy) / - (dx * dx + dy * dy); - - var i; - if (u < 0) { - i = new Point(line.start); - } else if (u > 1) { - i = new Point(line.end); - } else { - i = new Point(line.start.x + u * dx, line.start.y + u * dy); - } - return new Line(i, pt); -} - -// returns rectangle of intersection -intersect.util.rectAndRect = function(rect1, rect2) { - if (rect1 === true) { return new Rect(rect2); } - if (rect2 === true) { return new Rect(rect2); } - - if (intersect.rectAndRect(rect1, rect2)) { - var x1 = Math.max(rect1.x, rect2.x), - y1 = Math.max(rect1.y, rect2.y), - x2 = Math.min(rect1.x + rect1.width, rect2.x + rect2.width), - y2 = Math.min(rect1.y + rect1.height, rect2.y + rect2.height); - return new Rect(x1, y1, x2 - x1, y2 - y1); - } - return null; -} +/* +* @shim +*/ +import math.geom.intersect; +exports = math.geom.intersect; +logger.log("Warning: math2D.intersect is deprecated"); diff --git a/packages/math2D/morphology.js b/packages/math2D/morphology.js index 9b6ab81..9e07e4c 100644 --- a/packages/math2D/morphology.js +++ b/packages/math2D/morphology.js @@ -1,118 +1,6 @@ -/** - * @namespace - */ - -exports.open = function(img, mask) { - return exports.dilate(exports.erode(img, mask), mask); -} - -exports.close = function(img, mask) { - return exports.erode(exports.dilate(img, mask), mask); -} - -exports.getSquareMask = function(d) { - if (d % 2 == 0) { d++; } - var mask = []; - for (var x = 0; x < d; ++x) { - mask[x] = []; - for (var y = 0; y < d; ++y) { - mask[x][y] = 1; - } - } - return mask; -} - -exports.getCircleMask = function(d) { - if (d % 2 == 0) { d++; } - var mask = [], - r = d / 2 - 0.5, - c = r; - for (var y = 0; y < d; ++y) { - mask[y] = []; - for (var x = 0; x < d; ++x) { - var dx = x - c, dy = y - c; - mask[y][x] = Math.sqrt(dx * dx + dy * dy) <= r ? 1 : 0; - } - } - return mask; -} - -exports.multiply = function(img1, c) { - var w = img1.width, h = img1.height; - for (var y = 0; y < h; ++y) { - for (var x = 0; x < w; ++x) { - img1[y][x] *= c; - } - } - return img1; -} - -exports.subtract = function(img1, img2) { - var w = Math.min(img1.width, img2.width), - h = Math.min(img1.height, img2.height); - - for (var y = 0; y < h; ++y) { - for (var x = 0; x < w; ++x) { - img1[y][x] -= img2[y][x]; - } - } - return img1; -} - -exports.erode = function(img, mask) { - logger.info('erode'); - - var lmx = (mask.length - 1) / 2, - lmy = (mask[0].length - 1) / 2, - out = [], - w = img.width, - h = img.height; - - out.width = w; - out.height = h; - - for (var y = 0; y < h; ++y) { - out[y] = []; - for (var x = 0; x < w; ++x) { - var count = true; - for (var mx = -lmx; mx < lmx + 1; ++mx) { - for (var my = -lmy; my < lmy + 1; ++my) { - var sx = x + mx, sy = y + my; - mask[my + lmy][mx + lmx] - && (count &= sx >= 0 && sx < w && sy >= 0 && sy < h && img[sy][sx]); - } - } - out[y][x] = count && 255; - } - } - return out; -} - -exports.dilate = function (img, mask) { - logger.info('dilate'); - - var lmx = (mask.length - 1) / 2, - lmy = (mask[0].length - 1) / 2, - out = [], - w = img.width, - h = img.height; - - out.width = w; - out.height = h; - - for (var y = 0; y < h; ++y) { - out[y] = []; - for (var x = 0; x < w; ++x) { - var count = false; - for (var mx = -lmx; mx < lmx + 1; ++mx) { - for (var my = -lmy; my < lmy + 1; ++my) { - var sx = x + mx, sy = y + my; - count |= sx >= 0 && sx < w && sy >= 0 && sy < h - && mask[my + lmy][mx + lmx] && img[sy][sx]; - } - } - out[y][x] = count && 255; - } - } - return out; -} +/* +* @shim +*/ +import math.geom.morphology; +exports = math.geom.morphology; +logger.log("Warning: math2D.morphology is deprecated");