Indroduced more reliable method for finding self intersection on curves.
This commit is contained in:
Jan 2015-09-30 12:19:09 +02:00
parent ea3cc63e2e
commit ec70fa1806
2 changed files with 116 additions and 85 deletions

View file

@ -417,13 +417,16 @@ var Curve = Base.extend(/** @lends Curve# */{
* Returns all intersections between two {@link Curve} objects as an array * Returns all intersections between two {@link Curve} objects as an array
* of {@link CurveLocation} objects. * of {@link CurveLocation} objects.
* *
* If the parameter curve is null, the self intersection of the curve is
* returned, if it exists.
*
* @param {Curve} curve the other curve to find the intersections with * @param {Curve} curve the other curve to find the intersections with
* @return {CurveLocation[]} the locations of all intersection between the * @return {CurveLocation[]} the locations of all intersection between the
* curves * curves
*/ */
getIntersections: function(curve) { getIntersections: function(curve) {
return Curve.getIntersections(this.getValues(), curve.getValues(), return Curve.getIntersections(this.getValues(), curve ? curve.getValues() : null,
this, curve, [], {}); this, curve ? curve : this, [], {});
}, },
// TODO: adjustThroughPoint // TODO: adjustThroughPoint
@ -1757,6 +1760,55 @@ new function() { // Scope for intersection using bezier fat-line clipping
// #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, param) { getIntersections: function(v1, v2, c1, c2, locations, param) {
if (!v2) { // if v2 is null or undefined, search for self intersection
// get side of both handles
var h1Side = Line.getSide(v1[0], v1[1], v1[6], v1[7], v1[2], v1[3], false);
var h2Side = Line.getSide(v1[0], v1[1], v1[6], v1[7], v1[4], v1[5], false);
if (h1Side == h2Side) {
var edgeSum = (v1[0] - v1[4]) * (v1[3] - v1[7]) + (v1[2] - v1[6]) * (v1[5] - v1[1]);
// if both handles are on the same side, the curve can only have a self intersection if
// the edge sum and the handles's side have different signs. If the handles are on the
// left side, the edge sum must be negative for a self intersection (and vice versa)
if (Math.sign(edgeSum) == h1Side) return locations;
}
// As a second condition we check if the curve has an inflection point. If an inflection point
// exists, the curve cannot have a self intersection.
var ax = v1[6] - 3 * v1[4] + 3 * v1[2] - v1[0];
var bx = v1[4] - 2 * v1[2] + v1[0];
var cx = v1[2] - v1[0];
var ay = v1[7] - 3 * v1[5] + 3 * v1[3] - v1[1];
var by = v1[5] - 2 * v1[3] + v1[1];
var cy = v1[3] - v1[1];
var hasInflectionPoint = (Math.pow(ay * cx - ax * cy, 2) - 4 * (ay * bx - ax * by) * (by * cx - bx * cy) >= 0);
if (!hasInflectionPoint) {
// the curve may have a self intersection, find parameter to split curve. We search for the
// parameter where the velocity has an extremum by finding the roots of the cross product
// between the bezier curve's first and second derivative
var roots = [],
rootCount = Numerical.solveCubic(ax * ax + ay * ay, 3 * (ax * bx + ay * by),
(2 * (bx * bx + by * by) + ax * cx + ay * cy), (bx * cx + by * cy), roots, 0, 1);
// Select extremum with smallest curvature. This is always on the loop in case of a self intersection
var tSplit, maxCurvature;
for (var i = 0; i < rootCount; i++) {
var curvature = Math.abs(c1.getCurvatureAt(roots[i], true));
if (!maxCurvature || curvature > maxCurvature) {
maxCurvature = curvature;
tSplit = roots[i];
}
}
// Divide the curve in two and then apply the normal curve intersection code.
var parts = Curve.subdivide(v1, tSplit);
if (!param) param = {};
// After splitting, the end is always connected:
param.endConnected = true;
// Since the curve was split above, we need to
// adjust the parameters for both locations.
param.renormalize = function(t1, t2) {
return [t1 * tSplit, t2 * (1 - tSplit) + tSplit];
};
Curve.getIntersections(parts[0], parts[1], c1, c1, locations, param);
}
} else {
// Avoid checking curves if completely out of control bounds. // Avoid checking curves if completely out of control bounds.
// As a little optimization, we can scale the handles with 0.75 // As a little optimization, we can scale the handles with 0.75
// before calculating the control bounds and still be sure that the // before calculating the control bounds and still be sure that the
@ -1822,6 +1874,7 @@ new function() { // Scope for intersection using bezier fat-line clipping
addLocation(locations, param, v1, c1, 1, c1p2, v2, c2, 0, c2p1); addLocation(locations, param, v1, c1, 1, c1p2, v2, c2, 0, c2p1);
if (c1p2.isClose(c2p2, epsilon)) if (c1p2.isClose(c2p2, epsilon))
addLocation(locations, param, v1, c1, 1, c1p2, v2, c2, 1, c2p2); addLocation(locations, param, v1, c1, 1, c1p2, v2, c2, 1, c2p2);
}
return locations; return locations;
} }
}}; }};

View file

@ -88,38 +88,16 @@ var PathItem = Item.extend(/** @lends PathItem# */{
values1 = self ? values2[i] : curve1.getValues(matrix1); values1 = self ? values2[i] : curve1.getValues(matrix1);
if (self) { 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 p1 = curve1.getSegment1()._point,
seg2 = curve1.getSegment2(), p2 = curve1.getSegment2()._point;
p1 = seg1._point, Curve.getIntersections(values1, null, curve1, curve1,
p2 = seg2._point,
h1 = seg1._handleOut,
h2 = seg2._handleIn,
l1 = new Line(p1.subtract(h1), p1.add(h1)),
l2 = new Line(p2.subtract(h2), p1.add(h2));
// Check if extended handles of endpoints of this curve
// intersects each other. We cannot have a self intersection
// within this curve if they don't intersect due to convex-hull
// property.
if (l1.intersect(l2, false)) {
// Self intersecting is found by dividing the curve in two
// and and then applying the normal curve intersection code.
var parts = Curve.subdivide(values1, 0.5);
Curve.getIntersections(parts[0], parts[1], curve1, curve1,
locations, { locations, {
include: include, include: include,
// Only possible if there is only one closed curve: // Only possible if there is only one closed curve:
startConnected: length1 === 1 && p1.equals(p2), startConnected: length1 === 1 && p1.equals(p2)
// After splitting, the end is always connected:
endConnected: true,
renormalize: function(t1, t2) {
// Since the curve was split above, we need to
// adjust the parameters for both locations.
return [t1 / 2, (1 + t2) / 2];
}
} }
); );
} }
}
// 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 = self ? i + 1 : 0; j < length2; j++) { for (var j = self ? i + 1 : 0; j < length2; j++) {