Expose Curve.getCurvesIntersections()

To be used in offsetting code to check for self-intersections.
This commit is contained in:
Jürg Lehni 2017-02-06 16:37:05 +01:00
parent fafbd9ad36
commit 4588a90952
2 changed files with 245 additions and 234 deletions

View file

@ -445,7 +445,7 @@ var Curve = Base.extend(/** @lends Curve# */{
* curves
*/
getIntersections: function(curve) {
return Curve._getIntersections(this.getValues(),
return Curve.getCurveIntersections(this.getValues(),
curve && curve !== this ? curve.getValues() : null,
this, curve, [], {});
},
@ -2022,11 +2022,10 @@ new function() { // Scope for intersection using bezier fat-line clipping
}
}
return { statics: /** @lends Curve */{
_getIntersections: function(v1, v2, c1, c2, locations, param) {
function getCurveIntersections(v1, v2, c1, c2, locations, param) {
if (!v2) {
// If v2 is not provided, search for a self-intersection on v1.
return Curve._getLoopIntersection(v1, c1, locations, param);
return getLoopIntersection(v1, c1, locations, param);
}
// Avoid checking curves if completely out of control bounds.
var epsilon = /*#=*/Numerical.EPSILON,
@ -2050,7 +2049,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
max(c2y0, c2y1, c2y2, c2y3)))
return locations;
// Now detect and handle overlaps:
var overlaps = Curve.getOverlaps(v1, v2);
var overlaps = getOverlaps(v1, v2);
if (overlaps) {
for (var i = 0; i < 2; i++) {
var overlap = overlaps[i];
@ -2081,8 +2080,8 @@ new function() { // Scope for intersection using bezier fat-line clipping
// #805#issuecomment-148503018
if (straight && locations.length > before)
return locations;
// Handle the special case where the first curve's start- or end-
// point overlaps with the second curve's start or end-point.
// Handle the special case where the first curve's start- or end-point
// overlaps with the second curve's start or end-point.
var c1p0 = new Point(c1x0, c1y0),
c1p3 = new Point(c1x3, c1y3),
c2p0 = new Point(c2x0, c2y0),
@ -2096,9 +2095,9 @@ new function() { // Scope for intersection using bezier fat-line clipping
if (c1p3.isClose(c2p3, epsilon))
addLocation(locations, param, v1, c1, 1, c1p3, v2, c2, 1, c2p3);
return locations;
},
}
_getLoopIntersection: function(v1, c1, locations, param) {
function getLoopIntersection(v1, c1, locations, param) {
var info = Curve.classify(v1);
if (info.type === 'loop') {
var roots = info.roots;
@ -2107,13 +2106,80 @@ new function() { // Scope for intersection using bezier fat-line clipping
v1, c1, roots[1], null);
}
return locations;
},
}
function getCurvesIntersections(curves1, curves2, include, matrix1, matrix2,
_returnFirst) {
var self = !curves2;
if (self)
curves2 = curves1;
var length1 = curves1.length,
length2 = curves2.length,
values2 = [],
arrays = [],
locations,
current;
// Cache values for curves2 as we re-iterate them for each in curves1.
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 = self ? values2[i] : curve1.getValues(matrix1),
path1 = curve1.getPath();
// NOTE: Due to the nature of getCurveIntersections(), we use
// separate location arrays per path1, to make sure the circularity
// checks are not getting confused by locations on separate paths.
// The separate arrays are then flattened in the end.
if (path1 !== current) {
current = path1;
locations = [];
arrays.push(locations);
}
if (self) {
// First check for self-intersections within the same curve.
getLoopIntersection(values1, curve1, locations, {
include: include,
// Only possible if there is only one closed curve:
excludeStart: length1 === 1 &&
curve1.getPoint1().equals(curve1.getPoint2())
});
}
// Check for intersections with other curves.
// For self-intersection, we can start at i + 1 instead of 0.
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)
return locations;
var curve2 = curves2[j];
// Avoid end point intersections on consecutive curves when
// self-intersecting.
getCurveIntersections(
values1, values2[j], curve1, curve2, locations,
{
include: include,
// Do not compare indices to determine connection, since
// one array of curves can contain curves from separate
// sup-paths of a compound path.
excludeStart: self && curve1.getPrevious() === curve2,
excludeEnd: self && curve1.getNext() === curve2
}
);
}
}
// Flatten the list of location arrays to one array and return it.
locations = [];
for (var i = 0, l = arrays.length; i < l; i++) {
locations.push.apply(locations, arrays[i]);
}
return locations;
}
/**
* Code to detect overlaps of intersecting based on work by
* @iconexperience in #648
*/
getOverlaps: function(v1, v2) {
function getOverlaps(v1, v2) {
var abs = Math.abs,
timeEpsilon = /*#=*/Numerical.CURVETIME_EPSILON,
geomEpsilon = /*#=*/Numerical.GEOMETRIC_EPSILON,
@ -2122,12 +2188,11 @@ new function() { // Scope for intersection using bezier fat-line clipping
straightBoth = straight1 && straight2;
// Linear curves can only overlap if they are collinear. Instead of
// using the #isCollinear() check, we pick the longer of the two
// curves treated as lines, and see how far the starting and end
// points of the other line are from this line (assumed as an
// infinite line). But even if the curves are not straight, they
// might just have tiny handles within the geometric epsilon
// distance, so we have to check for that too.
// using the #isCollinear() check, we pick the longer of the two curves
// treated as lines, and see how far the starting and end points of the
// other line are from this line (assumed as an infinite line). But even
// if the curves are not straight, they might just have tiny handles
// within geometric epsilon distance, so we have to check for that too.
function getSquaredLineLength(v) {
var x = v[6] - v[0],
@ -2139,9 +2204,9 @@ new function() { // Scope for intersection using bezier fat-line clipping
l1 = flip ? v2 : v1,
l2 = flip ? v1 : v2,
line = new Line(l1[0], l1[1], l1[6], l1[7]);
// See if the starting and end point of curve two are very close to
// the picked line. Note that the curve for the picked line might
// not actually be a line, so we have to perform more checks after.
// See if the starting and end point of curve two are very close to the
// picked line. Note that the curve for the picked line might not
// actually be a line, so we have to perform more checks after.
if (line.getDistance(new Point(l2[0], l2[1])) < geomEpsilon &&
line.getDistance(new Point(l2[6], l2[7])) < geomEpsilon) {
// If not both curves are straight, check against both of their
@ -2159,8 +2224,8 @@ new function() { // Scope for intersection using bezier fat-line clipping
return null;
}
if (straight1 ^ straight2) {
// If one curve is straight, the other curve must be straight,
// too, otherwise they cannot overlap.
// If one curve is straight, the other curve must be straight too,
// otherwise they cannot overlap.
return null;
}
@ -2203,8 +2268,12 @@ new function() { // Scope for intersection using bezier fat-line clipping
pairs = null;
}
return pairs;
},
}
return { statics: /** @lends Curve */{
getCurveIntersections: getCurveIntersections,
getCurvesIntersections: getCurvesIntersections,
getOverlaps: getOverlaps,
// Exposed for use in boolean offsetting
getCurveLineIntersections: getCurveLineIntersections
}};

View file

@ -333,67 +333,9 @@ var PathItem = Item.extend(/** @lends PathItem# */{
if (!self && !this.getBounds(matrix1).touches(path.getBounds(matrix2)))
return [];
var curves1 = this.getCurves(),
curves2 = self ? curves1 : path.getCurves(),
length1 = curves1.length,
length2 = self ? length1 : curves2.length,
values2 = [],
arrays = [],
locations,
current;
// Cache values for curves2 as we re-iterate them for each in curves1.
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 = self ? values2[i] : curve1.getValues(matrix1),
path1 = curve1.getPath();
// NOTE: Due to the nature of Curve._getIntersections(), we need to
// use separate location arrays per path1, to make sure the
// circularity checks are not getting confused by locations on
// separate paths. We are flattening the separate arrays at the end.
if (path1 !== current) {
current = path1;
locations = [];
arrays.push(locations);
}
if (self) {
// First check for self-intersections within the same curve.
Curve._getLoopIntersection(values1, curve1, locations, {
include: include,
// Only possible if there is only one closed curve:
excludeStart: length1 === 1 &&
curve1.getPoint1().equals(curve1.getPoint2())
});
}
// Check for intersections with other curves. For self intersection,
// we can start at i + 1 instead of 0
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)
return locations;
var curve2 = curves2[j];
// Avoid end point intersections on consecutive curves when
// self intersecting.
Curve._getIntersections(
values1, values2[j], curve1, curve2, locations,
{
include: include,
// Do not compare indices here to determine connection,
// since one array of curves can contain curves from
// separate sup-paths of a compound path.
excludeStart: self && curve1.getPrevious() === curve2,
excludeEnd: self && curve1.getNext() === curve2
}
);
}
}
// Now flatten the list of location arrays to one array and return it.
locations = [];
for (var i = 0, l = arrays.length; i < l; i++) {
locations.push.apply(locations, arrays[i]);
}
return locations;
curves2 = !self && path.getCurves();
return Curve.getCurvesIntersections(curves1, curves2, include,
matrix1, matrix2, _returnFirst);
},
/**