From b96c88212ea943a0d8950d6d71d9a3bc3ee40705 Mon Sep 17 00:00:00 2001 From: Saivan Date: Thu, 5 Jul 2018 21:40:08 +1000 Subject: [PATCH] Fixed the transformation code to use the bbox properly This commit fixes the transformations and allows single animated transformations to properly work. --- dirty.html | 40 +++++++++++---- src/helpers.js | 28 +++------- src/matrix.js | 24 ++++----- src/runner.js | 45 ++++++++-------- src/transform.js | 131 +---------------------------------------------- todo.md | 8 +-- 6 files changed, 75 insertions(+), 201 deletions(-) diff --git a/dirty.html b/dirty.html index df7c871..3c9b241 100644 --- a/dirty.html +++ b/dirty.html @@ -259,9 +259,7 @@ SVG('#absolute').on('input slide', function (e) { let a = canvas.rect(200, 400).move(500, 400) .attr('opacity', 0.3) .addClass('pink') - .transform({ tx: 100, ty: 500, origin: 'top-left' }) - -canvas.ellipse(20, 20).center(700, 1100) + // .transform({ tx: 100, ty: 500, origin: 'top-left' }) var timer = 0 @@ -271,20 +269,42 @@ a.timeline().source(() => { return timer }) -let obj = { rotate: 180, origin: 'center', translate: [300, 0] } -let obj2 = { rotate: 360, origin: 'center' } +let obj = { rotate: 180, origin: 'top-left', tx: 500} +let obj2 = { rotate: 280, origin: 'center' } -a.clone() // startPosition +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) // animation +// 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)) +// var b = a.clone().animate(new SVG.Spring(500, 30)) //debugger -b.transform(obj) // animation -//a.animate(300).transform(obj, true).transform(obj2, true) // animation +//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) + +// +//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}) +// }) + + diff --git a/src/helpers.js b/src/helpers.js index b25d3b6..d4abc08 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -243,20 +243,8 @@ function formatTransforms (o) { // Populate all of the values return { - scaleX: scaleX, - scaleY: scaleY, - skewX: skewX, - skewY: skewY, - shear: shear, - theta: theta, - rx: rx, - ry: ry, - tx: tx, - ty: ty, - ox: ox, - oy: oy, - px: px, - py: py + scaleX, scaleY, skewX, skewY, shear, theta, + rx, ry, tx, ty, ox, oy, px, py } } @@ -273,20 +261,16 @@ function getOrigin (o, element) { const { height, width, x, y } = element.bbox() // Calculate the transformed x and y coordinates - const bx = string.includes('left') ? x + let bx = string.includes('left') ? x : string.includes('right') ? x + width : x + width / 2 - const by = string.includes('top') ? y + let by = string.includes('top') ? y : string.includes('bottom') ? y + height : y + height / 2 - // Find the new center in the transformed coordinates - const matrix = new SVG.Matrix(element) - const {x: tx , y: ty} = new SVG.Point(bx, by).transform(matrix) - // Set the bounds eg : "bottom-left", "Top right", "middle" etc... - const ox = o.ox != null ? o.ox : tx - const oy = o.oy != null ? o.oy : ty + const ox = o.ox != null ? o.ox : bx + const oy = o.oy != null ? o.oy : by // Set the origin based on the current matrix location return [ox, oy] diff --git a/src/matrix.js b/src/matrix.js index 07dc82b..2a9f2a9 100644 --- a/src/matrix.js +++ b/src/matrix.js @@ -45,30 +45,30 @@ SVG.Matrix = SVG.invent({ // Get the proposed transformations and the current transformations var t = formatTransforms(o) - var currentTransform = new SVG.Matrix(this) + var current = new SVG.Matrix(this) + let { x: ox, y: oy } = new SVG.Point(t.ox, t.oy).transform(current) // Construct the resulting matrix - var transformer = new SVG.Matrix(currentTransform) - .translate(-t.ox, -t.oy) + var transformer = new SVG.Matrix() + .translate(t.rx, t.ry) + .lmultiply(current) + .translate(-ox, -oy) .scale(t.scaleX, t.scaleY) .skew(t.skewX, t.skewY) .shear(t.shear) .rotate(t.theta) - .translate(t.ox, t.oy) - .translate(t.rx, t.ry) + .translate(ox, oy) // If we want the origin at a particular place, we force it there if (isFinite(t.px) || isFinite(t.py)) { - // Figure out where the origin went and the delta to get there - var current = new SVG.Point(t.ox - t.rx, t.oy - t.ry).transform(transformer) - var dx = t.px ? t.px - current.x : 0 - var dy = t.py ? t.py - current.y : 0 - - // Apply another translation + const origin = new SVG.Point(ox, oy).transform(transformer) + // TODO: Replace t.px with isFinite(t.px) + const dx = t.px ? t.px - origin.x : 0 + const dy = t.py ? t.py - origin.y : 0 transformer = transformer.translate(dx, dy) } - // We can apply translations after everything else + // Translate now after positioning transformer = transformer.translate(t.tx, t.ty) return transformer }, diff --git a/src/runner.js b/src/runner.js index 1454ad9..da494d3 100644 --- a/src/runner.js +++ b/src/runner.js @@ -616,8 +616,10 @@ SVG.extend(SVG.Runner, { // Parse the parameters var isMatrix = transforms.a != null - affine = transforms.affine || affine || !isMatrix relative = transforms.relative || relative + affine = transforms.affine != null + ? transforms.affine + : (affine != null ? affine : !isMatrix) /** The default of relative is false @@ -645,24 +647,23 @@ SVG.extend(SVG.Runner, { .stepper(this._stepper) this.queue(function() { - if (origin == null) { - element = this.element() - let transformedOrigin = getOrigin (transforms, element) - origin = new SVG.Point(transformedOrigin) - .transform(new SVG.Matrix(element).inverse()) + 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)) - this.element().addRunner(this) }, function (pos) { let currentMatrix = element._currentTransform(this) let {x, y} = origin.transform(currentMatrix) + /* 1. Transform the origin by figuring out the delta @@ -685,6 +686,7 @@ SVG.extend(SVG.Runner, { current = morpher.at(pos).valueOf() let matrix = new SVG.Matrix(current) this.addTransform(matrix) + return morpher.done() }, true) @@ -713,15 +715,8 @@ SVG.extend(SVG.Runner, { element.addRunner(this) if (!origin && affine) { - // origin = getOrigin(transforms, element) - // transforms = {...transforms, origin} - - let transformedOrigin = getOrigin (transforms, element) - origin = new SVG.Point(transformedOrigin) - .transform(new SVG.Matrix(element).inverse()) - + origin = new SVG.Point(getOrigin (transforms, element)) transforms = {...transforms, origin: [origin.x, origin.y]} - morpher.to(transforms) } @@ -730,6 +725,7 @@ SVG.extend(SVG.Runner, { element._clearTransformRunnersBefore(this) } + // FIXME: This should be current = current || ... // Define the starting point for the morpher let startMatrix = new SVG.Matrix(relative ? undefined : element) @@ -737,17 +733,18 @@ SVG.extend(SVG.Runner, { if (affine) { startMatrix.origin = origin - // that a hack to update the rotation in the morpher - // so it always takes the shortest path - const rTarget = transforms.rotate || 0 + // Get the current and target angle as it was set + const rTarget = morpher._to[3] const rCurrent = startMatrix.decompose(origin[0], origin[1]).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] + // HACK: We directly replace the rotation so that its faster morpher._to.splice(3, 1, target) } @@ -759,11 +756,13 @@ SVG.extend(SVG.Runner, { if (!relative) this.clearTransform() - let currentMatrix = element._currentTransform(this) - let {x, y} = origin.transform(currentMatrix) - - morpher._from.splice(-2, 2, x, y) - morpher._to.splice(-2, 2, x, y) + // Retarget the origin if we are in an + if (affine) { + let currentMatrix = element._currentTransform(this) + let {x, y} = origin.transform(currentMatrix) + morpher._from.splice(-2, 2, x, y) + morpher._to.splice(-2, 2, x, y) + } current = morpher.at(pos) this.addTransform(current) diff --git a/src/transform.js b/src/transform.js index 8b1d56b..0549d7a 100644 --- a/src/transform.js +++ b/src/transform.js @@ -58,7 +58,7 @@ SVG.extend(SVG.Element, { } // Set the origin according to the defined transform - o = {...o, origin: getOrigin(o, this)} + o = {...o, origin: getOrigin(o, this, relative)} // The user can pass a boolean, an SVG.Element or an SVG.Matrix or nothing var cleanRelative = relative === true ? this : (relative || false) @@ -66,132 +66,3 @@ SVG.extend(SVG.Element, { return this.attr('transform', result) } }) - -SVG.extend(SVG.Timeline, { - transform: function (o, relative, affine) { - - // // get target in case of the fx module, otherwise reference this - // var target = this.target() - // , matrix, bbox - // - // // act as a getter - // if (typeof o !== 'object') { - // // get current matrix - // matrix = new SVG.Matrix(target).extract() - // - // return typeof o === 'string' ? matrix[o] : matrix - // } - // - // // ensure relative flag - // relative = !!relative || !!o.relative - // - // // act on matrix - // if (o.a != null) { - // matrix = new SVG.Matrix(o) - // - // // act on rotation - // } else if (o.rotation != null) { - // // ensure centre point - // ensureCentre(o, target) - // - // // apply transformation - // matrix = new SVG.Rotate(o.rotation, o.cx, o.cy) - // - // // act on scale - // } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { - // // ensure centre point - // ensureCentre(o, target) - // - // // ensure scale values on both axes - // o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 - // o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 - // - // matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) - // - // // act on skew - // } else if (o.skewX != null || o.skewY != null) { - // // ensure centre point - // ensureCentre(o, target) - // - // // ensure skew values on both axes - // o.skewX = o.skewX != null ? o.skewX : 0 - // o.skewY = o.skewY != null ? o.skewY : 0 - // - // matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) - // - // // act on flip - // } else if (o.flip) { - // if(o.flip == 'x' || o.flip == 'y') { - // o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset - // } else { - // if(o.offset == null) { - // bbox = target.bbox() - // o.flip = bbox.cx - // o.offset = bbox.cy - // } else { - // o.flip = o.offset - // } - // } - // - // matrix = new SVG.Matrix().flip(o.flip, o.offset) - // - // // act on translate - // } else if (o.x != null || o.y != null) { - // matrix = new SVG.Translate(o.x, o.y) - // } - // - // if(!matrix) return this - // - // matrix.relative = relative - // - // this.last().transforms.push(matrix) - // - // return this._callStart() - // } - // // ensure scale values on both axes - // o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 - // o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 - // - // matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) - // - // // act on skew - // } else if (o.skewX != null || o.skewY != null) { - // // ensure centre point - // ensureCentre(o, target) - // - // // ensure skew values on both axes - // o.skewX = o.skewX != null ? o.skewX : 0 - // o.skewY = o.skewY != null ? o.skewY : 0 - // - // matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) - // - // // act on flip - // } else if (o.flip) { - // if (o.flip === 'x' || o.flip === 'y') { - // o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset - // } else { - // if (o.offset == null) { - // bbox = target.bbox() - // o.flip = bbox.cx - // o.offset = bbox.cy - // } else { - // o.flip = o.offset - // } - // } - // - // matrix = new SVG.Matrix().flip(o.flip, o.offset) - // - // // act on translate - // } else if (o.x != null || o.y != null) { - // matrix = new SVG.Translate(o.x, o.y) - // } - // - // if (!matrix) return this - // - // matrix.relative = relative - // - // this.last().transforms.push(matrix) - // - // return this._callStart() - } -}) diff --git a/todo.md b/todo.md index 9b0621a..3a9d9ba 100644 --- a/todo.md +++ b/todo.md @@ -10,10 +10,10 @@ Ulima ===== - Use runners[runnerid] = {startTime, runner, persist} timeline.persist('monkey-in', Infinity) - - -- animation result is different from setting directly -- format transforms didnt take all parameters into account (theta / rotate) +- folding transformations +- testing direct non affine morph +- why cant i use current? +- handle null values Both ==== -- 2.39.5