Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 81 additions & 101 deletions intersect.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,39 +25,6 @@ var p2s = /,?([a-z]),?/gi,

var isArray = Array.isArray || function(o) { return o instanceof Array; };

function hasProperty(obj, property) {
return Object.prototype.hasOwnProperty.call(obj, property);
}

function repush(array, item) {
for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
return array.push(array.splice(i, 1)[0]);
}
}

function cacher(f) {

function newf() {

var arg = Array.prototype.slice.call(arguments, 0),
args = arg.join('\u2400'),
cache = newf.cache = newf.cache || {},
count = newf.count = newf.count || [];

if (hasProperty(cache, args)) {
repush(count, args);
return cache[args];
}

count.length >= 1e3 && delete cache[count.shift()];
count.push(args);
cache[args] = f(...arguments);

return cache[args];
}
return newf;
}

/**
* Parse SVG path string and return an array of path components.
*
Expand Down Expand Up @@ -219,8 +186,6 @@ function isPointInsideBBox(bbox, x, y) {
}

function isBBoxIntersect(bbox1, bbox2) {
bbox1 = rectBBox(bbox1);
bbox2 = rectBBox(bbox2);
return isPointInsideBBox(bbox2, bbox1.x, bbox1.y)
|| isPointInsideBBox(bbox2, bbox1.x2, bbox1.y)
|| isPointInsideBBox(bbox2, bbox1.x, bbox1.y2)
Expand Down Expand Up @@ -312,9 +277,9 @@ function fixError(number) {
return Math.round(number * 100000000000) / 100000000000;
}

function findBezierIntersections(bez1, bez2, justCount) {
var bbox1 = bezierBBox(bez1),
bbox2 = bezierBBox(bez2);
function findBezierIntersections(bez1, bez2, justCount, bbox1, bbox2) {
bbox1 = bbox1 || bezierBBox(bez1);
bbox2 = bbox2 || bezierBBox(bez2);

if (!isBBoxIntersect(bbox1, bbox2)) {
return justCount ? 0 : [];
Expand All @@ -335,13 +300,15 @@ function findBezierIntersections(bez1, bez2, justCount) {
i, j;

for (i = 0; i < n1 + 1; i++) {
var p = findDotsAtSegment(...bez1, i / n1);
dots1[i] = { x: p.x, y: p.y, t: i / n1 };
var t = i / n1,
p = findDotsAtSegment(...bez1, t);
dots1[i] = { x: p.x, y: p.y, t: t };
}

for (i = 0; i < n2 + 1; i++) {
p = findDotsAtSegment(...bez2, i / n2);
dots2[i] = { x: p.x, y: p.y, t: i / n2 };
t = i / n2;
p = findDotsAtSegment(...bez2, t);
dots2[i] = { x: p.x, y: p.y, t: t };
}

for (i = 0; i < n1; i++) {
Expand Down Expand Up @@ -425,63 +392,33 @@ function findBezierIntersections(bez1, bez2, justCount) {
* @return {Intersection[]|number}
*/
export default function findPathIntersections(path1, path2, justCount) {
path1 = pathToCurve(path1);
path2 = pathToCurve(path2);

var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
var segments1 = pathToSegments(pathToCurve(path1)),
segments2 = pathToSegments(pathToCurve(path2)),
res = justCount ? 0 : [];

for (var i = 0, ii = path1.length; i < ii; i++) {
var pi = path1[i];
for (var i = 0, ii = segments1.length; i < ii; i++) {
var s1 = segments1[i];

if (pi[0] == 'M') {
x1 = x1m = pi[1];
y1 = y1m = pi[2];
} else {
for (var j = 0, jj = segments2.length; j < jj; j++) {
var s2 = segments2[j];

if (pi[0] == 'C') {
bez1 = [ x1, y1, ...pi.slice(1) ];
x1 = bez1[6];
y1 = bez1[7];
} else {
bez1 = [ x1, y1, x1, y1, x1m, y1m, x1m, y1m ];
x1 = x1m;
y1 = y1m;
if (!isBBoxIntersect(s1.bbox, s2.bbox)) {
continue;
}

for (var j = 0, jj = path2.length; j < jj; j++) {
var pj = path2[j];
var intr = findBezierIntersections(s1.bez, s2.bez, justCount, s1.bbox, s2.bbox);

if (pj[0] == 'M') {
x2 = x2m = pj[1];
y2 = y2m = pj[2];
} else {

if (pj[0] == 'C') {
bez2 = [ x2, y2, ...pj.slice(1) ];
x2 = bez2[6];
y2 = bez2[7];
} else {
bez2 = [ x2, y2, x2, y2, x2m, y2m, x2m, y2m ];
x2 = x2m;
y2 = y2m;
}

var intr = findBezierIntersections(bez1, bez2, justCount);

if (justCount) {
res += intr;
} else {
if (justCount) {
res += intr;
} else {

for (var k = 0, kk = intr.length; k < kk; k++) {
intr[k].segment1 = i;
intr[k].segment2 = j;
intr[k].bez1 = bez1;
intr[k].bez2 = bez2;
}
for (var k = 0, kk = intr.length; k < kk; k++) {
intr[k].segment1 = s1.segment;
intr[k].segment2 = s2.segment;
intr[k].bez1 = s1.bez;
intr[k].bez2 = s2.bez;

res = res.concat(intr);
}
res.push(intr[k]);
}
}
}
Expand All @@ -490,6 +427,50 @@ export default function findPathIntersections(path1, path2, justCount) {
return res;
}

/**
* Convert a curved path (only 'M' and 'C' commands) to a list of
* bezier segments, each annotated with its bounding box and the
* index of the originating path segment.
*
* @param {PathComponent[]} pathComponents
*
* @return {{ bez: number[], bbox: object, segment: number }[]}
*/
function pathToSegments(pathComponents) {
var segments = [],
x = 0, y = 0, xm = 0, ym = 0;

for (var i = 0, ii = pathComponents.length; i < ii; i++) {
var pi = pathComponents[i],
bez;

if (pi[0] == 'M') {
x = xm = pi[1];
y = ym = pi[2];

continue;
}

if (pi[0] == 'C') {
bez = [ x, y, pi[1], pi[2], pi[3], pi[4], pi[5], pi[6] ];
x = pi[5];
y = pi[6];
} else {
bez = [ x, y, x, y, xm, ym, xm, ym ];
x = xm;
y = ym;
}

segments.push({
bez: bez,
bbox: bezierBBox(bez),
segment: i
});
}

return segments;
}

/**
* Test if path is a set of path components of the form
* `[ ['M', 0, 0 ], ['L', 100, 100], ...]`.
Expand Down Expand Up @@ -632,20 +613,19 @@ function arcToCurve(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, r
rad = PI / 180 * (+angle || 0),
res = [],
xy,
rotate = cacher(function(x, y, rad) {
var X = x * math.cos(rad) - y * math.sin(rad),
Y = x * math.sin(rad) + y * math.cos(rad);

return { x: X, y: Y };
});
cos = math.cos(rad),
sin = math.sin(rad),
rotate = function(x, y) {
return { x: x * cos - y * sin, y: x * sin + y * cos };
};

if (!recursive) {
xy = rotate(x1, y1, -rad);
xy = rotate(x1, -y1);
x1 = xy.x;
y1 = xy.y;
xy = rotate(x2, y2, -rad);
y1 = -xy.y;
xy = rotate(x2, -y2);
x2 = xy.x;
y2 = xy.y;
y2 = -xy.y;

var x = (x1 - x2) / 2,
y = (y1 - y2) / 2;
Expand Down Expand Up @@ -722,7 +702,7 @@ function arcToCurve(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, r
var newres = [];

for (var i = 0, ii = res.length; i < ii; i++) {
newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
newres[i] = i % 2 ? rotate(res[i - 1], res[i]).y : rotate(res[i], res[i + 1]).x;
}

return newres;
Expand Down
19 changes: 19 additions & 0 deletions test/intersect.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,25 @@ describe('path-intersection', function() {
});


test('many segments, disjoint bounding boxes', {

// ensure segment pairs skipped via bounding box precheck
// do not yield (or hide) intersections
p0: 'M0,0 L100,0 L100,100 L0,100 Z M200,200 L300,200 L300,300 L200,300 Z',
p1: 'M50,-50 L50,150 L150,50 L-50,50 Z M400,400 L500,400 L500,500 L400,500 Z',
expectedIntersections: [
{ x: 50, y: -0, segment1: 1, segment2: 1 },
{ x: -0, y: -0, segment1: 1, segment2: 4 },
{ x: 100, y: 100, segment1: 2, segment2: 2 },
{ x: 100, y: 50, segment1: 2, segment2: 3 },
{ x: 50, y: 100, segment1: 3, segment2: 1 },
{ x: 100, y: 100, segment1: 3, segment2: 2 },
{ x: -0, y: 50, segment1: 4, segment2: 3 },
{ x: -0, y: 0, segment1: 4, segment2: 4 }
]
});


test('two ellipses', {
p0: 'M2.6146209161795992e-14,73 A427,427 -90,0,0 -7.843862748538798e-14,927 A427,427 -90,1,0 2.6146209161795992e-14,73',
p1: 'M71.048,16.789855835444428 A439.5,439.5 0,0,1 928.6918008943089,15.63106872689174 A439.5,439.5 0,1,1 71.048,16.789855835444428',
Expand Down
Loading