]> source.dussan.org Git - svg.js.git/commitdiff
Optimized the matrix functions so that the transform function is a multitude faster...
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Mon, 8 Oct 2018 13:18:53 +0000 (15:18 +0200)
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Mon, 8 Oct 2018 13:18:53 +0000 (15:18 +0200)
bench/runner.html
bench/tests/10000-transform.js [new file with mode: 0644]
dirty.html
spec/SpecRunner.html
spec/spec/matrix.js
src/helpers.js
src/matrix.js
src/runner.js

index 6d1bed2603a981200b7b37e2301b6471497f77bc..b39c1df8bba9a4df75baccd35aa92b1a5bb2a260 100644 (file)
   <div id="draw"></div>
   <svg id="native" width="100" height="100" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs"></svg>
   <script src="../dist/svg.js"></script>
+  <script type="text/javascript" src="../src/helpers.js"></script>
+  <script type="text/javascript" src="../src/transform.js"></script>
+  <script type="text/javascript" src="../src/matrix.js"></script>
+  <script type="text/javascript" src="../src/morph.js"></script>
+  <script type="text/javascript" src="../src/runner.js"></script>
+  <script type="text/javascript" src="../src/timeline.js"></script>
+  <script type="text/javascript" src="../src/controller.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js"></script>
   <script src="svg.bench.js"></script>
   <!--<script src="tests/10000-each.js"></script> -->
@@ -45,8 +52,9 @@
   <script src="tests/10000-circles.js"></script>
   <script src="tests/10000-paths.js"></script>
   <script src="tests/10000-boxes.js"></script>
-  <script src="tests/10000-pointArray-bbox.js"></script> -->
-  <script src="tests/10000-accesses.js"></script>
+  <script src="tests/10000-pointArray-bbox.js"></script>
+  <script src="tests/10000-accesses.js"></script> -->
+  <script src="tests/10000-transform.js"></script>
   <script>
     SVG.bench.run()
   </script>
diff --git a/bench/tests/10000-transform.js b/bench/tests/10000-transform.js
new file mode 100644 (file)
index 0000000..0fcc162
--- /dev/null
@@ -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)
+  })
+})
+
index aa270f03223ffd80009cea4d84951e459ddd478d..ffd722568a88b160530332593b05d96953c32427 100644 (file)
@@ -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) => {
index 5e70186f1812bdb6bc28d5532a47c6d535a7ef78..efc58a3c8d0e9b0bd4afbb07c5b5c0b0b8094af3 100644 (file)
 
   <!-- include source files here... -->
   <script src="../dist/svg.js" charset="utf-8"></script>
-  <script src="../src/controller.js" charset="utf-8"></script>
-  <script src="../src/morph.js" charset="utf-8"></script>
-  <script src="../src/runner.js" charset="utf-8"></script>
-  <script src="../src/timeline.js" charset="utf-8"></script>
+  <script type="text/javascript" src="../src/helpers.js"></script>
+  <script type="text/javascript" src="../src/transform.js"></script>
+  <script type="text/javascript" src="../src/matrix.js"></script>
+  <script type="text/javascript" src="../src/morph.js"></script>
+  <script type="text/javascript" src="../src/runner.js"></script>
+  <script type="text/javascript" src="../src/timeline.js"></script>
+  <script type="text/javascript" src="../src/controller.js"></script>
 
 </head>
 
   <script src="spec/ellipse.js"></script>
   <script src="spec/event.js"></script>
   <script src="spec/fx.js"></script>
-  <script src="spec/gradient.js"></script>
+  <script src="spec/gradient.js"></script>-->
   <script src="spec/helper.js"></script>
-  <script src="spec/hyperlink.js"></script>
+  <!--<script src="spec/hyperlink.js"></script>
   <script src="spec/image.js"></script>
   <script src="spec/line.js"></script>
   <script src="spec/marker.js"></script>
-  <script src="spec/mask.js"></script>
+  <script src="spec/mask.js"></script>-->
   <script src="spec/matrix.js"></script>
-  <script src="spec/number.js"></script>
+  <!--<script src="spec/number.js"></script>
   <script src="spec/path.js"></script>
   <script src="spec/pattern.js"></script>
   <script src="spec/point.js"></script>
   <script src="spec/viewbox.js"></script> -->
 
   <!-- <script src="spec/morphing.js"></script>
-  <script src="spec/animator.js"></script> -->
-  <script src="spec/runner.js"></script>
+  <script src="spec/animator.js"></script>
+  <script src="spec/runner.js"></script> -->
 </body>
 </html>
index 543ac0e9f318f5e7e10461644b803fb0bc78ba34..42ac1fe7ee401d764998fa326aca9ab8a1062b32 100644 (file)
 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])
index 4b55897c5d32538913719b85aa34bb50c9f0c238..fc22e4c5610ede4da98a2ab6911ddc4eb504d00b 100644 (file)
@@ -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
index 6e9357882a0a7900e6824f2445975026cd943fef..986b3fe4d3a4b1faa2db1dfb4621dd298fe7c501 100644 (file)
@@ -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)
+// }
index 4348aff244943f8fc512c6a625a9d7d753013f1d..3af1ca19f0c45927212a4b164b5b89dc5598e73b 100644 (file)
@@ -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)