]> source.dussan.org Git - svg.js.git/commitdiff
Started planning the way events work and got Spring working
authorSaivan <savian@me.com>
Mon, 28 May 2018 13:46:02 +0000 (23:46 +1000)
committerSaivan <savian@me.com>
Mon, 28 May 2018 13:46:02 +0000 (23:46 +1000)
dirty.html
src/controller.js
src/event.js
src/runner.js
src/timeline.js
useCases.md

index 1b8a3e3d89c784673497e137dfd615b3498fc85d..41c1583688d136a4c088e0bf0b42e57a5620981c 100644 (file)
@@ -54,7 +54,6 @@ function getColor(t) {
 
 
 
-
 // for (let i = 0 ; i < 15; i++) {
 //   for (let j = 0 ; j < 10; j++) {
 //
@@ -75,39 +74,42 @@ function getColor(t) {
 //   }
 // }
 
-var randPoint = (x = 50, y = 50) => [
-  Math.random() * 100 - 50 + x,
-  Math.random() * 100 - 50 + y
-]
-
-var poly = SVG('<polygon>').plot([
-  randPoint(),
-  randPoint(),
-  randPoint(),
-  randPoint(),
-  randPoint()
-]).attr({fill: 'none', stroke: 'black'}).addTo('svg')
-var polyAni = poly.animate(new SVG.Spring(300, 50))
-
-SVG.on(document, 'click', function (e) {
-  polyAni.plot([
-    randPoint(e.pageX-50, e.pageY-50),
-    randPoint(e.pageX+50, e.pageY-50),
-    randPoint(e.pageX+50, e.pageY),
-    randPoint(e.pageX+50, e.pageY+50),
-    randPoint(e.pageX-50, e.pageY+50)
-  ])
-})
+// var bla = SVG('<rect>').size(50, 50).center(100, 100).addTo('svg')
+// bla.animate().move(220, 200)
 
-// var mover = SVG('<ellipse>').size(50, 50).center(100, 100).addTo('svg')
-// var anim = mover.animate(SVG.PID(null, null, null, false)).move(500, 500)
+// var randPoint = (x = 50, y = 50) => [
+//   Math.random() * 100 - 50 + x,
+//   Math.random() * 100 - 50 + y
+// ]
+//
+// var poly = SVG('<polygon>').plot([
+//   randPoint(),
+//   randPoint(),
+//   randPoint(),
+//   randPoint(),
+//   randPoint()
+// ]).attr({fill: 'none', stroke: 'black'}).addTo('svg')
+// var polyAni = poly.animate(new SVG.Spring(3000, 0))
 //
-// SVG.on(document, 'mousemove', function (e) {
-//   //mover.animate(SVG.PID()).move(e.pageX, e.pageY)
-//   var p = mover.point(e.pageX, e.pageY)
-//   anim.center(p.x, p.y)
+// SVG.on(document, 'click', function (e) {
+//   polyAni.plot([
+//     randPoint(e.pageX-50, e.pageY-50),
+//     randPoint(e.pageX+50, e.pageY-50),
+//     randPoint(e.pageX+50, e.pageY),
+//     randPoint(e.pageX+50, e.pageY+50),
+//     randPoint(e.pageX-50, e.pageY+50)
+//   ])
 // })
 
+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) {
+  //mover.animate(SVG.PID()).move(e.pageX, e.pageY)
+  var p = mover.point(e.pageX, e.pageY)
+  anim.center(p.x, p.y)
+})
+
 </script>
 
 </body>
index 4ed0ac57c9f77a62b471a65c98a6d02d2f2d9137..359dc9f1161c2e3a01b736977d0be84b9df3c1f7 100644 (file)
@@ -91,18 +91,18 @@ SVG.Controller =  SVG.invent ({
   },
 })
 
-
 function recalculate () {
 
   // Apply the default parameters
-  this._duration = this._duration || 500
-  this._overshoot = this._overshoot || 0
+  var duration = (this._duration || 500) / 1000
+  var overshoot = this._overshoot || 0
 
   // Calculate the PID natural response
   var eps = 1e-10
-  var os = this._overshoot / 100 + eps
-  var zeta = -Math.log(os) / Math.sqrt(Math.PI ** 2 + Math.log(os) ** 2)
-  var wn = 4 / (zeta * this._duration / 1000)
+  var pi = Math.PI
+  var os = Math.log(overshoot / 100 + eps)
+  var zeta = - os / Math.sqrt(pi * pi + os * os)
+  var wn = 3.9 / ( zeta * duration )
 
   // Calculate the Spring values
   this.d = 2 * zeta * wn
@@ -125,18 +125,21 @@ SVG.Spring = SVG.invent ({
       if(dt == 0) return current
       dt /= 1000
 
-      // Get the parameters
-      var error = target - current
-      var lastError = c.error || 0
-      var velocity = (error - lastError) / dt
+      // Get the previous velocity
+      var velocity = c.velocity || 0
 
       // Apply the control to get the new position and store it
-      var control = this.d * velocity + this.k * error
-      var newPosition = current + 2 * control * dt * dt / 2
+      var acceleration = - this.d * velocity - this.k * (current - target)
+      var newPosition = current
+        + velocity * dt
+        + acceleration * dt * dt / 2
+
+      // Store the velocity
+      c.velocity = velocity + acceleration * dt
 
-      c.error = error
-      c.done = false //Math.abs(error) < 0.001
-      return newPosition
+      // Figure out if we have converged, and if so, pass the value
+      c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002
+      return c.done ? target : newPosition
     },
 
     duration: makeSetterGetter('_duration', recalculate),
@@ -181,7 +184,7 @@ SVG.PID = SVG.invent ({
 
       c.done = Math.abs(p) < 0.001
 
-      return current + (this.P * p + this.I * i + this.D * d)
+      return c.done ? target : current + (this.P * p + this.I * i + this.D * d)
     },
 
     windup: makeSetterGetter('windup'),
index 9d96ea7c30ce52f9e387fd9945d6665059bb76ca..ece00a81f905b83143e28a55a8b8d75c3434781c 100644 (file)
@@ -127,7 +127,7 @@ SVG.extend(SVG.Element, {
   },
   // Unbind event from listener
   off: function (event, listener) {
-    SVG.off(this.node, event, listener)
+    SVG.off(this, event, listener)
     return this
   },
   dispatch: function (event, data) {
index eecb950b0cc67fcada57b271b5a9d9c9e299166b..68dd7e822302974ffb7bef0182df333b4710b2b2 100644 (file)
@@ -23,9 +23,10 @@ SVG.Runner = SVG.invent({
       : options
 
     // Declare all of the variables
+    this._dispacher = document.createElement('div')
     this._element = null
-    this._queue = []
     this.done = false
+    this._queue = []
 
     // Work out the stepper and the duration
     this._duration = typeof options === 'number' && options
@@ -146,10 +147,10 @@ SVG.Runner = SVG.invent({
         alwaysInitialise: alwaysInitialise || false,
         initialiser: initFn || SVG.void,
         runner: runFn || SVG.void,
+        initialised: false,
         finished: false,
       })
       this.timeline()._continue()
-      this._element.timeline()._continue()
       return this
     },
 
@@ -157,6 +158,20 @@ SVG.Runner = SVG.invent({
       return this.queue(null, runFn, false)
     },
 
+    on (eventName, fn) {
+      SVG.on(this._dispacher, eventName, fn, this)
+      return this
+    },
+
+    // Queue a function to run after this runner
+    after (time, fn) {
+      return this.on('finish', fn)
+    },
+
+    fire: function (name) {
+
+    }
+
     /*
     Runner animation methods
     ========================
@@ -189,9 +204,9 @@ SVG.Runner = SVG.invent({
 
       // If we are on the rising edge, initialise everything, otherwise,
       // initialise only what needs to be initialised on the rising edge
-      var justStarted = this._last <= 0 && time >= 0
+      // var justStarted = this._last <= 0 && time >= 0
       var justFinished = this._last <= duration && finished
-      this._initialise(justStarted)
+      this._initialise()
       this._last = time
 
       // If we haven't started yet or we are over the time, just exit
@@ -208,6 +223,14 @@ SVG.Runner = SVG.invent({
 
       // Set whether this runner is complete or not
       this.done = finished
+      if (this.done) {
+        this._afterEvents.forEach(function (event) { event(this) })
+
+        if (this._element) this._element.fire(`runner.${id}.finish`, {runner: this})
+
+        el.animate().after()
+        el.on()
+      }
       return this
     },
 
@@ -287,16 +310,18 @@ SVG.Runner = SVG.invent({
     },
 
     // Run each initialise function in the runner if required
-    _initialise: function (all) {
+    _initialise: function () {
       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 always = current.alwaysInitialise
+        var needsInit = current.alwaysInitialise || !current.initialised
         var running = !current.finished
-        if ((always || all) && running) {
+
+        if (needsInit && running) {
           current.initialiser.call(this._element)
+          current.initialised = true
         }
       }
     },
index be8d52e777f1045926333c8186ae6ec962ee7b3b..154dabe29e1f7bd39e8f71bff9d996542a971043 100644 (file)
@@ -7,8 +7,6 @@ SVG.easing = {
   '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 }
 }
 
-var time = window.performance || window.Date
-
 SVG.Timeline = SVG.invent({
 
   // Construct a new timeline on the given element
@@ -40,71 +38,71 @@ SVG.Timeline = SVG.invent({
 
   extend: {
 
-    /**
-     * Runner Constructors
-     */
-
-    animate (duration, delay, nowOrAbsolute) {
-
-      // Clear the controller and the looping parameters
-      this._controller = duration instanceof Function ? duration : null
-      this._backwards = false
-      this._swing = false
-      this._loops = 0
-
-      // If we have an object we are declaring imperative animations
-      if (typeof duration === 'object') {
-        duration = duration.duration
-        delay = duration.delay
-        nowOrAbsolute = duration.absolute || duration.now
-      }
-
-      // The start time for the next animation can either be given explicitly,
-      // derived from the current timeline time or it can be relative to the
-      // last start time to chain animations direclty
-      var absoluteStartTime = typeof nowOrAbsolute === 'number' ? nowOrAbsolute
-        : nowOrAbsolute ? this._time
-        : this._startTime + this._duration
-
-      // We start the next animation after the delay required
-      this._startTime = absoluteStartTime + (delay || 0)
-      this._duration = duration instanceof Function ? null
-        : (duration || SVG.defaults.timeline.duration)
-
-      // Make a new runner to queue all of the animations onto
-      this._runner = new Runner(this._time - this._startTime, this.duration)
-      this._runners.push(this._runner)
-
-      // Step the animation
-      this._step()
-
-      // Allow for chaining
-      return this
-    },
-
-    delay (by, now) {
-      return this.animate(0, by, now)
-    },
-
-    /**
-     * Runner Behaviours
-     */
-
-    loop (swing, times, wait) {
-
-    },
-
-    ease (fn) {
-      var ease = SVG.easing[fn || SVG.defaults.timeline.ease] || fn
-      this._controller = function (from, to, pos) {
-        // FIXME: This is needed for at lest ObjectBag but could slow down stuff
-        if(typeof from !== 'number') {
-          return pos < 1 ? from : to
-        }
-        return from + (to - from) * ease(pos)
-      }
-      return this
-    },
+    // /**
+    //  * Runner Constructors
+    //  */
+    //
+    // animate (duration, delay, nowOrAbsolute) {
+    //
+    //   // Clear the controller and the looping parameters
+    //   this._controller = duration instanceof Function ? duration : null
+    //   this._backwards = false
+    //   this._swing = false
+    //   this._loops = 0
+    //
+    //   // If we have an object we are declaring imperative animations
+    //   if (typeof duration === 'object') {
+    //     duration = duration.duration
+    //     delay = duration.delay
+    //     nowOrAbsolute = duration.absolute || duration.now
+    //   }
+    //
+    //   // The start time for the next animation can either be given explicitly,
+    //   // derived from the current timeline time or it can be relative to the
+    //   // last start time to chain animations direclty
+    //   var absoluteStartTime = typeof nowOrAbsolute === 'number' ? nowOrAbsolute
+    //     : nowOrAbsolute ? this._time
+    //     : this._startTime + this._duration
+    //
+    //   // We start the next animation after the delay required
+    //   this._startTime = absoluteStartTime + (delay || 0)
+    //   this._duration = duration instanceof Function ? null
+    //     : (duration || SVG.defaults.timeline.duration)
+    //
+    //   // Make a new runner to queue all of the animations onto
+    //   this._runner = new Runner(this._time - this._startTime, this.duration)
+    //   this._runners.push(this._runner)
+    //
+    //   // Step the animation
+    //   this._continue()
+    //
+    //   // Allow for chaining
+    //   return this
+    // },
+    //
+    // delay (by, now) {
+    //   return this.animate(0, by, now)
+    // },
+    //
+    // /**
+    //  * Runner Behaviours
+    //  */
+    //
+    // loop (swing, times, wait) {
+    //
+    // },
+    //
+    // ease (fn) {
+    //   var ease = SVG.easing[fn || SVG.defaults.timeline.ease] || fn
+    //   this._controller = function (from, to, pos) {
+    //     // FIXME: This is needed for at lest ObjectBag but could slow down stuff
+    //     if(typeof from !== 'number') {
+    //       return pos < 1 ? from : to
+    //     }
+    //     return from + (to - from) * ease(pos)
+    //   }
+    //   return this
+    // },
 
     reverse () {
 
@@ -114,17 +112,17 @@ SVG.Timeline = SVG.invent({
      *
      */
 
-    tag (name) {
-      this._runner.tag(name)
-    },
-
-    runner (tag) {
-      if (tag) {
-        return this._runners.find(function (runTag) {return runTag === tag})
-      } else {
-        return this._runner
-      }
-    },
+    // tag (name) {
+    //   this._runner.tag(name)
+    // },
+    //
+    // runner (tag) {
+    //   if (tag) {
+    //     return this._runners.find(function (runTag) {return runTag === tag})
+    //   } else {
+    //     return this._runner
+    //   }
+    // },
 
     schedule (runner, delay, when) {
 
@@ -135,15 +133,21 @@ SVG.Timeline = SVG.invent({
       delay = delay || 0
 
       // Work out when to start the animation
-      if ( when == null || when === 'last' || when === 'relative' ) {
+      if ( when == null || when === 'last' || when === 'after' ) {
         // Take the last time and increment
-        // FIXME: How to figue out the relative time? Maybe use runner.endTime()
         absoluteStartTime = this._startTime + delay
 
       } else if (when === 'absolute' || when === 'start' ) {
         absoluteStartTime = delay
+
       } else if (when === 'now') {
         absoluteStartTime = this._time + delay
+
+      } else if ( when === 'relative' ) {
+
+        // TODO: If the runner already exists, shift it by the delay, otherwise
+        // this is relative to the start time ie: 0
+
       } else {
         // TODO: Throw error
       }
@@ -151,9 +155,7 @@ SVG.Timeline = SVG.invent({
       runner.time(-absoluteStartTime)
       this._startTime = absoluteStartTime + runner._duration
       this._runners.push(runner)
-
-      this._step()
-
+      this._continue()
       return this
     },
 
@@ -202,16 +204,16 @@ SVG.Timeline = SVG.invent({
       // 0 by default
     },
 
-    queue (initFn, runFn) {
-
-      // Make sure there is a function available
-      initFn = (initFn || SVG.void).bind(this)
-      runFn = (runFn || SVG.void).bind(this)
-
-      // Add the functions to the active runner
-      this._runner.add(initFn, runFn)
-      return this
-    },
+    // queue (initFn, runFn) {
+    //
+    //   // Make sure there is a function available
+    //   initFn = (initFn || SVG.void).bind(this)
+    //   runFn = (runFn || SVG.void).bind(this)
+    //
+    //   // Add the functions to the active runner
+    //   this._runner.add(initFn, runFn)
+    //   return this
+    // },
 
     // Queue a function to run after some time
     after (time, fn) {
index a15d51168416af14e12bc851ec1518664fb219d1..328ca2a3aafd0c83622f11ae6f7d28e42ded9630 100644 (file)
@@ -15,10 +15,10 @@ var animation = element
     .tag('second')
     .scale(3)
 
-animation.finish('first')
-animation.pause('first')
-animation.stop('first')
-animation.play('first')
+element.timeline.finish()
+element.timeline.pause()
+element.timeline.stop()
+element.timeline.play()
 
 ```
 
@@ -30,11 +30,16 @@ The user can specify their time which is relative to the timelines time.
 
 ```js
 
-var animation = element
-  .animate(2000).move(200, 200)
+var animation = element.animate(2000).move(200, 200)
 
 // after 1000 ms
-animation.animate(1000, 0, 500).scale(2)
+animation.animate(1000, 500, 'absolute').scale(2)
+
+
+var runner = elemenet.move(0, 0).animate(1000)
+
+// after 500ms
+runner.move(200, 200)
 
 ```
 
@@ -51,17 +56,11 @@ control over each animation that they define.
 
 ```js
 
-let animationA = element.loop(300, ">").rotate(360).runner()
-let animationB = element
-    .loop(200, "><")
-    .scale(2)
-    .runner(tag)
+let animationA = element.loop(300, ">").rotate(360)
+let animationB = element.loop(200, "><").scale(2)
 
-// After some time, they might want to end the first animation abruptly
-animationB.enable(false).end()
-
-// Maybe they want to pause a runner
-animationB.enable(false)
+// Maybe they want to disable a runner - which acts like pausing
+animationB.active(false)
 
 // Maybe they want to remove an animation matching a tag
 animationB.tag('B')
@@ -69,13 +68,36 @@ element.timeline().remove('B')
 
 // They can move around a runner as well
 element.timeline()
-    .schedule('B', 300) // Moves a runner to start at 300
+    .schedule('B', 300, 'absolute') // Moves a runner to start at 300
         // time(currentAbsolute - newAbsolute)
     .shift('B', 300)    // Shifts the runner start time by 300
+    // which is sugar to
+    .schedule('B', 300, 'relative')
         // seek(shiftTime)
 
 ```
 
+Lets demonstrate the difference between the schedule and shift
+
+```
+Given this:
+
+    --------
+        --------------
+                    ----------------
+
+Schedule:
+                --------
+                --------------
+                ----------------
+
+Shift:
+            --------
+                --------------
+                            ----------------
+```
+
+
 
 # A Sequenced Animation
 
@@ -296,3 +318,14 @@ runner.schedule(timeline, ...rest)
     .animate()...
 
 ```
+
+# Binding Events
+
+The user might want to react to some events that the runner might emit. We will
+emit the following events from the runner:
+- start - when a runner first initialises
+- finish - when a runner finishes
+- during - on every step
+- done - when a function completes
+
+Maybe they also want to react to timeline events as well