Add #interpolate() method to Segment, Path and CompoundPath

Closes #624
This commit is contained in:
Jürg Lehni 2016-02-02 22:11:06 +01:00
parent 53269ab169
commit 922a502ee2
3 changed files with 99 additions and 1 deletions

View file

@ -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}
*

View file

@ -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

View file

@ -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() {