diff --git a/src/path/PathItem.js b/src/path/PathItem.js index 376a5131..130db607 100644 --- a/src/path/PathItem.js +++ b/src/path/PathItem.js @@ -315,7 +315,7 @@ var PathItem = Item.extend(/** @lends PathItem# */{ && this._getWinding(point); return !!(this.getFillRule() === 'evenodd' ? winding & 1 : winding); /*#*/ } // !__options.nativeContains && __options.booleanOperations - } + }, // TODO: Write about negative indices, and add an example for ranges. /** @@ -467,6 +467,47 @@ var PathItem = Item.extend(/** @lends PathItem# */{ * paths[4].smooth({ type: 'continuous', from: -1, to: 1 }); */ + /** + * Interpolates between the two specified path items and uses the result + * as the geometry for this path item. The number of children and + * segments in the two paths involved in the operation should be the same. + * + * @param {PathItem} from the path item defining the geometry when `factor` + * is `0` + * @param {PathItem} to the path item defining the geometry when `factor` + * is `1` + * @param {Number} factor the interpolation coefficient, typically between + * `0` and `1`, but extrapolation is possible too + */ + interpolate: function(from, to, factor) { + var isPath = !this._children, + name = isPath ? '_segments' : '_children', + itemsFrom = from[name], + itemsTo = to[name], + items = this[name]; + if (!itemsFrom || !itemsTo || itemsFrom.length !== itemsTo.length) { + throw new Error('Invalid operands in interpolate() call: ' + + from + ', ' + to); + } + var current = items.length, + length = itemsTo.length; + if (current < length) { + var ctor = isPath ? Segment : Path; + for (var i = current; i < length; i++) { + this.add(new ctor()); + } + } else if (current > length) { + this[isPath ? 'removeSegments' : 'removeChildren'](length, current); + } + for (var i = 0; i < length; i++) { + items[i].interpolate(itemsFrom[i], itemsTo[i], factor); + } + if (isPath) { + this.setClosed(from._closed); + this._changed(/*#=*/Change.GEOMETRY); + } + }, + /** * {@grouptitle Postscript Style Drawing Commands} * diff --git a/src/path/Segment.js b/src/path/Segment.js index fb3d6ad3..e71562dc 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -580,6 +580,38 @@ var Segment = Base.extend(/** @lends Segment# */{ this._changed(); }, + /** + * Interpolates between the two specified segments and sets the point and + * handles of this segment accordingly. + * + * @param {Segment} from the segment defining the geometry when `factor` is + * `0` + * @param {Segment} to the segment defining the geometry when `factor` is + * `1` + * @param {Number} factor the interpolation coefficient, typically between + * `0` and `1`, but extrapolation is possible too + */ + interpolate: function(from, to, factor) { + var u = 1 - factor, + v = factor, + point1 = from._point, + point2 = to._point, + handleIn1 = from._handleIn, + handleIn2 = to._handleIn, + handleOut2 = to._handleOut, + handleOut1 = from._handleOut; + this._point.set( + u * point1._x + v * point2._x, + u * point1._y + v * point2._y, true); + this._handleIn.set( + u * handleIn1._x + v * handleIn2._x, + u * handleIn1._y + v * handleIn2._y, true); + this._handleOut.set( + u * handleOut1._x + v * handleOut2._x, + u * handleOut1._y + v * handleOut2._y, true); + this._changed(); + }, + _transformCoordinates: function(matrix, coords, change) { // Use matrix.transform version() that takes arrays of multiple // points for largely improved performance, as no calls to diff --git a/test/tests/Path.js b/test/tests/Path.js index c1d9c217..8dfdd3db 100644 --- a/test/tests/Path.js +++ b/test/tests/Path.js @@ -292,6 +292,31 @@ test('Simplifying a path with three segments of the same position should not thr }, 1); }); +test('Path#interpolate', function() { + var path = new Path(), + path0 = new Path.Circle({ + center: [0, 0], + radius: 10 + }), + path1 = new Path.Ellipse({ + center: [10, 20], + radius: [20, 10] + }), + halfway = new Path.Ellipse({ + center: [5, 10], + radius: [15, 10] + }); + + path.interpolate(path0, path1, 0); + equals(path, path0); + + path.interpolate(path0, path1, 1); + equals(path, path1); + + path.interpolate(path0, path1, 0.5); + equals(path, halfway); +}); + QUnit.module('Path Curves'); test('path.curves synchronisation', function() {