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
*/
getIntersections: function(curve) {
return Curve.filterIntersections(Curve.getIntersections(
this.getValues(), curve.getValues(), this, curve, []));
return Curve._filterIntersections(Curve._getIntersections(
this.getValues(), curve.getValues(), this, curve, [], {}));
},
// 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
function addLocation(locations, include, curve1, t1, point1, curve2, t2,
function addLocation(locations, param, curve1, t1, point1, curve2, t2,
point2) {
var loc = new CurveLocation(curve1, t1, point1, curve2, t2, point2);
if (!include || include(loc)) {
var loc = null,
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);
} else {
loc = null;
}
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) {
// Avoid deeper recursion.
// 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),
t = tMinNew + (tMaxNew - tMinNew) / 2;
addCurveIntersections(
v2, parts[0], curve2, curve1, locations, include,
v2, parts[0], curve2, curve1, locations, param,
uMin, uMax, tMinNew, t, tDiff, !reverse, recursion);
addCurveIntersections(
v2, parts[1], curve2, curve1, locations, include,
v2, parts[1], curve2, curve1, locations, param,
uMin, uMax, t, tMaxNew, tDiff, !reverse, recursion);
} else {
var parts = Curve.subdivide(v2, 0.5),
t = uMin + (uMax - uMin) / 2;
addCurveIntersections(
parts[0], v1, curve2, curve1, locations, include,
parts[0], v1, curve2, curve1, locations, param,
uMin, t, tMinNew, tMaxNew, tDiff, !reverse, recursion);
addCurveIntersections(
parts[1], v1, curve2, curve1, locations, include,
parts[1], v1, curve2, curve1, locations, param,
t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion);
}
} 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,
t2 = uMin + (uMax - uMin) / 2;
if (reverse) {
addLocation(locations, include,
addLocation(locations, param,
curve2, t2, Curve.getPoint(v2, t2),
curve1, t1, Curve.getPoint(v1, t1));
} else {
addLocation(locations, include,
addLocation(locations, param,
curve1, t1, Curve.getPoint(v1, t1),
curve2, t2, Curve.getPoint(v2, t2));
}
} 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);
}
}
@ -1534,7 +1538,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
* and the curve.
*/
function addCurveLineIntersections(v1, v2, curve1, curve2, locations,
include) {
param) {
var flip = Curve.isStraight(v1),
vc = flip ? v2 : v1,
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),
t1 = flip ? tl : tc,
t2 = flip ? tc : tl;
addLocation(locations, include,
addLocation(locations, param,
curve1, t1, Curve.getPoint(v1, t1),
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(
v1[0], v1[1], v1[6], v1[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
var x = point.x,
y = point.y;
addLocation(locations, include,
addLocation(locations, param,
curve1, Curve.getParameterOf(v1, 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:
* 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,
tolerance = /*#=*/Numerical.TOLERANCE,
epsilon = /*#=*/Numerical.EPSILON,
@ -1670,10 +1674,10 @@ new function() { // Scope for intersection using bezier fat-line clipping
t12 = pairs[0][1],
t21 = pairs[1][0],
t22 = pairs[1][1],
loc1 = addLocation(locations, include,
loc1 = addLocation(locations, param,
curve1, t11, Curve.getPoint(v1, t11),
curve2, t12, Curve.getPoint(v2, t12), true),
loc2 = addLocation(locations, include,
loc2 = addLocation(locations, param,
curve1, t21, Curve.getPoint(v1, t21),
curve2, t22, Curve.getPoint(v2, t22), true);
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
// #getIntersections() calls as it is required to create the resulting
// CurveLocation objects.
getIntersections: function(v1, v2, c1, c2, locations, include) {
if (addOverlap(v1, v2, c1, c2, locations, include))
_getIntersections: function(v1, v2, curve1, curve2, locations, param) {
if (!param.startConnected && !param.endConnected
&& addOverlap(v1, v2, curve1, curve2, locations, param))
return locations;
var straight1 = Curve.isStraight(v1),
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]),
c2p2 = new Point(v2[6], v2[7]),
tolerance = /*#=*/Numerical.TOLERANCE;
// Handle a special case where if both curves start or end at the
// same point, the same end-point case will be handled after we
// calculate other intersections within the curve.
// Handle the special case where the first curve's stat-point
// overlaps with the second curve's start- or end-points.
if (c1p1.isClose(c2p1, tolerance))
addLocation(locations, include, c1, 0, c1p1, c2, 0, c1p1);
if (c1p1.isClose(c2p2, tolerance))
addLocation(locations, include, c1, 0, c1p1, c2, 1, c1p1);
// Determine the correct intersection method based on values of
// straight1 & 2:
addLocation(locations, param, curve1, 0, c1p1, curve2, 0, c1p1);
if (!param.startConnected && c1p1.isClose(c2p2, tolerance))
addLocation(locations, param, curve1, 0, c1p1, curve2, 1, c1p1);
// Determine the correct intersection method based on whether one or
// curves are straight lines:
(straight1 && straight2
? addLineIntersection
: straight1 || straight2
? addCurveLineIntersections
: addCurveIntersections)(
v1, v2, c1, c2, locations, include,
v1, v2, curve1, curve2, locations, param,
// Define the defaults for these parameters of
// addCurveIntersections():
// tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion
0, 1, 0, 1, 0, false, 0);
// Handle the special case where c1's end-point overlap with
// c2's points.
if (c1p2.isClose(c2p1, tolerance))
addLocation(locations, include, c1, 1, c1p2, c2, 0, c1p2);
// Handle the special case where the first curve's end-point
// overlaps with the second curve's start- or end-points.
if (!param.endConnected && c1p2.isClose(c2p1, tolerance))
addLocation(locations, param, curve1, 1, c1p2, curve2, 0, c1p2);
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;
},
filterIntersections: function(locations, expand) {
_filterIntersections: function(locations, expand) {
var last = locations.length - 1,
tMax = 1 - /*#=*/Numerical.TOLERANCE;
// 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--) {
var loc = locations[i],
next;

View file

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

View file

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