mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-05 20:32:00 -05:00
Avoid matching connected start- and end points when self-intersecting curves.
Partial fix for #765.
This commit is contained in:
parent
3f53aa78ce
commit
1c1e19614e
3 changed files with 71 additions and 67 deletions
|
@ -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;
|
||||||
|
|
|
@ -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 = [],
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue