mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
Implement Path#compare() to compare for geometric equality of shapes.
Use it in boolean operations when handling fully overlapping paths. Relates to #1109
This commit is contained in:
parent
baf58fb021
commit
ccac7ec7c5
2 changed files with 101 additions and 7 deletions
|
@ -1548,6 +1548,96 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
|
|
||||||
toPath: '#clone',
|
toPath: '#clone',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares the geometry of two paths to see if they describe the same
|
||||||
|
* shape, detecting cases where paths start in different segments or even
|
||||||
|
* use different amounts of curves to describe the same shape, as long as
|
||||||
|
* their orientation is the same, and their segments and handles really
|
||||||
|
* result in the same visual appearance of curves.
|
||||||
|
*
|
||||||
|
* @param {Path} path the path to compare this path's geometry with
|
||||||
|
* @return {Boolean} {@true if two paths describe the shame shape}
|
||||||
|
*/
|
||||||
|
compare: function(path) {
|
||||||
|
var curves1 = this.getCurves(),
|
||||||
|
curves2 = path.getCurves(),
|
||||||
|
length1 = curves1.length,
|
||||||
|
length2 = curves2.length;
|
||||||
|
if (!length1 || !length2) {
|
||||||
|
// If one path defines curves and the other doesn't, we can't have
|
||||||
|
// matching geometries.
|
||||||
|
return length1 ^ length2;
|
||||||
|
}
|
||||||
|
var v1 = curves1[0].getValues(),
|
||||||
|
values2 = [],
|
||||||
|
pos1 = 0, pos2,
|
||||||
|
end1 = 0, end2;
|
||||||
|
// First, loop through curves2, looking for the start of the overlapping
|
||||||
|
// sequence with curves1[0]. Also cache curve values for later reuse.
|
||||||
|
for (var i2 = 0; i2 < length2; i2++) {
|
||||||
|
var v2 = curves2[i2].getValues();
|
||||||
|
values2.push(v2);
|
||||||
|
var overlaps = Curve.getOverlaps(v1, v2);
|
||||||
|
if (overlaps) {
|
||||||
|
// If the overlap doesn't start at the beginning of v2, then
|
||||||
|
// it can only be the a partial overlap with curves2[0], and
|
||||||
|
// the start is at curves2[-1]:
|
||||||
|
pos2 = !i2 && overlaps[0][0] > 0 ? length2 - 1 : i2;
|
||||||
|
// Set end2 to the start of the first overlap on curves2, so
|
||||||
|
// connection checks further down can work.
|
||||||
|
end2 = overlaps[0][1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now loop through both curve arrays, find their overlaps, verify that
|
||||||
|
// they keep joining, and see if we end back at the start on both paths.
|
||||||
|
var abs = Math.abs,
|
||||||
|
epsilon = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||||
|
v2 = values2[pos2],
|
||||||
|
start2;
|
||||||
|
while (v1 && v2) {
|
||||||
|
var overlaps = Curve.getOverlaps(v1, v2);
|
||||||
|
if (overlaps) {
|
||||||
|
// Check that the overlaps are joining on curves1
|
||||||
|
var t1 = overlaps[0][0];
|
||||||
|
if (abs(t1 - end1) < epsilon) {
|
||||||
|
end1 = overlaps[1][0];
|
||||||
|
if (end1 === 1) {
|
||||||
|
// Skip to the next curve if we're at the end of the
|
||||||
|
// current, and set v1 to null if at the end of curves1.
|
||||||
|
v1 = ++pos1 < length1 ? curves1[pos1].getValues() : null;
|
||||||
|
end1 = 0;
|
||||||
|
}
|
||||||
|
// Check that the overlaps are joining on curves2
|
||||||
|
var t2 = overlaps[0][1];
|
||||||
|
if (abs(t2 - end2) < epsilon) {
|
||||||
|
if (!start2)
|
||||||
|
start2 = [pos2, t2];
|
||||||
|
end2 = overlaps[1][1];
|
||||||
|
if (end2 === 1) {
|
||||||
|
// Wrap pos2 around the end on values2:
|
||||||
|
if (++pos2 >= length2)
|
||||||
|
pos2 = 0;
|
||||||
|
// Reuse cached values from initial search.
|
||||||
|
v2 = values2[pos2] || curves2[pos2].getValues();
|
||||||
|
end2 = 0;
|
||||||
|
}
|
||||||
|
if (!v1) {
|
||||||
|
// We're done with curves1. If we're not back at the
|
||||||
|
// start on curve2, the two paths are not identical.
|
||||||
|
return start2[0] === pos2 && start2[1] === end2;
|
||||||
|
}
|
||||||
|
// All good, continue to avoid the break; further down
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No overlap match found, break out early.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
_hitTestSelf: function(point, options, viewMatrix, strokeMatrix) {
|
_hitTestSelf: function(point, options, viewMatrix, strokeMatrix) {
|
||||||
var that = this,
|
var that = this,
|
||||||
style = this.getStyle(),
|
style = this.getStyle(),
|
||||||
|
|
|
@ -646,6 +646,13 @@ PathItem.inject(new function() {
|
||||||
return seg === start || seg === otherStart;
|
return seg === start || seg === otherStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function visitPath(path) {
|
||||||
|
var segments = path._segments;
|
||||||
|
for (var i = 0, l = segments.length; i < l; i++) {
|
||||||
|
segments[i]._visited = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If there are multiple possible intersections, find the one that's
|
// If there are multiple possible intersections, find the one that's
|
||||||
// either connecting back to start or is not visited yet, and will be
|
// either connecting back to start or is not visited yet, and will be
|
||||||
// part of the boolean result:
|
// part of the boolean result:
|
||||||
|
@ -711,19 +718,16 @@ PathItem.inject(new function() {
|
||||||
if (!seg._visited && seg._path._overlapsOnly) {
|
if (!seg._visited && seg._path._overlapsOnly) {
|
||||||
// TODO: Don't we also need to check for multiple overlaps?
|
// TODO: Don't we also need to check for multiple overlaps?
|
||||||
var path1 = seg._path,
|
var path1 = seg._path,
|
||||||
path2 = inter._segment._path,
|
path2 = inter._segment._path;
|
||||||
segments1 = path1._segments,
|
if (path1.compare(path2)) {
|
||||||
segments2 = path2._segments;
|
|
||||||
if (Base.equals(segments1, segments2)) {
|
|
||||||
// Only add the path to the result if it has an area.
|
// Only add the path to the result if it has an area.
|
||||||
if ((operator.unite || operator.intersect)
|
if ((operator.unite || operator.intersect)
|
||||||
&& path1.getArea()) {
|
&& path1.getArea()) {
|
||||||
paths.push(path1.clone(false));
|
paths.push(path1.clone(false));
|
||||||
}
|
}
|
||||||
// Now mark all involved segments as visited.
|
// Now mark all involved segments as visited.
|
||||||
for (var j = 0, k = segments1.length; j < k; j++) {
|
visitPath(path1);
|
||||||
segments1[j]._visited = segments2[j]._visited = true;
|
visitPath(path2);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Exclude three cases of invalid starting segments:
|
// Exclude three cases of invalid starting segments:
|
||||||
|
|
Loading…
Reference in a new issue