summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSaivan <savian@me.com>2018-03-03 22:08:26 +1100
committerSaivan <savian@me.com>2018-03-03 22:08:26 +1100
commite065a4415b7d6991ac14de81646f109e43bef9e7 (patch)
tree03a40cfdd89b8109bcffd871f523a2e516918a4d /src
parent8991bd195817c38e76bdf15accf16cf321ba84cf (diff)
downloadsvg.js-e065a4415b7d6991ac14de81646f109e43bef9e7.tar.gz
svg.js-e065a4415b7d6991ac14de81646f109e43bef9e7.zip
Added matrix composition and decompositions
This commit adds matrix composition and decompositions (untested), it also adds another playground to test that this is working as expected in every case. We also fixed a few linting errors.
Diffstat (limited to 'src')
-rw-r--r--src/helpers.js17
-rw-r--r--src/matrix.js101
-rw-r--r--src/sugar.js17
-rw-r--r--src/transform.js15
4 files changed, 107 insertions, 43 deletions
diff --git a/src/helpers.js b/src/helpers.js
index 16e6e4e..5a15bf9 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -209,21 +209,6 @@ function idFromReference (url) {
// Create matrix array for looping
var abcdef = 'abcdef'.split('')
-// Gets the distance of a point (a, b) from the origin
-function mag (a, b) {
- return Math.sqrt(a * a + b * b)
-}
-
-// Given a coordinate (a, b), this will calculate the sin, cosine and angle
-// of this point projected onto the unit circle directly
-function unitCircle (a, b) {
- var thetaRad = Math.atan2(b, a)
- var thetaDeg = thetaRad * 180 / Math.PI
- var cos = Math.cos(thetaRad)
- var sin = Math.sin(thetaRad)
- return {theta: thetaDeg, cos: cos, sin: sin}
-}
-
function closeEnough (a, b, threshold) {
- return Math.abs (b - a) < (threshold || 1e-6)
+ return Math.abs(b - a) < (threshold || 1e-6)
}
diff --git a/src/matrix.js b/src/matrix.js
index 557010c..ed1485c 100644
--- a/src/matrix.js
+++ b/src/matrix.js
@@ -1,4 +1,4 @@
-/* global abcdef, arrayToMatrix, parseMatrix, unitCircle, mag */
+/* global abcdef, arrayToMatrix, parseMatrix, closeEnough */
SVG.Matrix = SVG.invent({
// Initialize
@@ -25,12 +25,13 @@ SVG.Matrix = SVG.invent({
// Add methods
extend: {
+ // Clones this matrix
clone: function () {
return new SVG.Matrix(this)
},
- // Clone matrix
- affine: function (o) {
+ // 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
@@ -58,6 +59,9 @@ SVG.Matrix = SVG.invent({
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()
@@ -67,35 +71,103 @@ SVG.Matrix = SVG.invent({
.shear(shear)
.rotate(theta)
.translate(ox, oy)
- .translate(tx, ty)
- .lmultiply(new SVG.Matrix(this))
+ .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 - tx, oy - ty).transform(transformer)
+ 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
+ var sy = o.scaleY
+ var lam = o.shear
+ var theta = o.rotate
+ var tx = o.translateX
+ var ty = o.translateY
+
+ // 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 {
+ // Return the affine parameters
+ scaleX: sx,
+ scaleY: sy,
+ shear: lam,
+ rotate: theta,
+ translateX: e,
+ translateY: f,
+
+ // Return the matrix parameters
+ matrix: this,
+ a: this.a,
+ b: this.b,
+ c: this.c,
+ d: this.d,
+ e: this.e,
+ f: this.f,
+ }
+ },
+
// 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,
@@ -182,7 +254,7 @@ SVG.Matrix = SVG.invent({
// Shear matrix
shear: function (a, cx, cy) {
- var shear = new SVG.Matrix(1, a, 0, 1, 0, 0)
+ var shear = new SVG.Matrix(1, 0, a, 1, 0, 0)
var matrix = this.around(cx, cy, shear)
return matrix
},
@@ -241,12 +313,9 @@ SVG.Matrix = SVG.invent({
// 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)
+ 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
diff --git a/src/sugar.js b/src/sugar.js
index 6f81898..b1c6f38 100644
--- a/src/sugar.js
+++ b/src/sugar.js
@@ -46,6 +46,10 @@ SVG.extend([SVG.Element, SVG.FX], {
: this.transform({skew: [x, y], origin: [cx, cy]}, true)
},
+ shear: function (lam, cx, cy) {
+ return this.transform({shear: lam, origin: [cx, cy]}, true)
+ },
+
// Map scale to transform
scale: function (x, y, cx, cy) {
return arguments.length === 1 || arguments.length === 3
@@ -58,13 +62,18 @@ SVG.extend([SVG.Element, SVG.FX], {
return this.transform({ translate: [x, y] }, true)
},
+ // Map relative translations to transform
+ relative: function (x, y) {
+ return this.transform({ relative: [x, y] }, true)
+ },
+
// Map flip to transform
flip: function (direction, around) {
- var origin = (direction === "both" && isFinite(around)) ? [around, around]
- : (direction === "x") ? [around, 0]
- : (direction === "y") ? [0, around]
+ var origin = (direction === 'both' && isFinite(around)) ? [around, around]
+ : (direction === 'x') ? [around, 0]
+ : (direction === 'y') ? [0, around]
: [0, 0]
- this.transform({flip: direction || "both", origin: origin}, true)
+ this.transform({flip: direction || 'both', origin: origin}, true)
},
// Opacity
diff --git a/src/transform.js b/src/transform.js
index fbc75c4..f14917d 100644
--- a/src/transform.js
+++ b/src/transform.js
@@ -21,8 +21,9 @@ SVG.extend(SVG.Element, {
.reverse()
// merge every transformation into one matrix
.reduce(function (matrix, transform) {
- if (transform[0] === 'matrix')
+ if (transform[0] === 'matrix') {
return matrix.lmultiply(arrayToMatrix(transform[1]))
+ }
return matrix[transform[0]].apply(matrix, transform[1])
}, new SVG.Matrix())
@@ -55,11 +56,10 @@ SVG.extend(SVG.Element, {
// Act as a getter if no object was passed
if (o == null) {
- return new SVG.Matrix(this)
+ return new SVG.Matrix(this).decompose()
// Let the user pass in a matrix as well
} else if (o.a != null) {
-
// Construct a matrix from the first parameter
var matrix = new SVG.Matrix(o)
@@ -73,8 +73,9 @@ SVG.extend(SVG.Element, {
return this.attr('transform', matrix)
// Allow the user to define the origin with a string
- } else if (typeof o.origin === 'string'
- || (o.origin == null && o.ox == null && o.oy == null)) {
+ } else if (typeof o.origin === 'string' ||
+ (o.origin == null && o.ox == null && o.oy == null)
+ ) {
// Get the bounding box and string to use in our calculations
var string = typeof o.origin === 'string'
? o.origin.toLowerCase().trim()
@@ -97,12 +98,12 @@ SVG.extend(SVG.Element, {
}
// The user can pass a boolean, an SVG.Element or an SVG.Matrix or nothing
- var result = new SVG.Matrix(cyOrRel === true ? this : cyOrRel).affine(o)
+ var result = new SVG.Matrix(cyOrRel === true ? this : cyOrRel).transform(o)
var matrixString = result.toString()
// Apply the result directly to this matrix
return this.attr('transform', matrixString)
- },
+ }
})
SVG.extend(SVG.FX, {