From 1cb509f863b17af90827012e6f6ae81cb694f654 Mon Sep 17 00:00:00 2001 From: Saivan Date: Tue, 27 Feb 2018 17:23:59 +1100 Subject: Made developing a little more enjoyable This commit adds playgrounds and a build:dev mode. Now you can call npm run build:dev to make the linter warn you about errors without breaking. Also, we now have playgrounds, where you can use a built version of svg.js to run your own tests. --- bench/svg.bench.js | 67 +++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 34 deletions(-) (limited to 'bench') diff --git a/bench/svg.bench.js b/bench/svg.bench.js index 5b9541e..50ef547 100644 --- a/bench/svg.bench.js +++ b/bench/svg.bench.js @@ -1,56 +1,55 @@ -;( function() { +/* global Snap */ +;(function () { SVG.bench = { // Initalize test store - _chain: [] - , _before: function() {} - , _after: function() {} - , draw: SVG().addTo('#draw') - , snap: Snap(100, 100) - , raw: document.getElementById('native') + _chain: [], + _before: function () {}, + _after: function () {}, + draw: SVG().addTo('#draw'), + snap: Snap(100, 100), + raw: document.getElementById('native'), // Add descriptor - , describe: function(name, closure) { + describe: function (name, closure) { this._chain.push({ - name: name - , run: closure + name: name, + run: closure }) return this - } + }, // Add test - , test: function(name, run) { + test: function (name, run) { // run test - var start = ( new Date ).getTime() + var start = (new Date()).getTime() run() - this.write( name, ( new Date ).getTime() - start ) + this.write(name, (new Date()).getTime() - start) // clear everything this.clear() - } + }, // Skip test - , skip: function(name, run) { - this.write( name, false ) - } + skip: function (name, run) { + this.write(name, false) + }, // Run tests - , run: function() { + run: function () { this.pad() - + for (var h, i = 0, il = this._chain.length; i < il; i++) { - var h = document.createElement('h1') + h = document.createElement('h1') h.innerHTML = this._chain[i].name - this.pad().appendChild(h) - this._chain[i].run(this) } - } - + }, + // Write result - , write: function(name, ms) { + write: function (name, ms) { var test = document.createElement('div') if (typeof ms === 'number') { @@ -60,14 +59,14 @@ test.className = 'test skipped' test.innerHTML = name + ' (skipped)' } - + this.pad().appendChild(test) return this - } + }, // Reference writable element - , pad: function() { + pad: function () { var pad = document.getElementById('pad') if (!pad) { @@ -76,15 +75,15 @@ } return pad - } + }, // Clear canvasses - , clear: function() { - while(this.raw.hasChildNodes()) + clear: function () { + while (this.raw.hasChildNodes()) { this.raw.removeChild(this.raw.lastChild) + } this.draw.clear() this.snap.clear() } } - -})(); \ No newline at end of file +})() -- cgit v1.2.3 From 64b3144c89247d0be176bf01c28aa5fe6bef84d5 Mon Sep 17 00:00:00 2001 From: Saivan Date: Mon, 23 Apr 2018 20:25:41 +1000 Subject: Added some timeline and Morphing functions --- bench/runner.html | 7 +- bench/tests/10000-accesses.js | 35 ++++ src/default.js | 7 + src/fx.js | 25 ++- src/matrix.js | 6 +- src/morph.js | 394 ++++++++++++++++++++++++++++++++++++++++++ src/number.js | 6 + src/timeline.js | 174 ++++++++++++++----- 8 files changed, 602 insertions(+), 52 deletions(-) create mode 100644 bench/tests/10000-accesses.js create mode 100644 src/morph.js (limited to 'bench') diff --git a/bench/runner.html b/bench/runner.html index 965a884..6d1bed2 100644 --- a/bench/runner.html +++ b/bench/runner.html @@ -41,13 +41,14 @@ - + + - \ No newline at end of file + diff --git a/bench/tests/10000-accesses.js b/bench/tests/10000-accesses.js new file mode 100644 index 0000000..4c7dfea --- /dev/null +++ b/bench/tests/10000-accesses.js @@ -0,0 +1,35 @@ + +SVG.bench.describe('Access a dom attribues vs dom properties vs object properties', function(bench) { + bench.test('using an object', function() { + var sum = 0 + var obj = {x: "30"} + for (var i = 0; i < 1000000; i++) { + sum += obj.x * i + } + console.log(sum) + }) + + bench.test('figure out what the overhead is', function () { + var obj = bench.draw.rect(100, 100).move(0, 0) + }) + + bench.test('using dom attriutes', function () { + var sum = 0 + var obj = bench.draw.rect(100, 100).move(0, 0) + var node = obj.node + for (var i = 0; i < 1000000; i++) { + sum += node.getAttribute('x') * i + } + console.log(sum, node.getAttribute('x')) + }) + + bench.test('using dom properties', function () { + var sum = 0 + var obj = bench.draw.rect(100, 100).move(0, 0) + var node = obj.node + for (var i = 0; i < 1000000; i++) { + sum += node.x.baseVal * i + } + console.log(sum, node.x) + }) +}) diff --git a/src/default.js b/src/default.js index c77b028..f33083c 100644 --- a/src/default.js +++ b/src/default.js @@ -1,6 +1,13 @@ SVG.defaults = { + // Default animation values + timeline: { + duration: 600, + ease: '>', + delay: 0, + }, + // Default attribute values attrs: { diff --git a/src/fx.js b/src/fx.js index c94f747..dd515df 100644 --- a/src/fx.js +++ b/src/fx.js @@ -142,12 +142,30 @@ Controlable () new Controller(target, controller) + + +Number +Array +PathArray +ViewBox +PointArray +Color + + + + + + + + + + SVG.Timeline = { styleAttr (type, name, val) { - let morpher = new Morph(this.controller).to(val) + let morpher = new Morph(val).controller(this.controller) queue ( ()=> { - morpher = morpher.from(element[type]('name')) + morpher = morpher.morph(element[type]('name')) }, morpher.at ) @@ -159,7 +177,7 @@ SVG.Timeline = { let morpher = declarative ? new Controller(target) : new Morph().to(val) queue ( ()=> { - morpher = morpher.from(element[type]('name')) + morpher = morpher.from(element[type](name)) }, () => { this.element[type](name, morpher.at(pos)) @@ -339,7 +357,6 @@ this.queue(fn, morpher) new Morph(x(), xGiven) - x: function (x, relative) { if (this.target() instanceof SVG.G) { this.transform({x: x}, relative) diff --git a/src/matrix.js b/src/matrix.js index 1649370..cab657f 100644 --- a/src/matrix.js +++ b/src/matrix.js @@ -10,7 +10,11 @@ SVG.Matrix = SVG.invent({ source = source instanceof SVG.Element ? source.matrixify() : typeof source === 'string' ? arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat)) : Array.isArray(source) ? arrayToMatrix(source) - : typeof source === 'object' ? source + : (typeof source === 'object' && ( + source.a != null || source.b != null || source.c != null + || source.d != null || source.e != null || source.f != null + )) ? source + : (typeof source === 'object') ? new SVG.Matrix().transform(source) : arguments.length === 6 ? arrayToMatrix([].slice.call(arguments)) : base diff --git a/src/morph.js b/src/morph.js new file mode 100644 index 0000000..7e99fef --- /dev/null +++ b/src/morph.js @@ -0,0 +1,394 @@ +SVG.Morphable = SVG.invent{ + create: function (controller) { + this.controller = controller || function (from, to, pos) { + return pos < 1 ? from : to + } + }, + + extend: { + + from: function (val) { + this._from = this._set(val) + return this + } + + to: function (val, modifier) { + this._to = this._set(val) + this.modifier = modifier + return this + } + + type: function (type) { + this._type = type + return this + } + + _set: function (value) { + + if(!this._type) { + if (SVG.Color.isColor(val)) { + this._type = SVG.Color + + } else if (SVG.regex.delimiter.test(val)) { + + this._type = SVG.regex.pathLetters.test(val) + ? SVG.PathArray + : SVG.Array + + } else if (SVG.regex.numberAndUnit.test(val)) { + this._type = SVG.Number + + } else if (value in SVG.MorphableTypes) { + this._type = value.constructor + + // } else if (typeof value == 'object') { + // this._type = SVG.Morphable.TransformBag + } else { + this._type = SVG.Morphable.NonMorphable + } + } + + return (new this._type(value)).toArray() + } + + controller: function (controller) { + this._controller = controller + } + + at: function (pos) { + + var _this = this + + // for(var i = 0, len = this._from.length; i < len; ++i) { + // arr.push(this.controller(this._from[i], this._to[i])) + // } + + return this.type.fromArray(this.modifier(this._from.map(function (i, index) { + return _this.controller(i, _this._to[i], pos) + }))) + }, + + valueOf: function () { + return this._value + } + } +} + +SVG.Morphable.NonMorphable = SVG.invent({ + create: function (val) { + this.value = val + }, + + extend: { + valueOf: function () { + return this.value + }, + + toArray: function () { + return [this.value] + }, + + fromArray: function (arr) { + return new SVG.Morphable.NonMorphable(arr[0]) + } + } +}) + +SVG.Morphable.TransformBag = SVG.invent({ + create: function (val) { + this.value = new Matrix(val).decompose() + }, + + extend: { + valueOf: function () { + return this.value + }, + + toArray: function (){ + var v = this.value + + return [ + v.scaleX, + v.scaleY, + v.shear, + v.rotate, + v.translateX, + v.translateY + ] + } + + fromArray: function (arr) { + return new SVG.Morphable.TransformBag({ + scaleX: arr[0], + scaleY: arr[1], + shear: arr[2], + rotate: arr[3], + translateX: arr[4], + translateY: arr[5] + }) + } +}) + +SVG.MorphableTypes = [SVG.Number, SVG.Color, SVG.Box, SVG.Matrix, SVG.Morphable.NonMorphable, SVG.Morphable.TransformBag] +SVG.extend(SVG.MorphableTypes, { + to: (item, args) => { + let a = new SVG.Morphable().type(this.constructor).to(item, args) + }, +}) + + + + +// - Objects are just variable bags +// - morph rerutrns a morphable. No state on normal objects (like SVG.Color) +// - Objects can be represented as Array (with toArray()) +// - Objects have an unmorph/fromarray function which converts it back to a normal object + +// var b = new Color('#fff') +// b.morph('#000') === new Morph(b).to('#000') + +// sweet = Color('#fff') +// dark = Color('#fef') +// sweet.to(dark, 'hsl') + +// angle = Number(30) +// lastAngle = Number(300) +// angle.to(lastAngle, cyclic) + +// mat1 = Matrix().transform({rotation: 30, scale: 0}) +// mat2 = Matrix(30, 40, 50, 60, 10, 20) +// mat1.to(mat2) + + + +/** + ** absolute transformations + **/ + +// M v -----|-----(D M v = I v)------|-----> T v +// +// 1. define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx] +// 2. on every frame: pull the current state of all previous transforms (M - m can change) +// and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0] +// 3. Find the interpolated matrix I(pos) = m + pos * (t - m) +// - Note I(0) = M +// - Note I(1) = T +// 4. Now you get the delta matrix as a result: D = I * inv(M) + + + + +el.animate().trasform({rotate: 720, scale: 2}, true) + +el.animate().scale(2).rotate(720) + +el.animate().transform({origin: traslate, }) + +absolute -> start at current - {affine params} +relative -> start at 0 always - {random stuff} + + + |> object.toArray() + |> (_) => _.map(() => {}) + |> modifier + |> fromArray + +function transform(transforms, relative, affine) { + affine = transforms.affine || affine + relative = transforms.relative || relative + + // 1. define the final state (T) and decompose it (once) t = [tx, ty, the, lam, sy, sx] + var morpher = new SVG.Morphable.TransformBag().to(transforms) + + // make sure you have an identity matrix defined as default for relative transforms + var morpher.from() + var el = this.target() + + var initFn = relative ? function() {} : function() { + // 2. on every frame: pull the current state of all previous transforms (M - m can change) + morpher.from(el.transform()) + } + + this.queue(initFn, function (pos) { + // 3. Find the interpolated matrix I(pos) = m + pos * (t - m) + // - Note I(0) = M + // - Note I(1) = T + var matrix = morpher.at(pos) + + if(!relative) { + // 4. Now you get the delta matrix as a result: D = I * inv(M) + matrix = matrix.multiply(el.transform().inverse()) + } + + el.pushTransform(matrix) + }) +} + + +SVG.Morphable.TransformList = Object + + if(affine) { + var morpher = new Matrix().to(transforms) + } + + if(input is typeof plain object) { + // deal with a ttransformList + this.type = SVG.Morphable.TransformList + } + + var morpher = new Morphable(modifier).to(transforms) + + this.queue(() => { + morpher.from(this.transform()) + }, (pos) => { + var matrix = morpher.at(pos) + el.transform(matrix) + }) + +el.transform({rotate: 720, sclae: 2, }) + +el.scale(2) + .rotate(720) + +from -> 300 +to -> [295, 305] + +from -> 358 +to -> 1 + + + +from -> 300 +to -> 30 + +function transform(transforms, affine) { + + if(relative) { + + var morpher = new Morphable().to(transforms, affine) + this.queue(() => {}, (pos) => { + var matrix = morpher.at(pos) + el.transform(matrix) + }) + + } else { + + this.queue(() => { + morpher + }, (pos) => { + var matrix = morpher.at(pos) + el.transform(matrix) + }) + + } +} + + + +// el.animate().rotate(480) +function rotate(val) { // Relative + var morpher = new Morphable().from(0).to(val) + this.queue(() => {}, (pos)=> { + var rotation = morpher.at(pos) + el.rotate(rotation) + }) +} +// morph = new Morphable(0).to(50, [0, 360]) -> in timeline +// +// +// +// on each frame +// el.rotate(morph.at(pos)) + + +Morph.modifiers = { + + hsb: + + +} + + +new Color(#fff).to('#000', 'hsb') + +at returns a matrix anyway + +new Number() + + +el.animate().fill('#000', 'hsb') +function fill(val, colorspace) { + var morpher = new Morphable().to(val, colorspace || 'rgb') + + this.queue((val) => { + morpher.from(val) + }, (pos)=> { + var color = morpher.at(pos) + el.fill(color) + }) +} + +// +// Number.toArray() -> [3] +// Color.toArray() -> [red, green, blue] +// +// +// +// +// +// +// +// +// + + + + + + + + + +new Color(30, 50, 40).toArray() + + + + +new PathArray([['M', 0, 3], ['L', 4, 5]]).morph(5, 3, 2, 8, 5) + +controller = (s, e, p)=> {return s + (e-s) * p} + +[['M', 0, 3], ['L', 4, 5], ['A', 120, 120, 1, 0]] + + +['1', '2', '3'] => parseFloat() + + +rect.anim() + .color('blue') + .anim() + .color(new Color('red')) + + + +a = new SVG.Color('#3f2').to('#5f4').at(0.3) + + +new Morphable('#3f2').to('#5f4').at(0.4) + + + + + + + +/* +zoom(level, point) { + let morpher = SVG.Number(level).controller(this.controller) + this.queue( + () => {morpher = morpher.from(element.zoom())}, + (pos) => {element.zoom(morpher.at(pos), point)} + ) + return this +} +*/ diff --git a/src/number.js b/src/number.js index 6413f94..e6cecc2 100644 --- a/src/number.js +++ b/src/number.js @@ -100,5 +100,11 @@ SVG.Number = SVG.invent({ .plus(this) } + + +new SVG.Color('#2a4e5a').morph('#3b4f2a').at(0.4) + +new Morph().from('#2a4e5a').to('#3b4f2a').at(0.3) + } }) diff --git a/src/timeline.js b/src/timeline.js index 035c0f7..5106058 100644 --- a/src/timeline.js +++ b/src/timeline.js @@ -11,9 +11,9 @@ SVG.easing = { function Runner (timeline) { // We store a reference to the function to run and the timeline to use - this.functions = [] this.timeline = timeline this.transforms = [] + this.functions = [] this.done = false // We copy the current values from the timeline because they can change @@ -29,11 +29,10 @@ Runner.prototype = { this.functions.push(fn) }, - run: function (time) { - - var line = this.timeline + step: function (time) { // If it is time to do something, act now. + var end = this._start + this._duration var running = this._start < time && time < end if (running && this._running) { var position = (time - this._startTime) / this._duration @@ -50,7 +49,7 @@ Runner.prototype = { closure.finished = !running }, - stop: function () { + snap: function () { }, @@ -60,44 +59,128 @@ Runner.prototype = { } +let time = window.performance || window.Date + + SVG.Timeline = SVG.invent({ - create: function (o, easy, delay, epoch) { + // Construct a new timeline on the given element + create: function (element) { - this.baseTransform = [] - this.runners = [] - this.controller = null + // Store a reference to the element to call its parent methods + this._element = element - if(o instanceof 'function') { - this.controller = o + // Store the timing variables + this._startTime = time.now() + this._duration = SVG.defaults.duration + this._ease = SVG.defaults.ease - } else if (typeof o === 'object') { - ease = o.ease - delay = o.delay - o = o.duration - } + // Play control variables control how the animation proceeds + this._controller = o instanceof 'function' ? o : null + this._backwards = false + this._reverse = false + this._loops = 0 - this.ease = ease - this.delay = delay - this.duration = o + // Keep track of the running animations and their starting parameters + this._baseTransform = null + this._running = true + this._runners = [] }, extend: { - animate (duration, ease, delay, epoch) - loop (times, reverse) - duration (time) - delay (by, epoch) - ease (fn) - - play () - pause () - stop () - finish (all=true) - speed (newSpeed) - seek (dt) - persist (dt || forever) // 0 by default - reverse () + animate (duration, delay, now) { + + // Clear the controller and the looping parameters + this._controller = null + this._backwards = false + this._swing = false + this._loops = 0 + + // If we have a controller, we will use the declarative animation mode + if(duration instanceof 'function') { + + this._controller = duration + + // If we have an object we are declaring imperative animations + } else if (typeof duration === 'object') { + + ease = duration.ease + delay = duration.delay + now = duration.now + duration = duration.duration + } + + // We start the next animation after the old one is complete + this._startTime = now ? time.now() : (this._startTime + this._duration) + this._duration = duration || SVG.defaults.duration + + // Make a new runner to take care of the + + return this + }, + + duration (time) { + return this.animate(time, 0, false) + }, + + delay (by, now) { + return this.animate(0, by, now) + }, + + ease (fn) { + this._ease = SVG.easing[fn || SVG.defaults.ease] || fn + return this + }, + + loop (times, swing) { + this._loops = times + this._swing = swing + }, + + play () { + this._running = true + this._continue() + }, + + pause () { + this._running = false + }, + + stop () { + this.pause() + + // Cancel all of the requested animation frames + + }, + + finish (all=true) { + + }, + + speed (newSpeed) { + + }, + + seek (dt) { + + }, + + persist (dt || forever) { + // 0 by default + }, + + reverse () { + + }, + + queue (initialise, during) { + + // Make a new runner + var runner = new Runner(this) + this._runners.push() + + }, _step (dt) { @@ -105,23 +188,26 @@ SVG.Timeline = SVG.invent({ // Checks if we are running and continues the animation _continue () { - , continue: function () { - if (this.paused) return - if (!this.nextFrame) - this.step() - return this - } + if (this._paused) return + + // Go through each of the runners and step them + }, - } }, + // Only elements are animatable + parent: SVG.Element, + // These methods will be added to all SVG.Element objects construct: { - animate: function(o, ease, delay, epoch) { - return (this.timeline = this.timeline || new SVG.Timeline(o, ease, delay, epoch)) + animate: function(o, delay, now) { + + // Get the current timeline or construct a new one + this.timeline = (this.timeline || new SVG.Timeline(this)) + .animate(o, delay, now) + return this.timeline } } - } // Extend the attribute methods separately to avoid cluttering the main @@ -150,7 +236,7 @@ SVG.extend(SVG.Timeline, { this.queue( function () { - morpher = morpher.from(element[type]('name')) + morpher = morpher.from(element[type](name)) }, function () { this.element[type](name, morpher.at(pos)) -- cgit v1.2.3 From d3dd6ad74b35e674ab24a8d9bf0c3b2f336b5bca Mon Sep 17 00:00:00 2001 From: Ulrich-Matthias Schäfer Date: Mon, 8 Oct 2018 15:18:53 +0200 Subject: 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 (limited to 'bench') 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) -- cgit v1.2.3