mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-20 22:39:50 -05:00
Implement CompoundPath#flatten(), #simplify(), #smooth()
And improve documentation for PathItem#simplify(). Closes #920 Relates to #727
This commit is contained in:
parent
c793538841
commit
3965dd9b77
4 changed files with 141 additions and 123 deletions
|
@ -134,15 +134,6 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
||||||
return items;
|
return items;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverses the orientation of all nested paths.
|
|
||||||
*/
|
|
||||||
reverse: function() {
|
|
||||||
var children = this._children;
|
|
||||||
for (var i = 0, l = children.length; i < l; i++)
|
|
||||||
children[i].reverse();
|
|
||||||
},
|
|
||||||
|
|
||||||
// DOCS: reduce()
|
// DOCS: reduce()
|
||||||
// TEST: reduce()
|
// TEST: reduce()
|
||||||
reduce: function reduce(options) {
|
reduce: function reduce(options) {
|
||||||
|
@ -162,13 +153,6 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
||||||
return reduce.base.call(this);
|
return reduce.base.call(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
// NOTE: Documentation is in PathItem.js
|
|
||||||
smooth: function(options) {
|
|
||||||
var children = this._children;
|
|
||||||
for (var i = 0, l = children.length; i < l; i++)
|
|
||||||
children[i].smooth(options);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies whether the compound path is oriented clock-wise.
|
* Specifies whether the compound path is oriented clock-wise.
|
||||||
*
|
*
|
||||||
|
@ -331,42 +315,50 @@ new function() { // Injection scope for PostScript-like drawing functions
|
||||||
return children[children.length - 1];
|
return children[children.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
var fields = {
|
|
||||||
// NOTE: Documentation for these methods is found in PathItem, as they
|
|
||||||
// are considered abstract methods of PathItem and need to be defined in
|
|
||||||
// all implementing classes.
|
|
||||||
moveTo: function(/* point */) {
|
|
||||||
var current = getCurrentPath(this),
|
|
||||||
// Reuse current path if nothing was added yet
|
|
||||||
path = current && current.isEmpty() ? current
|
|
||||||
: new Path(Item.NO_INSERT);
|
|
||||||
if (path !== current)
|
|
||||||
this.addChild(path);
|
|
||||||
path.moveTo.apply(path, arguments);
|
|
||||||
},
|
|
||||||
|
|
||||||
moveBy: function(/* point */) {
|
|
||||||
var current = getCurrentPath(this, true),
|
|
||||||
last = current && current.getLastSegment(),
|
|
||||||
point = Point.read(arguments);
|
|
||||||
this.moveTo(last ? point.add(last._point) : point);
|
|
||||||
},
|
|
||||||
|
|
||||||
closePath: function(join) {
|
|
||||||
getCurrentPath(this, true).closePath(join);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Redirect all other drawing commands to the current path
|
// Redirect all other drawing commands to the current path
|
||||||
Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo', 'arcTo',
|
return Base.each(['lineTo', 'cubicCurveTo', 'quadraticCurveTo', 'curveTo',
|
||||||
'lineBy', 'cubicCurveBy', 'quadraticCurveBy', 'curveBy', 'arcBy'],
|
'arcTo', 'lineBy', 'cubicCurveBy', 'quadraticCurveBy', 'curveBy',
|
||||||
function(key) {
|
'arcBy'],
|
||||||
fields[key] = function() {
|
function(key) {
|
||||||
var path = getCurrentPath(this, true);
|
this[key] = function() {
|
||||||
path[key].apply(path, arguments);
|
var path = getCurrentPath(this, true);
|
||||||
};
|
path[key].apply(path, arguments);
|
||||||
}
|
};
|
||||||
);
|
}, {
|
||||||
|
// NOTE: Documentation for these methods is found in PathItem, as
|
||||||
|
// they are considered abstract methods of PathItem and need to be
|
||||||
|
// defined in all implementing classes.
|
||||||
|
moveTo: function(/* point */) {
|
||||||
|
var current = getCurrentPath(this),
|
||||||
|
// Reuse current path if nothing was added yet
|
||||||
|
path = current && current.isEmpty() ? current
|
||||||
|
: new Path(Item.NO_INSERT);
|
||||||
|
if (path !== current)
|
||||||
|
this.addChild(path);
|
||||||
|
path.moveTo.apply(path, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
return fields;
|
moveBy: function(/* point */) {
|
||||||
});
|
var current = getCurrentPath(this, true),
|
||||||
|
last = current && current.getLastSegment(),
|
||||||
|
point = Point.read(arguments);
|
||||||
|
this.moveTo(last ? point.add(last._point) : point);
|
||||||
|
},
|
||||||
|
|
||||||
|
closePath: function(join) {
|
||||||
|
getCurrentPath(this, true).closePath(join);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, Base.each(['reverse', 'flatten', 'simplify', 'smooth'], function(key) {
|
||||||
|
// Injection scope for methods forwarded to the child paths.
|
||||||
|
// NOTE: Documentation is in PathItem
|
||||||
|
this[key] = function(param) {
|
||||||
|
var children = this._children,
|
||||||
|
res;
|
||||||
|
for (var i = 0, l = children.length; i < l; i++) {
|
||||||
|
res = children[i][key](param) || res;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
}, {}));
|
||||||
|
|
|
@ -1070,6 +1070,8 @@ statics: {
|
||||||
/**
|
/**
|
||||||
* Returns the curve-time parameter of the specified point if it lies on the
|
* Returns the curve-time parameter of the specified point if it lies on the
|
||||||
* curve, `null` otherwise.
|
* curve, `null` otherwise.
|
||||||
|
* Note that if there is more than one possible solution in a
|
||||||
|
* self-intersecting curve, the first found result is returned.
|
||||||
*
|
*
|
||||||
* @param {Point} point the point on the curve
|
* @param {Point} point the point on the curve
|
||||||
* @return {Number} the curve-time parameter of the specified point
|
* @return {Number} the curve-time parameter of the specified point
|
||||||
|
|
|
@ -1202,9 +1202,6 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverses the orientation of the path, by reversing all its segments.
|
|
||||||
*/
|
|
||||||
reverse: function() {
|
reverse: function() {
|
||||||
this._segments.reverse();
|
this._segments.reverse();
|
||||||
// Reverse the handles:
|
// Reverse the handles:
|
||||||
|
@ -1223,33 +1220,6 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
this._changed(/*#=*/Change.GEOMETRY);
|
this._changed(/*#=*/Change.GEOMETRY);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the curves in a path to straight lines with an even distribution
|
|
||||||
* of points. The distance between the produced segments is as close as
|
|
||||||
* possible to the value specified by the `maxDistance` parameter.
|
|
||||||
*
|
|
||||||
* @param {Number} maxDistance the maximum distance between the points
|
|
||||||
*
|
|
||||||
* @example {@paperscript}
|
|
||||||
* // Flattening a circle shaped path:
|
|
||||||
*
|
|
||||||
* // Create a circle shaped path at { x: 80, y: 50 }
|
|
||||||
* // with a radius of 35:
|
|
||||||
* var path = new Path.Circle({
|
|
||||||
* center: new Size(80, 50),
|
|
||||||
* radius: 35
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* // Select the path, so we can inspect its segments:
|
|
||||||
* path.selected = true;
|
|
||||||
*
|
|
||||||
* // Create a copy of the path and move it 150 points to the right:
|
|
||||||
* var copy = path.clone();
|
|
||||||
* copy.position.x += 150;
|
|
||||||
*
|
|
||||||
* // Convert its curves to points, with a max distance of 20:
|
|
||||||
* copy.flatten(20);
|
|
||||||
*/
|
|
||||||
flatten: function(maxDistance) {
|
flatten: function(maxDistance) {
|
||||||
var iterator = new PathIterator(this, 64, 0.1),
|
var iterator = new PathIterator(this, 64, 0.1),
|
||||||
pos = 0,
|
pos = 0,
|
||||||
|
@ -1291,46 +1261,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
// NOTE: Documentation is in PathItem#simplify()
|
||||||
* Smooths a path by simplifying it. The {@link Path#segments} array is
|
|
||||||
* analyzed and replaced by a more optimal set of segments, reducing memory
|
|
||||||
* usage and speeding up drawing.
|
|
||||||
*
|
|
||||||
* @param {Number} [tolerance=2.5]
|
|
||||||
*
|
|
||||||
* @example {@paperscript height=300}
|
|
||||||
* // Click and drag below to draw to draw a line, when you release the
|
|
||||||
* // mouse, the is made smooth using path.simplify():
|
|
||||||
*
|
|
||||||
* var path;
|
|
||||||
* function onMouseDown(event) {
|
|
||||||
* // If we already made a path before, deselect it:
|
|
||||||
* if (path) {
|
|
||||||
* path.selected = false;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* // Create a new path and add the position of the mouse
|
|
||||||
* // as its first segment. Select it, so we can see the
|
|
||||||
* // segment points:
|
|
||||||
* path = new Path({
|
|
||||||
* segments: [event.point],
|
|
||||||
* strokeColor: 'black',
|
|
||||||
* selected: true
|
|
||||||
* });
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function onMouseDrag(event) {
|
|
||||||
* // On every drag event, add a segment to the path
|
|
||||||
* // at the position of the mouse:
|
|
||||||
* path.add(event.point);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function onMouseUp(event) {
|
|
||||||
* // When the mouse is released, simplify the path:
|
|
||||||
* path.simplify();
|
|
||||||
* path.selected = true;
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
simplify: function(tolerance) {
|
simplify: function(tolerance) {
|
||||||
var segments = new PathFitter(this).fit(tolerance || 2.5);
|
var segments = new PathFitter(this).fit(tolerance || 2.5);
|
||||||
if (segments)
|
if (segments)
|
||||||
|
|
|
@ -317,6 +317,46 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
/*#*/ } // !__options.nativeContains && __options.booleanOperations
|
/*#*/ } // !__options.nativeContains && __options.booleanOperations
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverses the orientation of the path item. When called on
|
||||||
|
* {@link CompoundPath} items, each of the nested paths is reversed. On
|
||||||
|
* {@link Path} items, the sequence of {@link Path#segments} is reversed.
|
||||||
|
*
|
||||||
|
* @name PathItem#reverse
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the curves in a path to straight lines with an even distribution
|
||||||
|
* of points. The distance between the produced segments is as close as
|
||||||
|
* possible to the value specified by the `maxDistance` parameter.
|
||||||
|
*
|
||||||
|
* @name PathItem#flatten
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @param {Number} maxDistance the maximum distance between the points
|
||||||
|
*
|
||||||
|
* @example {@paperscript}
|
||||||
|
* // Flattening a circle shaped path:
|
||||||
|
*
|
||||||
|
* // Create a circle shaped path at { x: 80, y: 50 }
|
||||||
|
* // with a radius of 35:
|
||||||
|
* var path = new Path.Circle({
|
||||||
|
* center: new Size(80, 50),
|
||||||
|
* radius: 35
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Select the path, so we can inspect its segments:
|
||||||
|
* path.selected = true;
|
||||||
|
*
|
||||||
|
* // Create a copy of the path and move it 150 points to the right:
|
||||||
|
* var copy = path.clone();
|
||||||
|
* copy.position.x += 150;
|
||||||
|
*
|
||||||
|
* // Convert its curves to points, with a max distance of 20:
|
||||||
|
* copy.flatten(20);
|
||||||
|
*/
|
||||||
|
|
||||||
// TODO: Write about negative indices, and add an example for ranges.
|
// TODO: Write about negative indices, and add an example for ranges.
|
||||||
/**
|
/**
|
||||||
* Smooths the path item without changing the amount of segments in the path
|
* Smooths the path item without changing the amount of segments in the path
|
||||||
|
@ -467,6 +507,59 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
||||||
* paths[4].smooth({ type: 'continuous', from: -1, to: 1 });
|
* paths[4].smooth({ type: 'continuous', from: -1, to: 1 });
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fits a sequence of as few curves as possible through the path's anchor
|
||||||
|
* points, ignoring the path items's curve-handles, with an allowed maximum
|
||||||
|
* error. When called on {@link CompoundPath} items, each of the nested
|
||||||
|
* paths is simplified. On {@link Path} items, the {@link Path#segments}
|
||||||
|
* array is processed and replaced by the resulting sequence of fitted
|
||||||
|
* curves.
|
||||||
|
*
|
||||||
|
* This method can be used to process and simplify the point data received
|
||||||
|
* from a mouse or touch device.
|
||||||
|
*
|
||||||
|
* @name PathItem#simplify
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @param {Number} [tolerance=2.5] the allowed maximum error when fitting
|
||||||
|
* the curves through the segment points
|
||||||
|
* @return {Boolean} {@true if the method was capable of fitting curves
|
||||||
|
* through the path's segment points}
|
||||||
|
*
|
||||||
|
* @example {@paperscript height=300}
|
||||||
|
* // Click and drag below to draw to draw a line, when you release the
|
||||||
|
* // mouse, the is made smooth using path.simplify():
|
||||||
|
*
|
||||||
|
* var path;
|
||||||
|
* function onMouseDown(event) {
|
||||||
|
* // If we already made a path before, deselect it:
|
||||||
|
* if (path) {
|
||||||
|
* path.selected = false;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Create a new path and add the position of the mouse
|
||||||
|
* // as its first segment. Select it, so we can see the
|
||||||
|
* // segment points:
|
||||||
|
* path = new Path({
|
||||||
|
* segments: [event.point],
|
||||||
|
* strokeColor: 'black',
|
||||||
|
* selected: true
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* function onMouseDrag(event) {
|
||||||
|
* // On every drag event, add a segment to the path
|
||||||
|
* // at the position of the mouse:
|
||||||
|
* path.add(event.point);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* function onMouseUp(event) {
|
||||||
|
* // When the mouse is released, simplify the path:
|
||||||
|
* path.simplify();
|
||||||
|
* path.selected = true;
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interpolates between the two specified path items and uses the result
|
* Interpolates between the two specified path items and uses the result
|
||||||
* as the geometry for this path item. The number of children and
|
* as the geometry for this path item. The number of children and
|
||||||
|
|
Loading…
Reference in a new issue