]> source.dussan.org Git - svg.js.git/commitdiff
condens transform function to an overall more stable one
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Sun, 8 Jul 2018 09:07:27 +0000 (11:07 +0200)
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Sun, 8 Jul 2018 09:07:27 +0000 (11:07 +0200)
dirty.html
src/morph.js
src/runner.js

index 3c9b241d925c3ea67b708bb796f0a43af32acfc2..ea40868dc22a47c9b0d36cb4dc189ad6dd2bace9 100644 (file)
@@ -5,16 +5,17 @@
   <meta charset="utf-8">
   <title></title>
   <script type="text/javascript" src="dist/svg.js"></script>
-  <script type="text/javascript" src="src/morph.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 type="text/javascript" src="src/transform.js"></script>
-  <script type="text/javascript" src="src/matrix.js"></script>
+
 
 
 </head>
@@ -81,7 +82,7 @@ function getColor(t) {
 //   .animate()
 //   .move(300, 200)
 
-
+//
 // for (let i = 0 ; i < 15; i++) {
 //   for (let j = 0 ; j < 10; j++) {
 //
@@ -98,7 +99,7 @@ function getColor(t) {
 //     // Animate the rect
 //     rect.animate(3000, Math.random() * 2000)
 //       // .during(t => rect.transform({rotate: 700 * t, origin: [cx, cy]}))
-//       .transform({rotate: 720, origin: [cx, cy]})
+//       .transform({rotate: 720}, true)
 //       // .during(t => rect.attr('transform', `rotate(${700 * t})`))
 //       .during(t => rect.attr('fill', getColor(o * 0.1 + t)))
 //   }
@@ -203,7 +204,7 @@ rectangle.animate().transform({
   origin: [bbox.cx, bbox.cy]
 })*/
 
-
+//
 let canvas = SVG('#canvas')
 
 SVG('#absolute').on('input slide', function (e) {
@@ -228,7 +229,7 @@ SVG('#absolute').on('input slide', function (e) {
   let a = canvas.rect(200, 400).move(500, 400)
     .attr('opacity', 0.3)
     .addClass('pink')
-    .transform({ px: 100, py: 500, origin: 'top-left' })
+    //.transform({ px: 100, py: 500, origin: 'top-left' })
 
 
   var timer = 0
@@ -238,28 +239,18 @@ SVG('#absolute').on('input slide', function (e) {
     return timer
   })
 
-  let obj = { rotate: val * 180, origin: 'center', translate: [300 * val, 0] }
-  let obj2 = { rotate: -val * 180, origin: 'center' }
+  let obj = { rotate: val * 180, origin: 'top-left' }
+  let obj2 = { rotate: val * 280, origin: 'center' }
 
   a.clone() // startPosition
   a.clone().transform(obj, true).transform(obj2, true) // endPosition
 })
 
-
-
-// transform({})
-
-
-// // Make the green rectange
-// canvas.rect(200, 400).move(500, 400)
-//   .attr('opacity', 0.3)
-//   .addClass('green')
-
-// Make the pink rectangle
+// // Make the pink rectangle
 let a = canvas.rect(200, 400).move(500, 400)
   .attr('opacity', 0.3)
   .addClass('pink')
-  // .transform({ tx: 100, ty: 500, origin: 'top-left' })
+  .transform({ tx: 300, ty: 500, origin: 'top-left' })
 
 
 var timer = 0
@@ -269,130 +260,37 @@ a.timeline().source(() => {
   return timer
 })
 
-let obj = { rotate: 180, origin: 'top-left', tx: 500}
+let obj = { rotate: 100, origin: 'top-left'}
 let obj2 = { rotate: 280, origin: 'center' }
+let obj3 = { rotate: 1000, origin: 'center', translate: [300, 200]}
 
-var z = a.clone().attr('fill', 'blue') // startPosition
-//a.clone().transform(obj, true).transform(obj2, true) // endPosition
-
-// that works
-// a.animate(new SVG.Spring(50, 30)).transform(obj, false, true)  // animation
-// that breaks (why??)
-
-// var b = a.clone().animate(new SVG.Spring(500, 30))
-//debugger
-//b.transform(obj, false, true)  // animation
-a.clone().animate(new SVG.Spring(1000, 15)).transform(obj).transform(obj2, true) // animation
-a.clone().animate(1000).transform(obj).transform(obj2, true) // animation
-a.clone().transform(obj).transform(obj2, true) // endPosition
-
-canvas.ellipse(20, 20).center(500, 400 + 0)
 
+// var c = a.clone()
+// var d = a.clone()
 //
-//el.center(100, 400)
-// z.transform({px: 100, py: 400})
-// z.transform({tx: 100, px: 100, py: 400})
-// z.transform({px: 100, py: 400})
-// z.transform({px: 100, py: 400})
-// z.transform({px: 100, py: 400})
-
-
-// SVG.on(document, 'mousemove', (e) => {
-//   let {x, y} = canvas.point(e.pageX, e.pageY)
-//   el.center(x, y)
-//   b.transform ({px: x, py: y, rotate: (x + y) / 3})
-//   z.transform ({px: x, py: y }) //, rotate: (x + y) / 3})
-// })
-
-
-
-
-
-
-//setTimeout(()=>console.log(a.transform()), 6000)
 //
-// // Put an ellipse where we expect the object to be
-// canvas.ellipse(30, 30).center(100, 500)
-//   .attr('opacity', 0.3)
-//   .addClass('dark-pink')
-
-
-// var timeline = new SVG.Timeline().pause()
-// var runner = new SVG.Runner(100000)
-// runner.queue(null, function (pos) {
-//   console.log(pos)
-// })
-// timeline.schedule(runner)
+// c.animate(1000)
+//   //.transform(obj)
+//   .transform(obj, true) // animation
 //
-// runner.after(() => console.log('finished with after'))
-// runner.on('finish', () => console.log('finished with on'))
+// d.animate(3000)
+//   //.transform(obj)
+//   .transform(obj, true) // animation
 
-//timeline.play()
-//timeline.finish()
+// a.clone().attr('fill', 'blue')
+//   //.transform(obj)
+//   .transform(obj2, true) // endPosition
 
 
+let b = a.clone().animate(new SVG.Spring(1000, 15))
 
-// var circle = SVG('<rect>').addTo('svg').size(100, 100).center(200, 200)
-// var runner = circle.animate(1000)
-//   .loop(3, true, 500)
-//   .reverse()
-//   .ease('<>')
-//   .center(500, 200)
-//   .during(t => circle.transform({rotate: 720 * t, origin: [200, 200]}))
+SVG.on(document, 'mousemove', (e) => {
+  let {x, y} = canvas.point(e.clientX, e.clientY)
+  b.transform ({tx: x, ty: y, rotate: (x + y) / 3}, true)
+})
 
-// var r = new SVG.Runner(1000).loop(10, false, 100)
-// r.queue(null, console.log)
-//
-// r.step(1200) // should be 0.1s
-// r.step(-300) // should be 0.9s
 
-/*
-var timer = 0
-SVG('svg').viewbox(-150, -150, 1000, 1000)
-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
-    })
-*/
-    // .animate(400, 0, 'absolute')
-    // .transform({
-    //   rotate: 360,
-    //   //origin: [200, 200],
-    // }, true)
-
-  // .animate(500, 0)
-  // .transform({scale:2})
-  // .transform({rotate: 360}, true)
-  // .transform({translateX: 50, translateY: 50}, true)
-  //.transform({translateX: 50, translateY: 50}, true)
-   // .animate(500, 0, 'absolute')
-   //    .transform({scale: 2}, true)
-  // .animate(2000, 0, 'absolute')
-  //    .transform({rotate: -300})
-
-// r.step(300) // should be 0.1
-// r.step(2 * 1100) // should be 0
-// r.step(-50) // 0.05
-// r.step(-100)
-// r.step(-100) // 0.95
 
 </script>
 
index 18ed6b5b8394111e010e920d2e100b49a4e4e871..6a9e6511eae2a627038ef4ceb94d8e9e74b721bf 100644 (file)
@@ -8,7 +8,6 @@ SVG.Morphable = SVG.invent({
     this._to = null
     this._type = null
     this._context = null
-    this.modifier = function(arr) { return arr }
     this._morphObj = null
   },
 
@@ -106,21 +105,11 @@ SVG.Morphable = SVG.invent({
     at: function (pos) {
       var _this = this
 
-      // for(var i = 0, len = this._from.length; i < len; ++i) {
-      //   arr.push(this.stepper(this._from[i], this._to[i]))
-      // }
-
       return this._morphObj.fromArray(
-        this.modifier(
-          this._from.map(function (i, index) {
-            return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context)
-          })
-        )
+        this._from.map(function (i, index) {
+          return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context)
+        })
       )
-    },
-
-    valueOf: function () {
-      return this._value
     }
   }
 })
@@ -142,6 +131,42 @@ SVG.Morphable.NonMorphable = SVG.invent({
   }
 })
 
+SVG.Morphable.TransformBag2 = SVG.invent({
+  create: function (obj) {
+    if(Array.isArray(obj)) {
+      obj = {
+        scaleX: obj[0],
+        scaleY: obj[1],
+        shear: obj[2],
+        rotate: obj[3],
+        translateX: obj[4],
+        translateY: obj[5],
+        originX: obj[6],
+        originY: obj[7]
+      }
+    }
+
+    Object.assign(this, obj)
+  },
+
+  extend: {
+    toArray: function (){
+      var v = this
+
+      return [
+        v.scaleX,
+        v.scaleY,
+        v.shear,
+        v.rotate,
+        v.translateX,
+        v.translateY,
+        v.originX,
+        v.originY,
+      ]
+    }
+  }
+})
+
 SVG.Morphable.TransformBag = SVG.invent({
   inherit: SVG.Matrix,
   create: function (obj) {
@@ -239,6 +264,7 @@ SVG.MorphableTypes = [
   SVG.PathArray,
   SVG.Morphable.NonMorphable,
   SVG.Morphable.TransformBag,
+  SVG.Morphable.TransformBag2,
   SVG.Morphable.ObjectBag,
 ]
 
@@ -256,29 +282,6 @@ SVG.extend(SVG.MorphableTypes, {
 })
 
 
-// animate().ease(function(pos) { return pos})
-// function Ease (func) {
-//   return function eased (fromOrCurr, to, pos) {
-//     return fromOrCurr + func(pos) * (to - fromOrCurr) // normal easing
-//   }
-// }
-
-
-///
-/// el.animate()
-///   .fill('#00f')
-///   ---->> timeline.fill
-///     val = new Morphable().to('#0ff').stepper(stepper)
-///     func init() {
-///       val.from(el.fill())
-///     }
-///     func run (pos) {
-///       curr = val.at(pos)
-///       el.fill(curr)
-///     }
-///     this.queue(init, run)
-
-
 
 
 // - Objects are just variable bags
@@ -304,42 +307,7 @@ SVG.extend(SVG.MorphableTypes, {
 
 
 
-
-// C R x = D C x = A x
-//
-//     (C R inv(C)) C x
-//
-//
-// C R = D C
-// D = C R inv(C)
-
-
-/*
-absolute -> start at current - {affine params}
-relative -> start at 0 always - {random stuff}
-*/
-
-
-
-
-
 /**
-    INIT
-     - save the current transformation
-
-    ELEMENT TIMELINE (one timeline per el)
-    - Reads the current transform and save it to the transformation stack
-    - Runs all available runners, runners will:
-      - Modify their transformation on the stack
-      - Mark their transformation as complete
-      - After each runner, we group the matrix (not for now)
-    - After running the runners, we bundle all contiguous transformations into
-      a single transformation
-
-
-    - transformtionstack is like this: [RunnerB, Matrix, RunnerC]
-      - skip merging for now (premature blabla)
-
 
 el.loop({times: 5, swing: true, wait: [20, 50]})
 
index da494d3edcba7e7f6df3327f9afef5b676c37157..ceb42fe05acaf80ff104c48646ad6b768a6dc3fa 100644 (file)
@@ -160,7 +160,7 @@ SVG.Runner = SVG.invent({
       this._queue.push({
         initialiser: initFn || SVG.void,
         runner: runFn || SVG.void,
-        isTransform: !!isTransform,
+        isTransform: isTransform,
         initialised: false,
         finished: false,
       })
@@ -364,7 +364,6 @@ SVG.Runner = SVG.invent({
 
     // Save a morpher to the morpher list so that we can retarget it later
     _rememberMorpher: function (method, morpher) {
-      if (method == 'transform' && !this._isDeclarative) return
       this._history[method] = {
         morpher: morpher,
         caller: this._queue[this._queue.length - 1],
@@ -374,9 +373,25 @@ SVG.Runner = SVG.invent({
     // Try to set the target for a morpher if the morpher exists, otherwise
     // do nothing and return false
     _tryRetarget: function (method, target) {
-      if (method == 'transform' && !this._isDeclarative) return false
       if(this._history[method]) {
-        this._history[method].morpher.to(target)
+
+        // if the last method wasnt even initialised, throw it away
+        if (!this._history[method].caller.initialised) {
+          let index = this._queue.indexOf(this._history[method].caller)
+          this._queue.splice(index, 1)
+          return false
+        }
+
+        // for the case of transformations, we use the special retarget function
+        // which has access to the outer scope
+        if (this._history[method].caller.isTransform) {
+          this._history[method].caller.isTransform(target)
+
+        // for everything else a simple morpher change is sufficient
+        } else {
+          this._history[method].morpher.to(target)
+        }
+
         this._history[method].caller.finished = false
         var timeline = this.timeline()
         timeline && timeline._continue()
@@ -480,9 +495,8 @@ function mergeTransforms () {
   let netTransform = runners
     .map(runner => runner.transforms)
     .reduce((last, curr) => last.lmultiply(curr))
-  this.transform(netTransform)
 
-  this._currentMatrix = runners[0]
+  this.transform(netTransform)
 
   // Merge any two transformations in a row that are done
   let lastRunner = null
@@ -490,7 +504,7 @@ function mergeTransforms () {
     if (lastRunner && runner.done && lastRunner.done) {
       delete runners[runner.id+1]
       runners[lastRunner.id+1] = {
-        transforms: runner.transforms.lmultiply(lastRunner.transforms),
+        transforms: runner.transforms.multiply(lastRunner.transforms),
         done: true,
         id: lastRunner.id,
       }
@@ -499,7 +513,7 @@ function mergeTransforms () {
     lastRunner = runner
   })
 
-  if (!lastRunner) {
+  if (lastRunner == runners[0]) {
     this._frameId = null
   }
 }
@@ -621,154 +635,90 @@ SVG.extend(SVG.Runner, {
       ? transforms.affine
       : (affine != null ? affine : !isMatrix)
 
-    /**
-      The default of relative is false
-      affine defaults to true if transformations are used and to false when a matrix is given
-
-      We end up with 4 possibilities:
-      false, false: absolute direct matrix morph with SVG.Matrix
-      true, false: relative direct matrix morph with SVG.Marix or relative whatever was passed transformation with ObjectBag
-
-      false, true: absolute affine transformation with SVG.TransformBag
-      true, true: relative whatever was passed transformation with ObjectBag
-    **/
-
-    // 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
-    var origin
-    var current
-    var element
-    if(relative && !isMatrix) {
-      morpher = new SVG.Morphable().type(SVG.Morphable.ObjectBag)
-        .stepper(this._stepper)
-
-      this.queue(function() {
-        element = this.element()
-        element.addRunner(this)
-
-        if (origin == null) {
-          origin = new SVG.Point(getOrigin (transforms, element))
-          transforms = {...transforms, origin: [origin.x, origin.y]}
-          morpher.to(formatTransforms(transforms))
-        }
-
-        let from = current || {origin}
-        morpher.from(formatTransforms(from))
-
-      }, function (pos) {
-        let currentMatrix = element._currentTransform(this)
-        let {x, y} = origin.transform(currentMatrix)
-
-
-        /*
-          1. Transform the origin by figuring out the delta
-
-            - At the start, we had:
-
-              let Sinv = new SVG.Matrix(element).inverse()
-              let origin = getOrigin(element)
-
-            - At a particular frame we have:
-
-              let C = Matrix(element)
-              let newOrigin = origin.transform(S.inv).transform(C)
-        */
-
-        // this is an ugly hack to update the origins in the morpher
-        let index = morpher._from.indexOf('ox')
-        morpher._from.splice(index, 4, 'ox', x, 'oy', y)
-        morpher._to.splice(index, 4, 'ox', x, 'oy', y)
-
-        current = morpher.at(pos).valueOf()
-        let matrix = new SVG.Matrix(current)
-        this.addTransform(matrix)
-
-        return morpher.done()
-      }, true)
 
-      this._isDeclarative && this._rememberMorpher('transform', morpher)
-      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:
-    // - false, true with SVG.Matrix
-    // - false, true with SVG.TransformBag
-    // - true, false with SVG.Matrix
-    // - false, false with SVG.Matrix
+    const morpher = new SVG.Morphable().type(
+      affine ? SVG.Morphable.TransformBag2 : SVG.Matrix
+    ).stepper(this._stepper)
 
-    var morphType = (isMatrix && !affine)
-      ? SVG.Matrix
-      : SVG.Morphable.TransformBag
+    let origin
+    let element
+    let current
+    let currentAngle
+    var u = 0
+    this.queue(function () {
 
-    morpher = new SVG.Morphable().type(morphType)
-    morpher.stepper(this._stepper)
-    morpher.to(transforms)
+      // make sure element and origin is defined
+      element = element || this.element()
+      origin = origin || getOrigin(transforms, element)
 
-    this.queue(function() {
-      element = this.element()
+      // add the runner to the element so it can merge transformations
       element.addRunner(this)
 
-      if (!origin && affine) {
-        origin = new SVG.Point(getOrigin (transforms, element))
-        transforms = {...transforms, origin: [origin.x, origin.y]}
-        morpher.to(transforms)
-      }
-
       if (!relative) {
         // Deactivate all transforms that have run so far if we are absolute
         element._clearTransformRunnersBefore(this)
       }
 
-      // FIXME: This should be current = current || ...
-      // Define the starting point for the morpher
-      let startMatrix = new SVG.Matrix(relative ? undefined : element)
+      let target = new SVG.Matrix({...transforms, origin})
+      let start = current || new SVG.Matrix(relative ? undefined : element)
 
-      // make sure to add an origin if we morph affine
       if (affine) {
-        startMatrix.origin = origin
+        target = target.decompose(origin[0], origin[1])
+        start = start.decompose(origin[0], origin[1])
 
         // Get the current and target angle as it was set
-        const rTarget = morpher._to[3]
-        const rCurrent = startMatrix.decompose(origin[0], origin[1]).rotate
+        const rTarget = target.rotate
+        const rCurrent = start.rotate
 
         // Figure out the shortest path to rotate directly
         const possibilities = [rTarget - 360, rTarget, rTarget + 360]
         const distances = possibilities.map( a => Math.abs(a - rCurrent) )
         const shortest = Math.min(...distances)
         const index = distances.indexOf(shortest)
-        const target = possibilities[index]
+        target.rotate = possibilities[index]
+      }
 
-        // HACK: We directly replace the rotation so that its faster
-        morpher._to.splice(3, 1, target)
+      if (relative) {
+        target.rotate = transforms.rotate || 0
+        start.rotate = currentAngle || start.rotate
       }
 
-      // make sure morpher starts at the current matrix for declarative
-      // this happens only when the init function is called multiple times
-      // which is only true for declarative
-      morpher.from(current || startMatrix)
+      morpher.from(start)
+      morpher.to(target)
+
     }, function (pos) {
 
+      // clear all other transforms before this in case something is saved
+      // on this runner. We are absolute. We dont need these!
       if (!relative) this.clearTransform()
 
-      // Retarget the origin if we are in an
+      // fix the origin so is in the right space
       if (affine) {
         let currentMatrix = element._currentTransform(this)
-        let {x, y} = origin.transform(currentMatrix)
+        let {x, y} = new SVG.Point(origin).transform(currentMatrix)
         morpher._from.splice(-2, 2, x, y)
         morpher._to.splice(-2, 2, x, y)
       }
 
-      current = morpher.at(pos)
+      let affineParameters = morpher.at(pos)
+      currentAngle = affineParameters.rotate
+      current = new SVG.Matrix(affineParameters)
+
       this.addTransform(current)
+
       return morpher.done()
 
-    }, true)
+    }, function (newTransforms) {
+
+      // only get a new origin if it changed since the last call
+      if ((newTransforms.origin || 'center').toString() != (transforms.origin || 'center').toString()) {
+        origin = getOrigin (transforms, element)
+      }
+
+      // overwrite the old transformations with the new ones
+      transforms = {...newTransforms, origin}
+    })
+
 
     this._isDeclarative && this._rememberMorpher('transform', morpher)