Split #resolveCrossings() into #resolveCrossings() and #reorient()

Closes #973
This commit is contained in:
Jürg Lehni 2016-07-18 14:04:51 +02:00
parent 7acb5bee45
commit 3f058e6471

View file

@ -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; };
}
}); });