From d3dd6ad74b35e674ab24a8d9bf0c3b2f336b5bca Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ulrich-Matthias=20Sch=C3=A4fer?= Date: Mon, 8 Oct 2018 15:18:53 +0200 Subject: [PATCH] Optimized the matrix functions so that the transform function is a multitude faster for parameterized input --- bench/runner.html | 12 ++- bench/tests/10000-transform.js | 32 +++++++ dirty.html | 48 +++------- spec/SpecRunner.html | 23 +++-- spec/spec/matrix.js | 165 +++++++-------------------------- src/helpers.js | 22 +++++ src/matrix.js | 122 +++++++++++++++--------- src/runner.js | 13 ++- 8 files changed, 209 insertions(+), 228 deletions(-) create mode 100644 bench/tests/10000-transform.js diff --git a/bench/runner.html b/bench/runner.html index 6d1bed2..b39c1df 100644 --- a/bench/runner.html +++ b/bench/runner.html @@ -38,6 +38,13 @@
+ + + + + + + @@ -45,8 +52,9 @@ - --> - + + --> + diff --git a/bench/tests/10000-transform.js b/bench/tests/10000-transform.js new file mode 100644 index 0000000..0fcc162 --- /dev/null +++ b/bench/tests/10000-transform.js @@ -0,0 +1,32 @@ +SVG.bench.describe('Transform 1000000 rects', function(bench) { + let parameters = { + translate: [20, 30], + origin: [100, 100], + rotate: 25, + skew: [10, 30], + scale: 0.5 + } + + let matrixLike = {a:2, b:3, c:1, d:2, e:49, f:100} + let matrix = new SVG.Matrix(matrixLike) + + let worker = new SVG.Matrix() + bench.test('with parameters', function() { + for (var i = 0; i < 1000000; i++) + worker.transform(parameters) + }) + + worker = new SVG.Matrix() + bench.test('with matrix like', function() { + for (var i = 0; i < 1000000; i++) { + worker.transform(matrixLike) + } + }) + + worker = new SVG.Matrix() + bench.test('with SVG.Matrix', function() { + for (var i = 0; i < 1000000; i++) + worker.transform(matrix) + }) +}) + diff --git a/dirty.html b/dirty.html index aa270f0..ffd7225 100644 --- a/dirty.html +++ b/dirty.html @@ -249,51 +249,29 @@ let canvas = SVG('#canvas') /** * FUZZYS EXAMPLE */ -// // // Make the pink rectangle // let a = canvas.rect(200, 400).move(500, 400) // .attr('opacity', 0.3) // .addClass('pink') // .transform({ tx: 300, ty: 500, origin: 'top-left' }) // -// -// var timer = 0 -// a.timeline().source(() => { -// timer += 1 -// document.querySelector('#absolute span').textContent = timer -// return timer -// }) -// // let obj = { rotate: 100, origin: 'top-left'} // let obj2 = { rotate: 280, origin: 'center' } // let obj3 = { rotate: 1000, origin: 'center', translate: [300, 200]} // // -// // var c = a.clone() -// // var d = a.clone() -// // -// // -// // c.animate(1000) -// // //.transform(obj) -// // .transform(obj, true) // animation -// // -// // d.animate(3000) -// // //.transform(obj) -// // .transform(obj, true) // animation -// -// // a.clone().attr('fill', 'blue') -// // //.transform(obj) -// // .transform(obj2, true) // endPosition +// var c = a.clone() // -// window.EL = canvas.ellipse(50, 50) -// -// let b = a.clone() -// let bA = b.animate(new SVG.Spring(1000, 15)) -// -// SVG.on(document, 'mousemove', (e) => { -// let {x, y} = canvas.point(e.clientX, e.clientY) -// bA.transform ({tx: x, ty: y, rotate: (x + y) / 3}, true) -// }) +// c.animate(3000) +// .transform(obj) +// .transform(obj2, true) // animation // +// a.clone().attr('fill', 'blue') +// .transform(obj) +// .transform(obj2, true) // endPosition + + +// SAIVANS EXAMPLE + canvas.ellipse(20, 20).center(100, 100) let r = canvas.rect(200, 400).move(100, 100) @@ -353,13 +331,13 @@ setTimeout(runTransformation({ // canvas.circle(50).center(100, 0).attr('fill', 'gray') // // setTimeout(runTransformation({ -// rotate: 180, +// rotate: 360, // origin: [100, 0] // }), 0.1 * wait ) // // // setTimeout(runTransformation({ -// rotate: 180, +// rotate: 360, // }), 0.4 * wait) // const getConsole = (time) => { diff --git a/spec/SpecRunner.html b/spec/SpecRunner.html index 5e70186..efc58a3 100644 --- a/spec/SpecRunner.html +++ b/spec/SpecRunner.html @@ -16,10 +16,13 @@ - - - - + + + + + + + @@ -74,15 +77,15 @@ - + --> - + - + - + + --> diff --git a/spec/spec/matrix.js b/spec/spec/matrix.js index 543ac0e..42ac1fe 100644 --- a/spec/spec/matrix.js +++ b/spec/spec/matrix.js @@ -1,116 +1,60 @@ describe('Matrix', function() { - var matrix + let comp = {a:2, b:0, c:0, d:2, e:100, f:50} describe('initialization', function() { - describe('without a source', function() { - - beforeEach(function() { - matrix = new SVG.Matrix - }) - - it('creates a new matrix with default values', function() { - expect(matrix.a).toBe(1) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(0) - expect(matrix.f).toBe(0) - }) - - describe('toString()' , function() { - it('exports correctly to a string', function() { - expect(matrix.toString()).toBe('matrix(1,0,0,1,0,0)') - }) - }) + it('creates a new matrix with default values', function() { + let matrix = new SVG.Matrix() + expect(matrix).toEqual(jasmine.objectContaining( + {a:1, b:0, c:0, d:1, e:0, f:0} + )) }) - describe('with an element given', function() { - var rect - - beforeEach(function() { - // Draw is defined in helpers - rect = draw.rect(100, 100) - .transform({ - rotate: -10, - translate: [40, 50], - scale: 2, - }) - matrix = new SVG.Matrix(rect) - }) - - it('parses the current transform matrix from an element', function() { - - expect(matrix.a).toBeCloseTo(1.969615506024416) - expect(matrix.b).toBeCloseTo(-0.34729635533386066) - expect(matrix.c).toBeCloseTo(0.34729635533386066) - expect(matrix.d).toBeCloseTo(1.969615506024416) - expect(matrix.e).toBeCloseTo(-25.84559306791384) - expect(matrix.f).toBeCloseTo(18.884042465472234) - }) - + it('parses the current transform matrix from an element', function() { + let rect = draw.rect(100, 100).transform(comp) + let matrix = new SVG.Matrix(rect) + expect(matrix).toEqual(jasmine.objectContaining(comp)) }) - describe('with a string given', function() { - it('parses the string value correctly', function() { - var matrix = new SVG.Matrix('2, 0, 0, 2, 100, 50') - - expect(matrix.a).toBe(2) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(2) - expect(matrix.e).toBe(100) - expect(matrix.f).toBe(50) - }) + it('parses a string value correctly', function() { + let matrix = new SVG.Matrix('2, 0, 0, 2, 100, 50') + expect(matrix).toEqual(jasmine.objectContaining(comp)) }) - describe('with an array given', function() { - it('parses the array correctly', function() { - var matrix = new SVG.Matrix([2, 0, 0, 2, 100, 50]) - - expect(matrix.a).toBe(2) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(2) - expect(matrix.e).toBe(100) - expect(matrix.f).toBe(50) - }) + it('parses an array correctly', function() { + let matrix = new SVG.Matrix([2, 0, 0, 2, 100, 50]) + expect(matrix).toEqual(jasmine.objectContaining(comp)) }) - describe('with an object given', function() { - it('parses the object correctly', function() { - var matrix = new SVG.Matrix({a:2, b:0, c:0, d:2, e:100, f:50}) - - expect(matrix.a).toBe(2) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(2) - expect(matrix.e).toBe(100) - expect(matrix.f).toBe(50) - }) + it('parses an object correctly', function() { + let matrix = new SVG.Matrix(comp) + expect(matrix).toEqual(jasmine.objectContaining(comp)) }) - describe('with 6 arguments given', function() { - it('parses the arguments correctly', function() { - var matrix = new SVG.Matrix(2, 0, 0, 2, 100, 50) + it('parses a transform object correctly', function() { + let matrix = new SVG.Matrix({scale: 2, translate: [100, 50]}) + expect(matrix).toEqual(jasmine.objectContaining(comp)) + }) - expect(matrix.a).toBe(2) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(2) - expect(matrix.e).toBe(100) - expect(matrix.f).toBe(50) - }) + it('parses 6 arguments correctly', function() { + let matrix = new SVG.Matrix(2, 0, 0, 2, 100, 50) + expect(matrix).toEqual(jasmine.objectContaining(comp)) }) + }) + describe('toString()' , function() { + it('exports correctly to a string', function() { + expect(new SVG.Matrix().toString()).toBe('matrix(1,0,0,1,0,0)') + }) }) describe('compose()', function() { it('composes a matrix to form the correct result', function() { - var composed = new SVG.Matrix().compose({ + let composed = new SVG.Matrix().compose({ scaleX: 3, scaleY: 20, shear: 4, rotate: 50, translateX: 23, translateY: 52, }) - var expected = new SVG.Matrix().scale(3, 20).shear(4).rotate(50).translate(23, 52) + + let expected = new SVG.Matrix().scale(3, 20).shear(4).rotate(50).translate(23, 52) expect(composed).toEqual(expected) }) }) @@ -151,43 +95,8 @@ describe('Matrix', function() { }) }) - describe('morph()', function() { - it('stores a given matrix for morphing', function() { - var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) - , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) - - matrix1.morph(matrix2) - - expect(matrix1.destination).toEqual(matrix2) - }) - it('stores a clone, not the given matrix itself', function() { - var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) - , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) - - matrix1.morph(matrix2) - - expect(matrix1.destination).not.toBe(matrix2) - }) - }) - - describe('at()', function() { - it('returns a morphed array at a given position', function() { - var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) - , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) - , matrix3 = matrix1.morph(matrix2).at(0.5) - - expect(matrix1.toString()).toBe('matrix(2,0,0,5,0,0)') - expect(matrix2.toString()).toBe('matrix(1,0,0,1,4,3)') - expect(matrix3.toString()).toBe('matrix(1.5,0,0,3,2,1.5)') - }) - it('returns itself when no destination specified', function() { - var matrix = new SVG.Matrix(2, 0, 0, 5, 0, 0) - expect(matrix.at(0.5)).toBe(matrix) - }) - }) - describe('multiply()', function() { - it('multiplies two matices', function() { + it('multiplies two matrices', function() { var matrix1 = new SVG.Matrix(1, 4, 2, 5, 3, 6) , matrix2 = new SVG.Matrix(7, 8, 8, 7, 9, 6) , matrix3 = matrix1.multiply(matrix2) @@ -211,9 +120,7 @@ describe('Matrix', function() { var matrix1 = new SVG.Matrix(2, 0, 0, 5, 4, 3) , matrix2 = matrix1.inverse() - , abcdef = [0.5,0,0,0.2,-2,-0.6] - - expect(matrix1.toString()).toBe('matrix(2,0,0,5,4,3)') + , abcdef = [0.5, 0, 0, 0.2, -2, -0.6] for(var i in 'abcdef') { expect(matrix2['abcdef'[i]]).toBeCloseTo(abcdef[i]) diff --git a/src/helpers.js b/src/helpers.js index 4b55897..fc22e4c 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -260,6 +260,28 @@ function formatTransforms (o) { } +// left matrix, right matrix, target matrix which is overwritten +function matrixMultiply (l, r, o) { + // 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 + + // make sure to use local variables because l/r and o could be the same + o.a = a + o.b = b + o.c = c + o.d = d + o.e = e + o.f = f + + return o +} + + function getOrigin (o, element, inSpace) { // Allow origin or around as the names let origin = o.origin // o.around == null ? o.origin : o.around diff --git a/src/matrix.js b/src/matrix.js index 6e93578..986b3fe 100644 --- a/src/matrix.js +++ b/src/matrix.js @@ -41,13 +41,13 @@ SVG.Matrix = SVG.invent({ // Get the proposed transformations and the current transformations var t = formatTransforms(o) - var current = new SVG.Matrix(this) + var current = this//new SVG.Matrix(this) // FIXME: do we need a new matrix here? let { x: ox, y: oy } = new SVG.Point(t.ox, t.oy).transform(current) // Construct the resulting matrix var transformer = new SVG.Matrix() .translateO(t.rx, t.ry) - .lmultiply(current) + .lmultiplyO(current) .translateO(-ox, -oy) .scaleO(t.scaleX, t.scaleY) .skewO(t.skewX, t.skewY) @@ -183,29 +183,24 @@ SVG.Matrix = SVG.invent({ multiplyO: function (matrix) { // Get the matrices var l = this - var r = new SVG.Matrix(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 - - this.a = a - this.b = b - this.c = c - this.d = d - this.e = e - this.f = f + var r = matrix instanceof SVG.Matrix + ? matrix + : new SVG.Matrix(matrix) - return this + return matrixMultiply(l, r, this) }, lmultiply: function (matrix) { - var result = new SVG.Matrix(matrix).multiplyO(this) - return result + return this.clone().lmultiplyO(matrix) + }, + + lmultiplyO: function (matrix) { + var r = this + var l = matrix instanceof SVG.Matrix + ? matrix + : new SVG.Matrix(matrix) + + return matrixMultiply(l, r, this) }, // Inverses matrix @@ -260,22 +255,28 @@ SVG.Matrix = SVG.invent({ // Scale matrix scale: function (x, y, cx, cy) { - return this.clone().scaleO(x, y, cx, cy) + return this.scaleO.call(this.clone(), ...arguments) + //return this.clone().scaleO(x, y, cx, cy) }, - scaleO: function (x, y, cx, cy) { + scaleO: function (x, y = x, cx = 0, cy = 0) { // Support uniform scaling - if (arguments.length === 1) { - y = x - } else if (arguments.length === 3) { + if (arguments.length == 3) { cy = cx cx = y y = x } - // Scale the current matrix - var scale = new SVG.Matrix(x, 0, 0, y, 0, 0) - return this.aroundO(cx, cy, scale) + let {a, b, c, d, e, f} = this + + this.a = a * x + this.b = b * y + this.c = c * x + this.d = d * y + this.e = e * x - cx * x + cx + this.f = f * y - cy * y + cy + + return this }, // Rotate matrix @@ -283,13 +284,23 @@ SVG.Matrix = SVG.invent({ return this.clone().rotateO(r, cx, cy) }, - rotateO: function (r, cx, cy) { + rotateO: function (r, cx = 0, cy = 0) { // Convert degrees to radians r = SVG.utils.radians(r) - // Construct the rotation matrix - var rotation = new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0) - return this.aroundO(cx, cy, rotation) + let cos = Math.cos(r) + let sin = Math.sin(r) + + let {a, b, c, d, e, f} = this + + this.a = a * cos - b * sin + this.b = b * cos + a * sin + this.c = c * cos - d * sin + this.d = d * cos + c * sin + this.e = e * cos - f * sin + cy * sin - cx * cos + cx + this.f = f * cos + e * sin - cx * sin - cy * cos + cy + + return this }, // Flip matrix on x or y, at a given offset @@ -308,21 +319,25 @@ SVG.Matrix = SVG.invent({ return this.clone().shearO(a, cx, cy) }, - shearO: function (a, cx, cy) { - var shear = new SVG.Matrix(1, 0, a, 1, 0, 0) - return this.aroundO(cx, cy, shear) + shearO: function (lx, cx = 0, cy = 0) { + let {a, b, c, d, e, f} = this + + this.a = a + b * lx + this.c = c + d * lx + this.e = e + f * lx - cy * lx + + return this }, // Skew Matrix skew: function (x, y, cx, cy) { - return this.clone().skewO(x, y, cx, cy) + return this.skewO.call(this.clone(), ...arguments) + //return this.clone().skew(x, y, cx, cy) }, - skewO: function (x, y, cx, cy) { + skewO: function (x, y = x, cx = 0, cy = 0) { // support uniformal skew - if (arguments.length === 1) { - y = x - } else if (arguments.length === 3) { + if (arguments.length == 3) { cy = cx cx = y y = x @@ -332,9 +347,19 @@ SVG.Matrix = SVG.invent({ x = SVG.utils.radians(x) y = SVG.utils.radians(y) - // Construct the matrix - var skew = new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0) - return this.aroundO(cx, cy, skew) + let lx = Math.tan(x) + let ly = Math.tan(y) + + let {a, b, c, d, e, f} = this + + this.a = a + b * lx + this.b = b + a * ly + this.c = c + d * lx + this.d = d + c * ly + this.e = e + f * lx - cy * lx + this.f = f + e * ly - cx * ly + + return this }, // SkewX @@ -359,7 +384,7 @@ SVG.Matrix = SVG.invent({ aroundO: function (cx, cy, matrix) { var dx = cx || 0 var dy = cy || 0 - return this.translateO(-dx, -dy).lmultiply(matrix).translateO(dx, dy) + return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy) }, around: function (cx, cy, matrix) { @@ -443,3 +468,10 @@ SVG.Matrix = SVG.invent({ // }) // // SVG.extend(SVG.Matrix, extensions) + + + + +// function matrixMultiplyParams (matrix, a, b, c, d, e, f) { +// return matrixMultiply({a, b, c, d, e, f}, matrix, matrix) +// } diff --git a/src/runner.js b/src/runner.js index 4348aff..3af1ca1 100644 --- a/src/runner.js +++ b/src/runner.js @@ -448,8 +448,7 @@ SVG.Runner = SVG.invent({ }, addTransform: function (transform, index) { - this.transforms = this.transforms.lmultiply(transform) - // this._element.addToCurrentTransform(transform) + this.transforms.lmultiplyO(transform) return this }, @@ -495,8 +494,6 @@ SVG.Runner.sanitise = function (duration, delay, when) { SVG.FakeRunner = class { constructor (transforms = new SVG.Matrix(), id = -1, done = true) { - // Object.assign(this, {transforms, id, done}) - this.transforms = transforms this.id = id this.done = done @@ -513,7 +510,7 @@ SVG.extend([SVG.Runner, SVG.FakeRunner], { }) -const lmultiply = (last, curr) => last.lmultiply(curr) +const lmultiply = (last, curr) => last.lmultiplyO(curr) const getRunnerTransform = (runner) => runner.transforms function mergeTransforms () { @@ -522,7 +519,7 @@ function mergeTransforms () { let runners = this._transformationRunners let netTransform = runners .map(getRunnerTransform) - .reduce(lmultiply) + .reduce(lmultiply, new SVG.Matrix()) this.transform(netTransform) @@ -590,7 +587,7 @@ SVG.extend(SVG.Element, { // taken into account .filter((runner) => runner.id <= current.id) .map(getRunnerTransform) - .reduce(lmultiply) + .reduce(lmultiply, new SVG.Matrix()) }, addRunner: function (runner) { @@ -909,6 +906,8 @@ SVG.extend(SVG.Runner, { return this.plot([a, b, c, d]) } + // FIXME: this needs to be rewritten such that the element is only accesed + // in the init function return this._queueObject('plot', new this._element.MorphArray(a)) /*var morpher = this._element.morphArray().to(a) -- 2.39.5