Avoid matching connected start- and end points when self-intersecting curves.

Partial fix for #765.
This commit is contained in:
Jürg Lehni 2015-09-09 07:28:08 +02:00
parent 3f53aa78ce
commit 1c1e19614e
3 changed files with 71 additions and 67 deletions

View file

@ -402,8 +402,8 @@ var Curve = Base.extend(/** @lends Curve# */{
* curves * curves
*/ */
getIntersections: function(curve) { getIntersections: function(curve) {
return Curve.filterIntersections(Curve.getIntersections( return Curve._filterIntersections(Curve._getIntersections(
this.getValues(), curve.getValues(), this, curve, [])); this.getValues(), curve.getValues(), this, curve, [], {}));
}, },
// TODO: adjustThroughPoint // TODO: adjustThroughPoint
@ -1317,18 +1317,22 @@ new function() { // Scope for methods that require private functions
}, },
new function() { // Scope for intersection using bezier fat-line clipping new function() { // Scope for intersection using bezier fat-line clipping
function addLocation(locations, include, curve1, t1, point1, curve2, t2, function addLocation(locations, param, curve1, t1, point1, curve2, t2,
point2) { point2) {
var loc = new CurveLocation(curve1, t1, point1, curve2, t2, point2); var loc = null,
if (!include || include(loc)) { tMin = /*#=*/Numerical.TOLERANCE,
tMax = 1 - tMin;
if (t1 >= (param.startConnected ? tMin : 0)
&& t1 <= (param.endConnected ? tMax : 1)) {
loc = new CurveLocation(curve1, t1, point1, curve2, t2, point2);
if (param.adjust)
param.adjust(loc);
locations.push(loc); locations.push(loc);
} else {
loc = null;
} }
return loc; return loc;
} }
function addCurveIntersections(v1, v2, curve1, curve2, locations, include, function addCurveIntersections(v1, v2, curve1, curve2, locations, param,
tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion) { tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion) {
// Avoid deeper recursion. // Avoid deeper recursion.
// NOTE: @iconexperience determined that more than 20 recursions are // NOTE: @iconexperience determined that more than 20 recursions are
@ -1391,19 +1395,19 @@ new function() { // Scope for intersection using bezier fat-line clipping
var parts = Curve.subdivide(v1, 0.5), var parts = Curve.subdivide(v1, 0.5),
t = tMinNew + (tMaxNew - tMinNew) / 2; t = tMinNew + (tMaxNew - tMinNew) / 2;
addCurveIntersections( addCurveIntersections(
v2, parts[0], curve2, curve1, locations, include, v2, parts[0], curve2, curve1, locations, param,
uMin, uMax, tMinNew, t, tDiff, !reverse, recursion); uMin, uMax, tMinNew, t, tDiff, !reverse, recursion);
addCurveIntersections( addCurveIntersections(
v2, parts[1], curve2, curve1, locations, include, v2, parts[1], curve2, curve1, locations, param,
uMin, uMax, t, tMaxNew, tDiff, !reverse, recursion); uMin, uMax, t, tMaxNew, tDiff, !reverse, recursion);
} else { } else {
var parts = Curve.subdivide(v2, 0.5), var parts = Curve.subdivide(v2, 0.5),
t = uMin + (uMax - uMin) / 2; t = uMin + (uMax - uMin) / 2;
addCurveIntersections( addCurveIntersections(
parts[0], v1, curve2, curve1, locations, include, parts[0], v1, curve2, curve1, locations, param,
uMin, t, tMinNew, tMaxNew, tDiff, !reverse, recursion); uMin, t, tMinNew, tMaxNew, tDiff, !reverse, recursion);
addCurveIntersections( addCurveIntersections(
parts[1], v1, curve2, curve1, locations, include, parts[1], v1, curve2, curve1, locations, param,
t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion); t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
} }
} else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance) { } else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance) {
@ -1411,16 +1415,16 @@ new function() { // Scope for intersection using bezier fat-line clipping
var t1 = tMinNew + (tMaxNew - tMinNew) / 2, var t1 = tMinNew + (tMaxNew - tMinNew) / 2,
t2 = uMin + (uMax - uMin) / 2; t2 = uMin + (uMax - uMin) / 2;
if (reverse) { if (reverse) {
addLocation(locations, include, addLocation(locations, param,
curve2, t2, Curve.getPoint(v2, t2), curve2, t2, Curve.getPoint(v2, t2),
curve1, t1, Curve.getPoint(v1, t1)); curve1, t1, Curve.getPoint(v1, t1));
} else { } else {
addLocation(locations, include, addLocation(locations, param,
curve1, t1, Curve.getPoint(v1, t1), curve1, t1, Curve.getPoint(v1, t1),
curve2, t2, Curve.getPoint(v2, t2)); curve2, t2, Curve.getPoint(v2, t2));
} }
} else if (tDiff > /*#=*/Numerical.EPSILON) { // Iterate } else if (tDiff > /*#=*/Numerical.EPSILON) { // Iterate
addCurveIntersections(v2, v1, curve2, curve1, locations, include, addCurveIntersections(v2, v1, curve2, curve1, locations, param,
uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion); uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
} }
} }
@ -1534,7 +1538,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
* and the curve. * and the curve.
*/ */
function addCurveLineIntersections(v1, v2, curve1, curve2, locations, function addCurveLineIntersections(v1, v2, curve1, curve2, locations,
include) { param) {
var flip = Curve.isStraight(v1), var flip = Curve.isStraight(v1),
vc = flip ? v2 : v1, vc = flip ? v2 : v1,
vl = flip ? v1 : v2, vl = flip ? v1 : v2,
@ -1574,14 +1578,14 @@ new function() { // Scope for intersection using bezier fat-line clipping
var tl = Curve.getParameterOf(rvl, x, 0), var tl = Curve.getParameterOf(rvl, x, 0),
t1 = flip ? tl : tc, t1 = flip ? tl : tc,
t2 = flip ? tc : tl; t2 = flip ? tc : tl;
addLocation(locations, include, addLocation(locations, param,
curve1, t1, Curve.getPoint(v1, t1), curve1, t1, Curve.getPoint(v1, t1),
curve2, t2, Curve.getPoint(v2, t2)); curve2, t2, Curve.getPoint(v2, t2));
} }
} }
} }
function addLineIntersection(v1, v2, curve1, curve2, locations, include) { function addLineIntersection(v1, v2, curve1, curve2, locations, param) {
var point = Line.intersect( var point = Line.intersect(
v1[0], v1[1], v1[6], v1[7], v1[0], v1[1], v1[6], v1[7],
v2[0], v2[1], v2[6], v2[7]); v2[0], v2[1], v2[6], v2[7]);
@ -1590,7 +1594,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
// since they will be used for sorting // since they will be used for sorting
var x = point.x, var x = point.x,
y = point.y; y = point.y;
addLocation(locations, include, addLocation(locations, param,
curve1, Curve.getParameterOf(v1, x, y), point, curve1, Curve.getParameterOf(v1, x, y), point,
curve2, Curve.getParameterOf(v2, x, y), point); curve2, Curve.getParameterOf(v2, x, y), point);
} }
@ -1600,7 +1604,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
* Code to detect overlaps of intersecting curves by @iconexperience: * Code to detect overlaps of intersecting curves by @iconexperience:
* https://github.com/paperjs/paper.js/issues/648 * https://github.com/paperjs/paper.js/issues/648
*/ */
function addOverlap(v1, v2, curve1, curve2, locations, include) { function addOverlap(v1, v2, curve1, curve2, locations, param) {
var abs = Math.abs, var abs = Math.abs,
tolerance = /*#=*/Numerical.TOLERANCE, tolerance = /*#=*/Numerical.TOLERANCE,
epsilon = /*#=*/Numerical.EPSILON, epsilon = /*#=*/Numerical.EPSILON,
@ -1670,10 +1674,10 @@ new function() { // Scope for intersection using bezier fat-line clipping
t12 = pairs[0][1], t12 = pairs[0][1],
t21 = pairs[1][0], t21 = pairs[1][0],
t22 = pairs[1][1], t22 = pairs[1][1],
loc1 = addLocation(locations, include, loc1 = addLocation(locations, param,
curve1, t11, Curve.getPoint(v1, t11), curve1, t11, Curve.getPoint(v1, t11),
curve2, t12, Curve.getPoint(v2, t12), true), curve2, t12, Curve.getPoint(v2, t12), true),
loc2 = addLocation(locations, include, loc2 = addLocation(locations, param,
curve1, t21, Curve.getPoint(v1, t21), curve1, t21, Curve.getPoint(v1, t21),
curve2, t22, Curve.getPoint(v2, t22), true); curve2, t22, Curve.getPoint(v2, t22), true);
if (loc1) if (loc1)
@ -1690,8 +1694,9 @@ new function() { // Scope for intersection using bezier fat-line clipping
// We need to provide the original left curve reference to the // We need to provide the original left curve reference to the
// #getIntersections() calls as it is required to create the resulting // #getIntersections() calls as it is required to create the resulting
// CurveLocation objects. // CurveLocation objects.
getIntersections: function(v1, v2, c1, c2, locations, include) { _getIntersections: function(v1, v2, curve1, curve2, locations, param) {
if (addOverlap(v1, v2, c1, c2, locations, include)) if (!param.startConnected && !param.endConnected
&& addOverlap(v1, v2, curve1, curve2, locations, param))
return locations; return locations;
var straight1 = Curve.isStraight(v1), var straight1 = Curve.isStraight(v1),
straight2 = Curve.isStraight(v2), straight2 = Curve.isStraight(v2),
@ -1700,39 +1705,38 @@ new function() { // Scope for intersection using bezier fat-line clipping
c2p1 = new Point(v2[0], v2[1]), c2p1 = new Point(v2[0], v2[1]),
c2p2 = new Point(v2[6], v2[7]), c2p2 = new Point(v2[6], v2[7]),
tolerance = /*#=*/Numerical.TOLERANCE; tolerance = /*#=*/Numerical.TOLERANCE;
// Handle a special case where if both curves start or end at the // Handle the special case where the first curve's stat-point
// same point, the same end-point case will be handled after we // overlaps with the second curve's start- or end-points.
// calculate other intersections within the curve.
if (c1p1.isClose(c2p1, tolerance)) if (c1p1.isClose(c2p1, tolerance))
addLocation(locations, include, c1, 0, c1p1, c2, 0, c1p1); addLocation(locations, param, curve1, 0, c1p1, curve2, 0, c1p1);
if (c1p1.isClose(c2p2, tolerance)) if (!param.startConnected && c1p1.isClose(c2p2, tolerance))
addLocation(locations, include, c1, 0, c1p1, c2, 1, c1p1); addLocation(locations, param, curve1, 0, c1p1, curve2, 1, c1p1);
// Determine the correct intersection method based on values of // Determine the correct intersection method based on whether one or
// straight1 & 2: // curves are straight lines:
(straight1 && straight2 (straight1 && straight2
? addLineIntersection ? addLineIntersection
: straight1 || straight2 : straight1 || straight2
? addCurveLineIntersections ? addCurveLineIntersections
: addCurveIntersections)( : addCurveIntersections)(
v1, v2, c1, c2, locations, include, v1, v2, curve1, curve2, locations, param,
// Define the defaults for these parameters of // Define the defaults for these parameters of
// addCurveIntersections(): // addCurveIntersections():
// tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion // tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion
0, 1, 0, 1, 0, false, 0); 0, 1, 0, 1, 0, false, 0);
// Handle the special case where c1's end-point overlap with // Handle the special case where the first curve's end-point
// c2's points. // overlaps with the second curve's start- or end-points.
if (c1p2.isClose(c2p1, tolerance)) if (!param.endConnected && c1p2.isClose(c2p1, tolerance))
addLocation(locations, include, c1, 1, c1p2, c2, 0, c1p2); addLocation(locations, param, curve1, 1, c1p2, curve2, 0, c1p2);
if (c1p2.isClose(c2p2, tolerance)) if (c1p2.isClose(c2p2, tolerance))
addLocation(locations, include, c1, 1, c1p2, c2, 1, c1p2); addLocation(locations, param, curve1, 1, c1p2, curve2, 1, c1p2);
return locations; return locations;
}, },
filterIntersections: function(locations, expand) { _filterIntersections: function(locations, expand) {
var last = locations.length - 1, var last = locations.length - 1,
tMax = 1 - /*#=*/Numerical.TOLERANCE; tMax = 1 - /*#=*/Numerical.TOLERANCE;
// Merge intersections very close to the end of a curve to the // Merge intersections very close to the end of a curve to the
// beginning of the next curve. // beginning of the next curve, so we can compare them.
for (var i = last; i >= 0; i--) { for (var i = last; i >= 0; i--) {
var loc = locations[i], var loc = locations[i],
next; next;

View file

@ -76,7 +76,7 @@ PathItem.inject(new function() {
_path2.reverse(); _path2.reverse();
// Split curves at intersections on both paths. Note that for self // Split curves at intersections on both paths. Note that for self
// intersection, _path2 will be null and getIntersections() handles it. // intersection, _path2 will be null and getIntersections() handles it.
splitPath(Curve.filterIntersections( splitPath(Curve._filterIntersections(
_path1._getIntersections(_path2, null, []), true)); _path1._getIntersections(_path2, null, []), true));
/* /*
console.time('inter'); console.time('inter');
@ -88,7 +88,7 @@ PathItem.inject(new function() {
_path2._getIntersections(null, null, locations); _path2._getIntersections(null, null, locations);
console.timeEnd('self'); console.timeEnd('self');
} }
splitPath(Curve.filterIntersections(locations, true)); splitPath(Curve._filterIntersections(locations, true));
*/ */
var chain = [], var chain = [],
segments = [], segments = [],

View file

@ -63,31 +63,30 @@ var PathItem = Item.extend(/** @lends PathItem# */{
// intersections. // intersections.
// NOTE: The hidden argument _matrix is used internally to override the // NOTE: The hidden argument _matrix is used internally to override the
// passed path's transformation matrix. // passed path's transformation matrix.
return Curve.filterIntersections(this._getIntersections( return Curve._filterIntersections(this._getIntersections(
this !== path ? path : null, _matrix, [])); this !== path ? path : null, _matrix, []));
}, },
_getIntersections: function(path, matrix, locations, returnFirst) { _getIntersections: function(path, matrix, locations, returnFirst) {
var curves1 = this.getCurves(), var self = !path, // self-intersections?
curves2 = path ? path.getCurves() : curves1, curves1 = this.getCurves(),
curves2 = self ? curves1 : path.getCurves(),
matrix1 = this._matrix.orNullIfIdentity(), matrix1 = this._matrix.orNullIfIdentity(),
matrix2 = path ? (matrix || path._matrix).orNullIfIdentity() matrix2 = self ? matrix1
: matrix1, : (matrix || path._matrix).orNullIfIdentity(),
length1 = curves1.length, length1 = curves1.length,
length2 = path ? curves2.length : length1, length2 = path ? curves2.length : length1,
values2 = [], values2 = [];
tMin = /*#=*/Numerical.TOLERANCE,
tMax = 1 - tMin;
// First check the bounds of the two paths. If they don't intersect, // First check the bounds of the two paths. If they don't intersect,
// we don't need to iterate through their curves. // we don't need to iterate through their curves.
if (path && !this.getBounds(matrix1).touches(path.getBounds(matrix2))) if (path && !this.getBounds(matrix1).touches(path.getBounds(matrix2)))
return []; return locations;
for (var i = 0; i < length2; i++) for (var i = 0; i < length2; i++)
values2[i] = curves2[i].getValues(matrix2); values2[i] = curves2[i].getValues(matrix2);
for (var i = 0; i < length1; i++) { for (var i = 0; i < length1; i++) {
var curve1 = curves1[i], var curve1 = curves1[i],
values1 = path ? curve1.getValues(matrix1) : values2[i]; values1 = self ? values2[i] : curve1.getValues(matrix1);
if (!path) { if (self) {
// First check for self-intersections within the same curve // First check for self-intersections within the same curve
var seg1 = curve1.getSegment1(), var seg1 = curve1.getSegment1(),
seg2 = curve1.getSegment2(), seg2 = curve1.getSegment2(),
@ -103,15 +102,17 @@ var PathItem = Item.extend(/** @lends PathItem# */{
// Self intersecting is found by dividing the curve in two // Self intersecting is found by dividing the curve in two
// and and then applying the normal curve intersection code. // and and then applying the normal curve intersection code.
var parts = Curve.subdivide(values1); var parts = Curve.subdivide(values1);
Curve.getIntersections( Curve._getIntersections(
parts[0], parts[1], curve1, curve1, locations, parts[0], parts[1], curve1, curve1, locations, {
function(loc) { // Only possible if there is only one curve:
if (loc._parameter <= tMax) { startConnected: length1 === 1,
// After splitting, the end is always connected:
endConnected: true,
adjust: function(loc) {
// Since the curve was split above, we need to // Since the curve was split above, we need to
// adjust the parameters for both locations. // adjust the parameters for both locations.
loc._parameter /= 2; loc._parameter /= 2;
loc._parameter2 = 0.5 + loc._parameter2 / 2; loc._parameter2 = 0.5 + loc._parameter2 / 2;
return true;
} }
} }
); );
@ -119,20 +120,19 @@ var PathItem = Item.extend(/** @lends PathItem# */{
} }
// Check for intersections with other curves. For self intersection, // Check for intersections with other curves. For self intersection,
// we can start at i + 1 instead of 0 // we can start at i + 1 instead of 0
for (var j = path ? 0 : i + 1; j < length2; j++) { for (var j = self ? i + 1 : 0; j < length2; j++) {
// There might be already one location from the above // There might be already one location from the above
// self-intersection check: // self-intersection check:
if (returnFirst && locations.length) if (returnFirst && locations.length)
break; break;
Curve.getIntersections( // Avoid end point intersections on consecutive curves when
// self intersecting.
Curve._getIntersections(
values1, values2[j], curve1, curves2[j], locations, values1, values2[j], curve1, curves2[j], locations,
// Avoid end point intersections on consecutive curves when self ? {
// self intersecting. startConnected: j === length2 - 1 && i === 0,
!path && (j === i + 1 || j === length2 - 1 && i === 0) endConnected: j === i + 1
&& function(loc) { } : {}
var t = loc._parameter;
return t >= tMin && t <= tMax;
}
); );
} }
} }