mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-19 14:10:14 -05:00
parent
374107c439
commit
8a0f0ad448
3 changed files with 20 additions and 162 deletions
|
@ -49,16 +49,11 @@ PathItem.inject(new function() {
|
|||
/*
|
||||
* Creates a clone of the path that we can modify freely, with its matrix
|
||||
* applied to its geometry. Calls #reduce() to simplify compound paths and
|
||||
* remove empty curves, #resolveCrossings() to resolve self-intersection
|
||||
* make sure all paths have correct winding direction.
|
||||
* remove empty curves.
|
||||
*/
|
||||
function preparePath(path, resolve) {
|
||||
var res = path.clone(false).reduce({ simplify: true })
|
||||
return path.clone(false).reduce({ simplify: true })
|
||||
.transform(null, true, true);
|
||||
return resolve
|
||||
? res.resolveCrossings().reorient(
|
||||
res.getFillRule() === 'nonzero', true)
|
||||
: res;
|
||||
}
|
||||
|
||||
function createResult(ctor, paths, reduce, path1, path2, options) {
|
||||
|
@ -100,10 +95,13 @@ PathItem.inject(new function() {
|
|||
if (_path2 && (operator.subtract || operator.exclude)
|
||||
^ (_path2.isClockwise() ^ _path1.isClockwise()))
|
||||
_path2.reverse();
|
||||
// Split curves at crossings on both paths. Note that for self-
|
||||
// intersection, path2 is null and getIntersections() handles it.
|
||||
var crossings = divideLocations(
|
||||
CurveLocation.expand(_path1.getCrossings(_path2))),
|
||||
// Split curves in intersections and self-intersections on both paths.
|
||||
var intersections = divideLocations(CurveLocation.expand(
|
||||
Curve.getIntersections(
|
||||
// Note that for self-intersection, path2 is null.
|
||||
_path1.getCurves().concat(_path2 ? _path2.getCurves() : [])
|
||||
)
|
||||
)),
|
||||
paths1 = _path1._children || [_path1],
|
||||
paths2 = _path2 && (_path2._children || [_path2]),
|
||||
segments = [],
|
||||
|
@ -121,18 +119,18 @@ PathItem.inject(new function() {
|
|||
}
|
||||
}
|
||||
|
||||
if (crossings.length) {
|
||||
if (intersections.length) {
|
||||
// Collect all segments and curves of both involved operands.
|
||||
collect(paths1);
|
||||
if (paths2)
|
||||
collect(paths2);
|
||||
// Propagate the winding contribution. Winding contribution of
|
||||
// curves does not change between two crossings.
|
||||
// curves does not change between two intersections.
|
||||
// First, propagate winding contributions for curve chains starting
|
||||
// in all crossings:
|
||||
for (var i = 0, l = crossings.length; i < l; i++) {
|
||||
propagateWinding(crossings[i]._segment, _path1, _path2, curves,
|
||||
operator);
|
||||
// in all intersections:
|
||||
for (var i = 0, l = intersections.length; i < l; i++) {
|
||||
propagateWinding(intersections[i]._segment, _path1, _path2,
|
||||
curves, operator);
|
||||
}
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
var segment = segments[i],
|
||||
|
@ -146,8 +144,8 @@ PathItem.inject(new function() {
|
|||
}
|
||||
paths = tracePaths(segments, operator);
|
||||
} else {
|
||||
// When there are no crossings, the result can be determined through
|
||||
// a much faster call to reorientPaths():
|
||||
// When there are no intersections, the result can be determined
|
||||
// through a much faster call to reorientPaths():
|
||||
paths = reorientPaths(
|
||||
// Make sure reorientPaths() never works on original
|
||||
// _children arrays by calling paths1.slice()
|
||||
|
@ -358,7 +356,7 @@ PathItem.inject(new function() {
|
|||
var loc = locations[i],
|
||||
// Retrieve curve-time before calling include(), because it may
|
||||
// be changed to the scaled value after splitting previously.
|
||||
// See CurveLocation#getCurve(), #resolveCrossings()
|
||||
// See CurveLocation#getCurve()
|
||||
time = loc._time,
|
||||
origTime = time,
|
||||
exclude = include && !include(loc),
|
||||
|
@ -1101,120 +1099,6 @@ PathItem.inject(new function() {
|
|||
], true, this, path, options);
|
||||
},
|
||||
|
||||
/*
|
||||
* Resolves all crossings of a path item by splitting the path or
|
||||
* compound-path in each self-intersection and tracing the result.
|
||||
* If possible, the existing path / compound-path is modified if the
|
||||
* amount of resulting paths allows so, otherwise a new path /
|
||||
* compound-path is created, replacing the current one.
|
||||
*
|
||||
* @return {PahtItem} the resulting path item
|
||||
*/
|
||||
resolveCrossings: function() {
|
||||
var children = this._children,
|
||||
// Support both path and compound-path items
|
||||
paths = children || [this];
|
||||
|
||||
function hasOverlap(seg) {
|
||||
var inter = seg && seg._intersection;
|
||||
return inter && inter._overlap;
|
||||
}
|
||||
|
||||
// First collect all overlaps and crossings while taking not of the
|
||||
// existence of both.
|
||||
var hasOverlaps = false,
|
||||
hasCrossings = false,
|
||||
intersections = this.getIntersections(null, function(inter) {
|
||||
return inter.hasOverlap() && (hasOverlaps = true) ||
|
||||
inter.isCrossing() && (hasCrossings = true);
|
||||
}),
|
||||
// We only need to keep track of curves that need clearing
|
||||
// outside of divideLocations() if two calls are necessary.
|
||||
clearCurves = hasOverlaps && hasCrossings && [];
|
||||
intersections = CurveLocation.expand(intersections);
|
||||
if (hasOverlaps) {
|
||||
// First divide in all overlaps, and then remove the inside of
|
||||
// the resulting overlap ranges.
|
||||
var overlaps = divideLocations(intersections, function(inter) {
|
||||
return inter.hasOverlap();
|
||||
}, clearCurves);
|
||||
for (var i = overlaps.length - 1; i >= 0; i--) {
|
||||
var seg = overlaps[i]._segment,
|
||||
prev = seg.getPrevious(),
|
||||
next = seg.getNext();
|
||||
if (hasOverlap(prev) && hasOverlap(next)) {
|
||||
seg.remove();
|
||||
prev._handleOut._set(0, 0);
|
||||
next._handleIn._set(0, 0);
|
||||
// If the curve that is left has no length, remove it
|
||||
// altogether. Check for paths with only one segment
|
||||
// before removal, since `prev.getCurve() == null`.
|
||||
if (prev !== seg && !prev.getCurve().hasLength()) {
|
||||
// Transfer handleIn when removing segment:
|
||||
next._handleIn.set(prev._handleIn);
|
||||
prev.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasCrossings) {
|
||||
// Divide any remaining intersections that are still part of
|
||||
// valid paths after the removal of overlaps.
|
||||
divideLocations(intersections, hasOverlaps && function(inter) {
|
||||
// Check both involved curves to see if they're still valid,
|
||||
// meaning they are still part of their paths.
|
||||
var curve1 = inter.getCurve(),
|
||||
seg1 = inter.getSegment(),
|
||||
// Do not call getCurve() and getSegment() on the other
|
||||
// intersection yet, as it too is in the intersections
|
||||
// array and will be divided later. But check if its
|
||||
// current curve is valid, as required by some rare edge
|
||||
// cases, related to intersections on the same curve.
|
||||
other = inter._intersection,
|
||||
curve2 = other._curve,
|
||||
seg2 = other._segment;
|
||||
if (curve1 && curve2 && curve1._path && curve2._path)
|
||||
return true;
|
||||
// Remove all intersections that were involved in the
|
||||
// handling of overlaps, to not confuse tracePaths().
|
||||
if (seg1)
|
||||
seg1._intersection = null;
|
||||
if (seg2)
|
||||
seg2._intersection = null;
|
||||
}, clearCurves);
|
||||
if (clearCurves)
|
||||
clearCurveHandles(clearCurves);
|
||||
// Finally resolve self-intersections through tracePaths()
|
||||
paths = tracePaths(Base.each(paths, function(path) {
|
||||
this.push.apply(this, path._segments);
|
||||
}, []));
|
||||
}
|
||||
// Determine how to return the paths: First try to recycle the
|
||||
// current path / compound-path, if the amount of paths does not
|
||||
// require a conversion.
|
||||
var length = paths.length,
|
||||
item;
|
||||
if (length > 1 && children) {
|
||||
if (paths !== children)
|
||||
this.setChildren(paths);
|
||||
item = this;
|
||||
} else if (length === 1 && !children) {
|
||||
if (paths[0] !== this)
|
||||
this.setSegments(paths[0].removeSegments());
|
||||
item = this;
|
||||
}
|
||||
// Otherwise create a new compound-path and see if we can reduce it,
|
||||
// and attempt to replace this item with it.
|
||||
if (!item) {
|
||||
item = new CompoundPath(Item.NO_INSERT);
|
||||
item.addChildren(paths);
|
||||
item = item.reduce();
|
||||
item.copyAttributes(this);
|
||||
this.replaceWith(item);
|
||||
}
|
||||
return item;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fixes the orientation of the sub-paths of a compound-path, assuming
|
||||
* that non of its sub-paths intersect, by reorienting them so that they
|
||||
|
|
|
@ -351,9 +351,6 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
return this.getIntersections(path, function(inter) {
|
||||
// TODO: Only return overlaps that are actually crossings! For this
|
||||
// we need proper overlap range detection / merging first...
|
||||
// But as we call #resolveCrossings() first in boolean operations,
|
||||
// removing all self-touching areas in paths, this currently works
|
||||
// as it should in the known use cases.
|
||||
// The ideal implementation would deal with it in a way outlined in:
|
||||
// https://github.com/paperjs/paper.js/issues/874#issuecomment-168332391
|
||||
return inter.hasOverlap() || inter.isCrossing();
|
||||
|
|
|
@ -67,29 +67,6 @@ test('frame.intersect(rect)', function() {
|
|||
'M140,50l10,0l0,150l-10,0z');
|
||||
});
|
||||
|
||||
test('PathItem#resolveCrossings()', function() {
|
||||
var paths = [
|
||||
'M100,300l0,-50l50,-50l-50,0l150,0l-150,0l50,0l-50,0l100,0l-100,0l0,-100l200,0l0,200z',
|
||||
'M50,300l0,-150l50,25l0,-75l200,0l0,200z M100,200l50,0l-50,-25z',
|
||||
'M330.1,388.5l-65,65c0,0 -49.1,-14.5 -36.6,-36.6c12.5,-22.1 92.4,25.1 92.4,25.1c0,0 -33.3,-73.3 -23.2,-85.9c10,-12.8 32.4,32.4 32.4,32.4z',
|
||||
'M570,290l5.8176000300452415,33.58556812220928l-28.17314339506561,-14.439003967264455l31.189735425395614,-4.568209255479985c-5.7225406635552645e-9,-3.907138079739525e-8 -59.366611385062015,8.695139599513823 -59.366611385062015,8.695139599513823z',
|
||||
'M228.26666666666668,222.72h55.46666666666667c3.05499999999995,0 5.546666666666624,2.4916666666666742 5.546666666666624,5.546666666666681v55.46666666666667c0,3.05499999999995 -2.4916666666666742,5.546666666666624 -5.546666666666624,5.546666666666624h-55.46666666666667c-3.055000000000007,0 -5.546666666666681,-2.4916666666666742 -5.546666666666681,-5.546666666666624v-55.46666666666667c0,-3.055000000000007 2.4916666666666742,-5.546666666666681 5.546666666666681,-5.546666666666681zM283.73333399705655,289.2799999999998c-2.212411231994338e-7,1.1368683772161603e-13 2.212409526691772e-7,0 0,0z'
|
||||
];
|
||||
var results = [
|
||||
'M100,300l0,-50l50,-50l-50,0l0,-100l200,0l0,200z',
|
||||
'M50,300l0,-150l50,25l0,-75l200,0l0,200z M100,200l50,0l-50,-25z',
|
||||
'M291.85631,426.74369l-26.75631,26.75631c0,0 -49.1,-14.5 -36.6,-36.6c7.48773,-13.23831 39.16013,-1.61018 63.35631,9.84369z M330.1,388.5l-22.09831,22.09831c-8.06306,-21.54667 -15.93643,-47.46883 -10.30169,-54.49831c10,-12.8 32.4,32.4 32.4,32.4z M320.9,442c0,0 -12.84682,-7.58911 -29.04369,-15.25631l16.14539,-16.14539c6.38959,17.07471 12.89831,31.40169 12.89831,31.40169z',
|
||||
'M570,290l5.8176,33.58557l-28.17314,-14.439c-14.32289,2.0978 -28.17688,4.12693 -28.17688,4.12693z',
|
||||
'M228.26666666666668,222.72h55.46666666666667c3.05499999999995,0 5.546666666666624,2.4916666666666742 5.546666666666624,5.546666666666681v55.46666666666667c0,3.05499999999995 -2.4916666666666742,5.546666666666624 -5.546666666666624,5.546666666666624h-55.46666666666667c-3.055000000000007,0 -5.546666666666681,-2.4916666666666742 -5.546666666666681,-5.546666666666624v-55.46666666666667c0,-3.055000000000007 2.4916666666666742,-5.546666666666681 5.546666666666681,-5.546666666666681z'
|
||||
];
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var path = PathItem.create(paths[i]),
|
||||
result = PathItem.create(results[i]);
|
||||
path.fillRule = 'evenodd';
|
||||
compareBoolean(path.resolveCrossings(), result, 'path.resolveCrossings(); // Test ' + (i + 1));
|
||||
}
|
||||
});
|
||||
|
||||
test('#541', function() {
|
||||
var shape0 = new Path.Rectangle({
|
||||
insert: false,
|
||||
|
@ -610,12 +587,12 @@ test('#973', function() {
|
|||
path.segments[1].point.y += 60;
|
||||
path.segments[3].point.y -= 60;
|
||||
|
||||
var resolved = path.resolveCrossings();
|
||||
var resolved = path.unite();
|
||||
var orientation = resolved.children.map(function(child) {
|
||||
return child.isClockwise();
|
||||
});
|
||||
equals(orientation, [true, false, true],
|
||||
'children orientation after calling path.resolveCrossings()');
|
||||
'children orientation after calling path.unite()');
|
||||
});
|
||||
|
||||
test('#1036', function() {
|
||||
|
|
Loading…
Reference in a new issue