mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Split #resolveCrossings() into #resolveCrossings() and #reorient()
Closes #973
This commit is contained in:
parent
7acb5bee45
commit
3f058e6471
1 changed files with 134 additions and 130 deletions
|
@ -55,7 +55,7 @@ PathItem.inject(new function() {
|
||||||
.transform(null, true, true);
|
.transform(null, true, true);
|
||||||
if (closed)
|
if (closed)
|
||||||
res.setClosed(true);
|
res.setClosed(true);
|
||||||
return closed ? res.resolveCrossings() : res;
|
return closed ? res.resolveCrossings().reorient() : res;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createResult(ctor, paths, reduce, path1, path2) {
|
function createResult(ctor, paths, reduce, path1, path2) {
|
||||||
|
@ -854,17 +854,13 @@ PathItem.inject(new function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resolves all crossings of a path item, first by splitting the path or
|
* Resolves all crossings of a path item by splitting the path or
|
||||||
* compound-path in each self-intersection and tracing the result, then
|
* compound-path in each self-intersection and tracing the result.
|
||||||
* fixing the orientation of the resulting sub-paths by making sure that
|
|
||||||
* all sub-paths are of different winding direction than the first path,
|
|
||||||
* except for when individual sub-paths are disjoint, i.e. islands,
|
|
||||||
* which are reoriented so that:
|
|
||||||
* - The holes have opposite winding direction.
|
|
||||||
* - Islands have to have the same winding direction as the first child.
|
|
||||||
* If possible, the existing path / compound-path is modified if the
|
* If possible, the existing path / compound-path is modified if the
|
||||||
* amount of resulting paths allows so, otherwise a new path /
|
* amount of resulting paths allows so, otherwise a new path /
|
||||||
* compound-path is created, replacing the current one.
|
* compound-path is created, replacing the current one.
|
||||||
|
*
|
||||||
|
* @return {PahtItem} the resulting path item
|
||||||
*/
|
*/
|
||||||
resolveCrossings: function() {
|
resolveCrossings: function() {
|
||||||
var children = this._children,
|
var children = this._children,
|
||||||
|
@ -881,8 +877,8 @@ PathItem.inject(new function() {
|
||||||
var hasOverlaps = false,
|
var hasOverlaps = false,
|
||||||
hasCrossings = false,
|
hasCrossings = false,
|
||||||
intersections = this.getIntersections(null, function(inter) {
|
intersections = this.getIntersections(null, function(inter) {
|
||||||
return inter._overlap && (hasOverlaps = true)
|
return inter._overlap && (hasOverlaps = true) ||
|
||||||
|| inter.isCrossing() && (hasCrossings = true);
|
inter.isCrossing() && (hasCrossings = true);
|
||||||
});
|
});
|
||||||
intersections = CurveLocation.expand(intersections);
|
intersections = CurveLocation.expand(intersections);
|
||||||
if (hasOverlaps) {
|
if (hasOverlaps) {
|
||||||
|
@ -932,72 +928,11 @@ PathItem.inject(new function() {
|
||||||
this.push.apply(this, path._segments);
|
this.push.apply(this, path._segments);
|
||||||
}, []));
|
}, []));
|
||||||
}
|
}
|
||||||
// By now, all paths are non-overlapping, but might be fully
|
// Determine how to return the paths: First try to recycle the
|
||||||
// contained inside each other.
|
// current path / compound-path, if the amount of paths does not
|
||||||
// Next we adjust their orientation based on on further checks:
|
// require a conversion.
|
||||||
var length = paths.length,
|
var length = paths.length,
|
||||||
item;
|
item;
|
||||||
if (length > 1) {
|
|
||||||
// First order the paths by the area of their bounding boxes.
|
|
||||||
// Make a clone of paths as it may still be the children array.
|
|
||||||
paths = paths.slice().sort(function (a, b) {
|
|
||||||
return b.getBounds().getArea() - a.getBounds().getArea();
|
|
||||||
});
|
|
||||||
var first = paths[0],
|
|
||||||
items = [first],
|
|
||||||
excluded = {},
|
|
||||||
isNonZero = this.getFillRule() === 'nonzero',
|
|
||||||
windings = isNonZero && Base.each(paths, function(path) {
|
|
||||||
this.push(path.isClockwise() ? 1 : -1);
|
|
||||||
}, []);
|
|
||||||
// Walk through paths, from largest to smallest.
|
|
||||||
// The first, largest child can be skipped.
|
|
||||||
for (var i = 1; i < length; i++) {
|
|
||||||
var path = paths[i],
|
|
||||||
point = path.getInteriorPoint(),
|
|
||||||
isContained = false,
|
|
||||||
container = null,
|
|
||||||
exclude = false;
|
|
||||||
for (var j = i - 1; j >= 0 && !container; j--) {
|
|
||||||
// We run through the paths from largest to smallest,
|
|
||||||
// meaning that for any current path, all potentially
|
|
||||||
// containing paths have already been processed and
|
|
||||||
// their orientation has been fixed. Since we want to
|
|
||||||
// achieve alternating orientation of contained paths,
|
|
||||||
// all we have to do is to find one include path that
|
|
||||||
// contains the current path, and then set the
|
|
||||||
// orientation to the opposite of the containing path.
|
|
||||||
if (paths[j].contains(point)) {
|
|
||||||
if (isNonZero && !isContained) {
|
|
||||||
windings[i] += windings[j];
|
|
||||||
// Remove path if rule is nonzero and winding
|
|
||||||
// of path and containing path is not zero.
|
|
||||||
if (windings[i] && windings[j]) {
|
|
||||||
exclude = excluded[i] = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isContained = true;
|
|
||||||
// If the containing path is not excluded, we're
|
|
||||||
// done searching for the orientation defining path.
|
|
||||||
container = !excluded[j] && paths[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!exclude) {
|
|
||||||
// Set to the opposite orientation of containing path,
|
|
||||||
// or the same orientation as the first path if the path
|
|
||||||
// is not contained in any other path.
|
|
||||||
path.setClockwise(container ? !container.isClockwise()
|
|
||||||
: first.isClockwise());
|
|
||||||
items.push(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Replace paths with the processed items list:
|
|
||||||
paths = items;
|
|
||||||
length = items.length;
|
|
||||||
}
|
|
||||||
// First try to recycle the current path / compound-path, if the
|
|
||||||
// amount of paths do not require a conversion.
|
|
||||||
if (length > 1 && children) {
|
if (length > 1 && children) {
|
||||||
if (paths !== children) {
|
if (paths !== children) {
|
||||||
// TODO: Fix automatic child-orientation in CompoundPath,
|
// TODO: Fix automatic child-orientation in CompoundPath,
|
||||||
|
@ -1020,64 +955,133 @@ PathItem.inject(new function() {
|
||||||
this.replaceWith(item);
|
this.replaceWith(item);
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
},
|
||||||
};
|
|
||||||
}, /** @lends PathItem# */{
|
/**
|
||||||
/**
|
* Fixes the orientation of the sub-paths of a compound-path, by first
|
||||||
* Returns a point that is guaranteed to be inside the path.
|
* ordering them according to the area they cover, and then making sure
|
||||||
*
|
* that all sub-paths are of different winding direction than the first,
|
||||||
* @bean
|
* biggest path, except for when individual sub-paths are disjoint,
|
||||||
* @type Point
|
* i.e. islands, which are reoriented so that:
|
||||||
*/
|
*
|
||||||
getInteriorPoint: function() {
|
* - The holes have opposite winding direction.
|
||||||
var bounds = this.getBounds(),
|
* - Islands have to have the same winding direction as the first child.
|
||||||
point = bounds.getCenter(true);
|
*
|
||||||
if (!this.contains(point)) {
|
* @return {PahtItem} a reference to the item itself, reoriented
|
||||||
// Since there is no guarantee that a poly-bezier path contains the
|
*/
|
||||||
// center of its bounding rectangle, we shoot a ray in x direction
|
reorient: function() {
|
||||||
// and select a point between the first consecutive intersections of
|
var children = this._children;
|
||||||
// the ray on the left.
|
if (children && children.length > 1) {
|
||||||
var curves = this.getCurves(),
|
// First order the paths by their areas.
|
||||||
y = point.y,
|
children = this.removeChildren().sort(function (a, b) {
|
||||||
intercepts = [],
|
return abs(b.getArea()) - abs(a.getArea());
|
||||||
roots = [];
|
});
|
||||||
// Get values for all y-monotone curves that intersect the ray at y.
|
var first = children[0],
|
||||||
for (var i = 0, l = curves.length; i < l; i++) {
|
paths = [first],
|
||||||
var v = curves[i].getValues(),
|
excluded = {},
|
||||||
o0 = v[1],
|
isNonZero = this.getFillRule() === 'nonzero',
|
||||||
o1 = v[3],
|
windings = isNonZero && Base.each(children, function(path) {
|
||||||
o2 = v[5],
|
this.push(path.isClockwise() ? 1 : -1);
|
||||||
o3 = v[7];
|
}, []);
|
||||||
if (y >= Math.min(o0, o1, o2, o3) &&
|
// Walk through children, from largest to smallest.
|
||||||
y <= Math.max(o0, o1, o2, o3)) {
|
// The first, largest child can be skipped.
|
||||||
var monos = Curve.getMonoCurves(v);
|
for (var i = 1, l = children.length; i < l; i++) {
|
||||||
for (var j = 0, m = monos.length; j < m; j++) {
|
var path = children[i],
|
||||||
var mv = monos[j],
|
point = path.getInteriorPoint(),
|
||||||
mo0 = mv[1],
|
isContained = false,
|
||||||
mo3 = mv[7];
|
container = null,
|
||||||
// Filter out horizontal monotone curves by comparing
|
exclude = false;
|
||||||
// their ordinate values, and make sure the y coordinate
|
for (var j = i - 1; j >= 0 && !container; j--) {
|
||||||
// is within the curve before testing for intercepts.
|
// We run through the paths from largest to smallest,
|
||||||
if ((mo0 !== mo3) &&
|
// meaning that for any current path, all potentially
|
||||||
(y >= mo0 && y <= mo3 || y >= mo3 && y <= mo0)) {
|
// containing paths have already been processed and
|
||||||
var x = y === mo0 ? mv[0]
|
// their orientation has been fixed. Since we want to
|
||||||
: y === mo3 ? mv[6]
|
// achieve alternating orientation of contained paths,
|
||||||
: Curve.solveCubic(mv, 1, y, roots, 0, 1) === 1
|
// all we have to do is to find one include path that
|
||||||
? Curve.getPoint(mv, roots[0]).x
|
// contains the current path, and then set the
|
||||||
: (mv[0] + mv[6]) / 2;
|
// orientation to the opposite of the containing path.
|
||||||
intercepts.push(x);
|
if (children[j].contains(point)) {
|
||||||
|
if (isNonZero && !isContained) {
|
||||||
|
windings[i] += windings[j];
|
||||||
|
// Remove path if rule is nonzero and winding
|
||||||
|
// of path and containing path is not zero.
|
||||||
|
if (windings[i] && windings[j]) {
|
||||||
|
exclude = excluded[i] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isContained = true;
|
||||||
|
// If the containing path is not excluded, we're
|
||||||
|
// done searching for the orientation defining path.
|
||||||
|
container = !excluded[j] && children[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exclude) {
|
||||||
|
// Set to the opposite orientation of containing path,
|
||||||
|
// or the same orientation as the first path if the path
|
||||||
|
// is not contained in any other path.
|
||||||
|
path.setClockwise(container ? !container.isClockwise()
|
||||||
|
: first.isClockwise());
|
||||||
|
paths.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setChildren(paths, true); // Preserve orientation
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a point that is guaranteed to be inside the path.
|
||||||
|
*
|
||||||
|
* @bean
|
||||||
|
* @type Point
|
||||||
|
*/
|
||||||
|
getInteriorPoint: function() {
|
||||||
|
var bounds = this.getBounds(),
|
||||||
|
point = bounds.getCenter(true);
|
||||||
|
if (!this.contains(point)) {
|
||||||
|
// Since there is no guarantee that a poly-bezier path contains
|
||||||
|
// the center of its bounding rectangle, we shoot a ray in x
|
||||||
|
// direction and select a point between the first consecutive
|
||||||
|
// intersections of the ray on the left.
|
||||||
|
var curves = this.getCurves(),
|
||||||
|
y = point.y,
|
||||||
|
intercepts = [],
|
||||||
|
roots = [];
|
||||||
|
// Process all y-monotone curves that intersect the ray at y:
|
||||||
|
for (var i = 0, l = curves.length; i < l; i++) {
|
||||||
|
var v = curves[i].getValues(),
|
||||||
|
o0 = v[1],
|
||||||
|
o1 = v[3],
|
||||||
|
o2 = v[5],
|
||||||
|
o3 = v[7];
|
||||||
|
if (y >= min(o0, o1, o2, o3) && y <= max(o0, o1, o2, o3)) {
|
||||||
|
var monos = Curve.getMonoCurves(v);
|
||||||
|
for (var j = 0, m = monos.length; j < m; j++) {
|
||||||
|
var mv = monos[j],
|
||||||
|
mo0 = mv[1],
|
||||||
|
mo3 = mv[7];
|
||||||
|
// Only handle curves that are not horizontal and
|
||||||
|
// that can cross the point's ordinate.
|
||||||
|
if ((mo0 !== mo3) &&
|
||||||
|
(y >= mo0 && y <= mo3 || y >= mo3 && y <= mo0)){
|
||||||
|
var x = y === mo0 ? mv[0]
|
||||||
|
: y === mo3 ? mv[6]
|
||||||
|
: Curve.solveCubic(mv, 1, y, roots, 0, 1)
|
||||||
|
=== 1
|
||||||
|
? Curve.getPoint(mv, roots[0]).x
|
||||||
|
: (mv[0] + mv[6]) / 2;
|
||||||
|
intercepts.push(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (intercepts.length > 1) {
|
||||||
|
intercepts.sort(function(a, b) { return a - b; });
|
||||||
|
point.x = (intercepts[0] + intercepts[1]) / 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Fallback in case no non-horizontal curves were found.
|
return point;
|
||||||
if (!intercepts.length)
|
|
||||||
return point;
|
|
||||||
intercepts.sort(function(a, b) {
|
|
||||||
return a - b;
|
|
||||||
});
|
|
||||||
point.x = (intercepts[0] + intercepts[1]) / 2;
|
|
||||||
}
|
}
|
||||||
return point;
|
};
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue