diff options
Diffstat (limited to 'src/matrix.js')
-rw-r--r-- | src/matrix.js | 250 |
1 files changed, 202 insertions, 48 deletions
diff --git a/src/matrix.js b/src/matrix.js index e823a81..787b06b 100644 --- a/src/matrix.js +++ b/src/matrix.js @@ -1,4 +1,4 @@ -/* global abcdef, arrayToMatrix, deltaTransformPoint, parseMatrix */ +/* global abcdef, arrayToMatrix, parseMatrix, closeEnough */ SVG.Matrix = SVG.invent({ // Initialize @@ -24,53 +24,150 @@ SVG.Matrix = SVG.invent({ // Add methods extend: { - // Extract individual transformations - extract: function () { - // find delta transform points - var px = deltaTransformPoint(this, 0, 1) - var py = deltaTransformPoint(this, 1, 0) - var skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90 + // Clones this matrix + clone: function () { + return new SVG.Matrix(this) + }, + + // Transform a matrix into another matrix by manipulating the space + transform: function (o) { + // Get all of the parameters required to form the matrix + var flipX = o.flip && (o.flip === 'x' || o.flip === 'both') ? -1 : 1 + var flipY = o.flip && (o.flip === 'y' || o.flip === 'both') ? -1 : 1 + var skewX = o.skew && o.skew.length ? o.skew[0] + : isFinite(o.skew) ? o.skew + : isFinite(o.skewX) ? o.skewX + : 0 + var skewY = o.skew && o.skew.length ? o.skew[1] + : isFinite(o.skew) ? o.skew + : isFinite(o.skewY) ? o.skewY + : 0 + var scaleX = o.scale && o.scale.length ? o.scale[0] * flipX + : isFinite(o.scale) ? o.scale * flipX + : isFinite(o.scaleX) ? o.scaleX * flipX + : flipX + var scaleY = o.scale && o.scale.length ? o.scale[1] * flipY + : isFinite(o.scale) ? o.scale * flipY + : isFinite(o.scaleY) ? o.scaleY * flipY + : flipY + var shear = o.shear || 0 + var theta = o.rotate || 0 + var ox = o.origin && o.origin.length ? o.origin[0] : o.ox || 0 + var oy = o.origin && o.origin.length ? o.origin[1] : o.oy || 0 + var px = o.position && o.position.length ? o.position[0] : o.px + var py = o.position && o.position.length ? o.position[1] : o.py + var tx = o.translate && o.translate.length ? o.translate[0] : o.tx || 0 + var ty = o.translate && o.translate.length ? o.translate[1] : o.ty || 0 + var rx = o.relative && o.relative.length ? o.relative[0] : o.rx || 0 + var ry = o.relative && o.relative.length ? o.relative[1] : o.ry || 0 + var currentTransform = new SVG.Matrix(this) + + // Construct the resulting matrix + var transformer = new SVG.Matrix() + .translate(-ox, -oy) + .scale(scaleX, scaleY) + .skew(skewX, skewY) + .shear(shear) + .rotate(theta) + .translate(ox, oy) + .translate(rx, ry) + .lmultiply(currentTransform) + + // If we want the origin at a particular place, we force it there + if (isFinite(px) && isFinite(py)) { + // Figure out where the origin went and the delta to get there + var p = new SVG.Point(ox - rx, oy - ry).transform(transformer) + var dx = px - p.x + var dy = py - p.y + + // Apply another translation + transformer = transformer.translate(dx, dy) + } + + // We can apply translations after everything else + transformer = transformer.translate(tx, ty) + return transformer + }, + + // Applies a matrix defined by its affine parameters + compose: function (o) { + // Get the parameters + var sx = o.scaleX || 1 + var sy = o.scaleY || 1 + var lam = o.shear || 0 + var theta = o.rotate || 0 + var tx = o.translateX || 0 + var ty = o.translateY || 0 + + // Apply the standard matrix + var result = new SVG.Matrix() + .scale(sx, sy) + .shear(lam) + .rotate(theta) + .translate(tx, ty) + .lmultiply(this) + return result + }, + + // Decomposes this matrix into its affine parameters + decompose: function () { + // Get the parameters from the matrix + var a = this.a + var b = this.b + var c = this.c + var d = this.d + var e = this.e + var f = this.f + + // Figure out if the winding direction is clockwise or counterclockwise + var determinant = a * d - b * c + var ccw = determinant > 0 ? 1 : -1 + + // Since we only shear in x, we can use the x basis to get the x scale + // and the rotation of the resulting matrix + var sx = ccw * Math.sqrt(a * a + b * b) + var theta = 180 / Math.PI * Math.atan2(ccw * b, ccw * a) + + // We can then solve the y basis vector simultaneously to get the other + // two affine parameters directly from these parameters + var lam = (a * c + b * d) / determinant + var sy = ((c * sx) / (lam * a - b)) || ((d * sx) / (lam * b + a)) + + // Construct the decomposition and return it return { - // translation - x: this.e, - y: this.f, - transformedX: (this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b), - transformedY: (this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d), - // skew - skewX: -skewX, - skewY: 180 / Math.PI * Math.atan2(py.y, py.x), - // scale - scaleX: Math.sqrt(this.a * this.a + this.b * this.b), - scaleY: Math.sqrt(this.c * this.c + this.d * this.d), - // rotation - rotation: skewX, + // Return the affine parameters + scaleX: sx, + scaleY: sy, + shear: lam, + rotate: theta, + translateX: e, + translateY: f, + + // Return the matrix parameters + matrix: new SVG.Matrix(this), a: this.a, b: this.b, c: this.c, d: this.d, e: this.e, - f: this.f, - matrix: new SVG.Matrix(this) + f: this.f } }, - // Clone matrix - clone: function () { - return new SVG.Matrix(this) - }, + // Morph one matrix into another morph: function (matrix) { - // store new destination + // Store new destination this.destination = new SVG.Matrix(matrix) - return this }, + // Get morphed matrix at a given position at: function (pos) { - // make sure a destination is defined + // Make sure a destination is defined if (!this.destination) return this - // calculate morphed matrix at a given position + // Calculate morphed matrix at a given position var matrix = new SVG.Matrix({ a: this.a + (this.destination.a - this.a) * pos, b: this.b + (this.destination.b - this.b) * pos, @@ -82,21 +179,47 @@ SVG.Matrix = SVG.invent({ return matrix }, - // Multiplies by given matrix + + // Left multiplies by the given matrix multiply: function (matrix) { - return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native())) + // Get the matrices + var l = this + var r = parseMatrix(matrix) + + // Work out the product directly + var a = l.a * r.a + l.c * r.b + var b = l.b * r.a + l.d * r.b + var c = l.a * r.c + l.c * r.d + var d = l.b * r.c + l.d * r.d + var e = l.e + l.a * r.e + l.c * r.f + var f = l.f + l.b * r.e + l.d * r.f + + // Form the matrix and return it + var product = new SVG.Matrix(a, b, c, d, e, f) + return product }, + + lmultiply: function (matrix) { + var l = parseMatrix(matrix) + return l.multiply(this) + }, + // Inverses matrix inverse: function () { return new SVG.Matrix(this.native().inverse()) }, + // Translate matrix translate: function (x, y) { - return new SVG.Matrix(this.native().translate(x || 0, y || 0)) + var translation = new SVG.Matrix(this) + translation.e += x || 0 + translation.f += y || 0 + return translation }, + // Scale matrix scale: function (x, y, cx, cy) { - // support uniformal scale + // Support uniform scaling if (arguments.length === 1) { y = x } else if (arguments.length === 3) { @@ -105,22 +228,38 @@ SVG.Matrix = SVG.invent({ y = x } - return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0)) + // Rotate the current matrix + var scale = new SVG.Matrix(x, 0, 0, y, 0, 0) + var matrix = this.around(cx, cy, scale) + return matrix }, + // Rotate matrix rotate: function (r, cx, cy) { - // convert degrees to radians + // Convert degrees to radians r = SVG.utils.radians(r) - return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0)) + // Construct the rotation matrix + var rotation = new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0) + var matrix = this.around(cx, cy, rotation) + return matrix }, + // Flip matrix on x or y, at a given offset - flip: function (a, o) { - return a === 'x' ? this.scale(-1, 1, o, 0) - : a === 'y' ? this.scale(1, -1, 0, o) - : this.scale(-1, -1, a, o != null ? o : a) + flip: function (axis, around) { + return axis === 'x' ? this.scale(-1, 1, around, 0) + : axis === 'y' ? this.scale(1, -1, 0, around) + : this.scale(-1, -1, axis, around || axis) // Define an x, y flip point }, - // Skew + + // Shear matrix + shear: function (a, cx, cy) { + var shear = new SVG.Matrix(1, 0, a, 1, 0, 0) + var matrix = this.around(cx, cy, shear) + return matrix + }, + + // Skew Matrix skew: function (x, y, cx, cy) { // support uniformal skew if (arguments.length === 1) { @@ -131,27 +270,33 @@ SVG.Matrix = SVG.invent({ y = x } - // convert degrees to radians + // Convert degrees to radians x = SVG.utils.radians(x) y = SVG.utils.radians(y) - return this.around(cx, cy, new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0)) + // Construct the matrix + var skew = new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0) + var matrix = this.around(cx, cy, skew) + return matrix }, + // SkewX skewX: function (x, cx, cy) { return this.skew(x, 0, cx, cy) }, + // SkewY skewY: function (y, cx, cy) { return this.skew(0, y, cx, cy) }, + // Transform around a center point around: function (cx, cy, matrix) { - return this - .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0)) - .multiply(matrix) - .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0)) + var dx = cx || 0 + var dy = cy || 0 + return this.translate(-dx, -dy).lmultiply(matrix).translate(dx, dy) }, + // Convert to native SVGMatrix native: function () { // create new matrix @@ -164,6 +309,15 @@ SVG.Matrix = SVG.invent({ return matrix }, + + // Check if two matrices are equal + equals: function (other) { + var comp = parseMatrix(other) + return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) && + closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) && + closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f) + }, + // Convert matrix to string toString: function () { return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')' |