]> source.dussan.org Git - svg.js.git/commitdiff
Runners now work with absolute and relative transformations
authorSaivan <savian@me.com>
Fri, 22 Jun 2018 15:58:43 +0000 (01:58 +1000)
committerSaivan <savian@me.com>
Fri, 22 Jun 2018 15:58:43 +0000 (01:58 +1000)
This commit finally gets transformations workin in both absolute and
relative mode. This is the last **major** hurdle for the new
animation module, so we just need to clean it up and fix some bugs 🐞

dirty.html
src/runner.js
src/timeline.js

index 1708b0d9440f72bd37df1cb92e32f2a13f52a20e..139b5d97cdbad32272a86662f011c9eb0292e157 100644 (file)
@@ -160,13 +160,19 @@ function getColor(t) {
 //   ])
 // })
 
-// var mover = SVG('<ellipse>').size(50, 50).center(100, 100).addTo('svg')
-// var anim = mover.animate(new SVG.Spring(500, 10)).move(500, 500)
-//
-// SVG.on(document, 'mousemove', function (e) {
-//   var p = mover.point(e.pageX, e.pageY)
-//   anim.center(p.x, p.y)
-// })
+var mover = SVG('<ellipse>').size(50, 50).center(100, 100).addTo('svg')
+var anim = mover.animate(new SVG.Spring(500, 10)).move(500, 500)
+
+let date = +new Date
+SVG.on(document, 'mousemove', function (e) {
+  if (+new Date - date > 50) {
+    date = +new Date
+  } else {
+    return
+  }
+  var p = mover.point(e.pageX - 1000, e.pageY - 1000)
+  anim.transform({px: p.x, py: p.y})
+})
 
 // var timeline = new SVG.Timeline().pause()
 // var runner = new SVG.Runner(100000)
@@ -198,26 +204,31 @@ function getColor(t) {
 // r.step(-300) // should be 0.9s
 
 
-var timer = 0
-let rec1 = SVG('<rect>').addTo('svg').size(100, 100)//.transform({translateX: -50, translateY: -50})
-rec1.timeline().source(() => {
-  timer += 1
-  document.querySelector('#absolute span').textContent = timer
-  return timer
-})
+// var timer = 0
+// SVG('svg').viewbox(-300, -300, 600, 600)
+// let rec1 = SVG('<rect>').addTo('svg')
+//   .size(100, 100)
+//   //.transform({translateX: -50, translateY: -50})
+// rec1.timeline().source(() => {
+//   timer += 2
+//   document.querySelector('#absolute span').textContent = timer
+//   return timer
+// })
+
+// var runner = rec1
+//   // .animate(100).attr('fill', '#fff')
+//   //.animate().transform({rotate: -45, origin: [50, 50]})
+//   .animate(200)
+//   .transform({
+//     rotate: 320,
+//     origin: [200, 200],
+//   }, true)
+//
+//   rec1.animate(150, 150, 'absolute')
+//     .transform({
+//       scale:2
+//     })
 
-var runner = rec1
-  // .animate(100).attr('fill', '#fff')
-  //.animate().transform({rotate: -45, origin: [50, 50]})
-   .animate(500)
-  .transform({
-    rotate: 90,
-    origin: [50, 50],
-  })
-  .animate(200, 0, 'absolute')
-  .transform({
-    scale:2
-  })
   // .animate(500, 0)
   // .transform({scale:2})
   // .transform({rotate: 360}, true)
index 600f6728b1a7d6ec2f55f0ef863c4aaea7989e1e..9bb1aa636d537d98b86e6690fd67b92834da787e 100644 (file)
@@ -16,12 +16,12 @@ SVG.Runner = SVG.invent({
     // Store a unique id on the runner, so that we can identify it
     this.id = SVG.Runner.id++
 
-    // ensure a default value
+    // Ensure a default value
     options = options == null
       ? SVG.defaults.timeline.duration
       : options
 
-    // ensure that we get a controller
+    // Ensure that we get a controller
     options = typeof options === 'function'
       ? new SVG.Controller(options)
       : options
@@ -47,8 +47,9 @@ SVG.Runner = SVG.invent({
     this._last = 0
     this.tags = {}
 
-    // save transforms applied to this runner
+    // Save transforms applied to this runner
     this.transforms = new SVG.Matrix()
+    this._transformsRunning = true
 
     // Looping variables
     this._haveReversed = false
@@ -57,9 +58,6 @@ SVG.Runner = SVG.invent({
     this._swing = false
     this._wait = 0
     this._times = 1
-
-    // save the transformation we are starting with
-    this._baseTransform = null
   },
 
   construct: {
@@ -159,9 +157,8 @@ SVG.Runner = SVG.invent({
     These methods allow us to attach basic functions to the runner directly
     */
 
-    queue: function (initFn, runFn, alwaysInitialise) {
+    queue: function (initFn, runFn) {
       this._queue.push({
-        alwaysInitialise: alwaysInitialise || false,
         initialiser: initFn || SVG.void,
         runner: runFn || SVG.void,
         initialised: false,
@@ -266,13 +263,16 @@ SVG.Runner = SVG.invent({
 
     step: function (dt) {
 
+      // If we are inactive, this stepper just gets skipped
+      if (!this.enabled) return this
+
       // Update the time and get the new position
       dt = dt == null ? 16 : dt
       this._time += dt
       var position = this.position()
 
       // Figure out if we need to run the stepper in this frame
-      var runNow = this._lastPosition !== position && this._time >= 0
+      var running = this._lastPosition !== position && this._time >= 0
       this._lastPosition = position
 
       // Figure out if we just started
@@ -284,25 +284,22 @@ SVG.Runner = SVG.invent({
         // this.fire('start', this)
       }
 
-      // Work out if the runner is finished
-      // set the done flag here so animations know,
-      // that they are running in the last step
-      // (this is good for transformations which can be merged)
+      // Work out if the runner is finished set the done flag here so animations
+      // know, that they are running in the last step (this is good for
+      // transformations which can be merged)
+      var declarative = this._isDeclarative
       this.done = !declarative && !justFinished && this._time >= duration
 
       // Call initialise and the run function
-      this._initialise()
-      var declarative = this._isDeclarative
-      if ( runNow || declarative ) {
+      if ( running || declarative ) {
+        this._initialise(running)
         this.transforms = new SVG.Matrix()
         var converged = this._run(declarative ? dt : position)
         // this.fire('step', this)
       }
-
       // correct the done flag here
       // declaritive animations itself know when they converged
       this.done = this.done || (converged && declarative)
-
       if (this.done) {
         // this.fire('finish', this)
       }
@@ -387,16 +384,22 @@ SVG.Runner = SVG.invent({
     },
 
     // Run each initialise function in the runner if required
-    _initialise: function () {
+    _initialise: function (running) {
+
+      // If we aren't running, we shouldn't initialise when not declarative
+      if (!running && !this._isDeclarative) return
+
+      // Loop through all of the initialisers
       for (var i = 0, len = this._queue.length; i < len ; ++i) {
         // Get the current initialiser
         var current = this._queue[i]
 
         // Determine whether we need to initialise
-        var needsInit = current.alwaysInitialise || !current.initialised
+        var needsIt = this._isDeclarative || (!current.initialised && running)
         var running = !current.finished
 
-        if (needsInit && running) {
+        // Call the initialiser if we need to
+        if (needsIt && running) {
           current.initialiser.call(this)
           current.initialised = true
         }
@@ -415,8 +418,8 @@ SVG.Runner = SVG.invent({
 
         // Run the function if its not finished, we keep track of the finished
         // flag for the sake of declarative _queue
-        current.finished = current.finished
-          || (current.runner.call(this, positionOrDt) === true)
+        var converged = current.runner.call(this, positionOrDt)
+        current.finished = current.finished || (converged === true)
         allfinished = allfinished && current.finished
       }
 
@@ -424,16 +427,15 @@ SVG.Runner = SVG.invent({
       return allfinished
     },
 
-    _pushLeft: function (transform) {
+    addTransform: function (transform) {
       this.transforms = this.transforms.lmultiply(transform)
-      this.element().addRunner(this)
       return this
     },
 
-    _currentTransform: function () {
-      return this.element()._currentTransform(this)
+    clearTransform: function () {
+      this.transforms = new SVG.Matrix()
+      return this
     }
-
   },
 })
 
@@ -469,88 +471,101 @@ SVG.Runner.sanitise = function (duration, delay, when) {
   }
 }
 
-reduceTransform = function (arr, base) {
-  return arr.reduce(function (last, curr) {
-    return last.lmultiply(curr)
-  }, base)
-}
-
 function mergeTransforms () {
-  var net = reduceTransform(this.runners.map(el => el.transforms), this._baseTransform)
-  this.transform(net)
-  this._mergeTransforms = null
-
-  this.runners.forEach(function (r, index, arr) {
-    if(!r.done) return
-    if(index == 0) {
-      this._baseTransform = this._baseTransform.multiply(r.transforms)
-      arr.shift()
-    } else if(arr[index-1].done) {
-      var obj = {
+
+  // Find the matrix to apply to the element and apply it
+  let runners = this._transformationRunners
+  let netTransform = runners
+    .map(runner => runner.transforms)
+    .reduce((last, curr) => last.lmultiply(curr))
+  this.transform(netTransform)
+
+  // Merge any two transformations in a row that are done
+  let lastRunner = null
+  let lastIndex = null
+  runners.forEach((runner, i) => {
+
+    if (i != 0 && runner.done && lastRunner.done) {
+      delete runners[lastRunner.id]
+      runners[i] = {
+        transforms: runner.transforms.lmultiply(lastRunner.transforms),
         done: true,
-        transforms: r.transforms.multiply(this.runners[index].transforms)
+        id: i,
       }
-
-      arr.splice(index-1, 2, obj)
     }
-  }.bind(this))
 
-  //_this._transformationChain = []
+    lastRunner = runner
+  })
+
+  // // Try to merge any adjacent transforms into a single transform
+  // for (let i = runners.length; --i;) {
+  //
+  //   let runner = runners[i]
+  //   let lastRunner = runners[i-1]
+  //   if (!runner.done || !lastRunner.done) continue
+  //
+  //   runners.splice(i-1, 2, {
+  //     transforms: runner.transforms.lmultiply(lastRunner.transforms),
+  //     done: true,
+  //   })
+  // }
 }
 
 SVG.extend(SVG.Element, {
+  //
+  // clearTransforms: function (currentRunner) {
+  //
+  //   let runners = this._transformationRunners
+  //   for (let i = 0, len = runners.length; i < len; ++i) {
+  //     let runner = runners[i]
+  //
+  //     // if we hit the current runner
+  //     if (runner == currentRunner) {
+  //
+  //       // update the runners on the element to only use its current matrix and
+  //       // and all runners which come after the current runner + the current
+  //       this._transformationRunners = runners.slice(i)
+  //       break
+  //     }
+  //
+  //     runners[i]._transformsRunning = false
+  //   }
+  // },
 
-  addRunner: function (r) {
-    var runners = this.runners
-    var index = ((runners.indexOf(r) + 1) || this.runners.push(r)) - 1
-
-    //if(r.done) this.checkForSimplification(index)
-
-    this._mergeTransforms = SVG.Animator.transform_frame(mergeTransforms.bind(this), this._frameId)
+  _clearTransformRunnersBefore: function (currentRunner) {
+    this._transformationRunners = this._transformationRunners.filter((runner) => {
+      return runner.id >= currentRunner.id
+    })
   },
 
-  checkForSimplification: function (index) {
-    var r
-    if(index == 0) {
-      //while(this.runners[0] && this.runners[0].done) {
-        r = this.runners.shift()
-        this._baseTransform = this._baseTransform.lmultiply(r.transforms)
-        r.transforms = new SVG.Matrix()
-      //}
-      return
-    }
-
-    var r = this.runners[index-1]
-
-    if(!r.done) return
-
-    var obj = {
-      done: true,
-      transforms: r.transforms.multiply(this.runners[index].transforms)
-    }
+  addRunner: function (runner) {
+    // let runners = this._transformationRunners
+    // if (!runners.includes(runner)) {
+    //   runners.push(runner)
+    // }
 
-    this.runners.splice(index-1, 2, obj)
+    this._transformationRunners[runner.id] = runner
 
+    SVG.Animator.transform_frame(
+      mergeTransforms.bind(this), this._frameId
+    )
   },
 
   _prepareRunner: function () {
-    if (!this._baseTransform) {
-      this._baseTransform = new SVG.Matrix(this)
-      this._mergeTransforms = null
-      this._transformationChain = []
-      this.runners = []
+    if (this._frameId == null) {
+      this._transformationRunners = []
       this._frameId = SVG.Element.frameId++
     }
   },
 
   _currentTransform: function (r) {
 
-    var index = this.runners.indexOf(r)
+    var index = this._transformationRunners.indexOf(r)
     if(index < 0) {
       return this._baseTransform
     }
 
-    var transforms = this.runners.slice(0, this.runners.indexOf(r)+1).map(el => el.transforms)
+    var transforms = this._transformationRunners.slice(0, this._transformationRunners.indexOf(r)+1).map(el => el.transforms)
 
     return reduceTransform(transforms, this._baseTransform)
   }
@@ -584,7 +599,7 @@ SVG.extend(SVG.Runner, {
     }, function (pos) {
       this.element()[type](name, morpher.at(pos))
       return morpher.done()
-    }, this._isDeclarative)
+    })
 
     return this
   },
@@ -597,7 +612,7 @@ SVG.extend(SVG.Runner, {
    }, function (pos) {
      this.element().zoom(morpher.at(pos), point)
      return morpher.done()
-   }, this._isDeclarative)
+   })
 
    return this
  },
@@ -618,12 +633,12 @@ SVG.extend(SVG.Runner, {
   // 4. Now you get the delta matrix as a result: D = F * inv(M)
 
   transform: function (transforms, relative, affine) {
+
+    // Parse the parameters
     var isMatrix = transforms.a != null
     affine = transforms.affine || affine || !isMatrix
     relative = transforms.relative || relative
 
-    var morpher
-
     /**
       The default of relative is false
       affine defaults to true if transformations are used and to false when a matrix is given
@@ -637,24 +652,25 @@ SVG.extend(SVG.Runner, {
     **/
 
 
-    // if we have a relative transformation and its not a matrix
+    // If we have a relative transformation and its not a matrix
     // we morph all parameters directly with the ObjectBag
     // the following cases are covered here:
     // - true, false with ObjectBag
     // - true, true with ObjectBag
+    var morpher
     if(relative && !isMatrix) {
       morpher = new SVG.Morphable.ObjectBag(formatTransforms({}))
         .to(formatTransforms(transforms))
         .stepper(this._stepper)
-// debugger
-      return this.queue(function() {}, function (pos) {
-        this._pushLeft(new SVG.Matrix(morpher.at(pos).valueOf()))
+      return this.queue(function() {
+        this.element().addRunner(this)
+      }, function (pos) {
+        this.addTransform(new SVG.Matrix(morpher.at(pos).valueOf()))
         return morpher.done()
-      }, this._isDeclarative)
+      })
       return this
     }
 
-
     // what is left is affine morphing for SVG.Matrix and absolute transformations with TransformBag
     // also non affine direct and relative morhing with SVG.Matrix
     // the following cases are covered here:
@@ -664,36 +680,41 @@ SVG.extend(SVG.Runner, {
     // - false, false with SVG.Matrix
 
     // 1.  define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx]
-    morpher = (isMatrix && !affine)
-      ? new SVG.Matrix().to(transforms)
-      : new SVG.Morphable.TransformBag().to(transforms)
+    var morphType = (isMatrix && !affine)
+      ? SVG.Matrix
+      : SVG.Morphable.TransformBag
+
+    morpher = new SVG.Morphable().type(morphType)
 
     morpher.stepper(this._stepper)
 
-    // create identity Matrix for relative not affine Matrix transformation
-    morpher.from()
+    this.queue(function() {
+      let element = this.element()
+      element.addRunner(this)
 
-    this.queue(function() {}, function (pos) {
+      // If we have an absolute transform, it needs to over-ride any other
+      // tranformations that are available on the element
+      if (!relative) {
 
-      // 2. on every frame: pull the current state of all previous transforms (M - m can change)
-      var curr = this._currentTransform()
-      if(!relative) morpher.from(curr)
+        // Deactivate all transforms that have run so far if we are absolute
+        element._clearTransformRunnersBefore(this)
+        currentBase = new SVG.Matrix(element)
+        morpher.from(currentBase).to(transforms)
+      }
 
-      // 3. Find the interpolated matrix F(pos) = m + pos * (t - m)
-      //   - Note F(0) = M
-      //   - Note F(1) = T
-      var matrix = morpher.at(pos)
+      // Define the starting point for the morpher
+      let startMatrix = new SVG.Matrix(relative ? null : element)
+      morpher.from(startMatrix)
 
-      if(!relative) {
-        // 4. Now you get the delta matrix as a result: D = F * inv(M)
-        var delta = matrix.multiply(curr.inverse())
-        this._pushLeft(delta)
-      } else {
-        this._pushLeft(matrix)
-      }
+    }, function (pos) {
 
+      if (!this._transformsRunning) return
+      if (!relative) this.clearTransform()
+      var matrix = morpher.at(pos)
+      this.addTransform(matrix)
       return morpher.done()
-    }, this._isDeclarative)
+
+    })
 
     return this
   },
@@ -731,7 +752,7 @@ SVG.extend(SVG.Runner, {
       }, function (pos) {
         this.element()[method](morpher.at(pos))
         return morpher.done()
-      }, this._isDeclarative)
+      })
 
       // Register the morpher so that if it is changed again, we can retarget it
       this._rememberMorpher(method, morpher)
@@ -750,7 +771,7 @@ SVG.extend(SVG.Runner, {
     }, function (pos) {
       this.element()[method](morpher.at(pos))
       return morpher.done()
-    }, this._isDeclarative)
+    })
 
     // Register the morpher so that if it is changed again, we can retarget it
     this._rememberMorpher(method, morpher)
index 9d03bbc3bd9735defb2b013f9a9b12b1693e9fd3..ee728245b8b6fd05a91a75dfd69a0f913f77dbc5 100644 (file)
@@ -218,7 +218,6 @@ SVG.Timeline = SVG.invent({
         var runnerInfo = this._runners[this._order[i]]
         var runner = runnerInfo.runner
 
-        if(runner.done) continue
         if(!runner.active()) continue
 
         // If this runner is still going, signal that we need another animation
@@ -226,7 +225,7 @@ SVG.Timeline = SVG.invent({
         var finished = runner.step(dtTime).done
         if (!finished) {
           runnersLeft = true
-          continue
+          // continue
         }