From 4e2680e605e79beb106ee1b74dc7c598b56ca428 Mon Sep 17 00:00:00 2001 From: hkrish Date: Tue, 14 May 2013 20:27:04 +0200 Subject: [PATCH] Minor optimizations --- fatline/Intersect.js | 992 +++++++++++++++++++++---------------------- 1 file changed, 496 insertions(+), 496 deletions(-) diff --git a/fatline/Intersect.js b/fatline/Intersect.js index 59e4cce8..c2ad6680 100644 --- a/fatline/Intersect.js +++ b/fatline/Intersect.js @@ -1,496 +1,496 @@ - -var EPSILON = 10e-12; -var TOLERANCE = 10e-6; -var MAX_RECURSE = 10; -var MAX_ITERATE = 20; - -/** - * This method is analogous to paperjs#PathItem.getIntersections - */ -function getIntersections2( path1, path2 ){ - // First check the bounds of the two paths. If they don't intersect, - // we don't need to iterate through their curves. - if (!path1.getBounds().touches(path2.getBounds())) - return []; - var locations = [], - curves1 = path1.getCurves(), - curves2 = path2.getCurves(), - length2 = curves2.length, - values2 = []; - for (var i = 0; i < length2; i++) - values2[i] = curves2[i].getValues(); - for (var i = 0, l = curves1.length; i < l; i++) { - var curve1 = curves1[i], - values1 = curve1.getValues(); - for (var j = 0; j < length2; j++){ - value2 = values2[j]; - var v1Linear = Curve.isLinear(values1); - var v2Linear = Curve.isLinear(value2); - if( v1Linear && v2Linear ){ - _getLineLineIntersection(values1, value2, curve1, curves2[j], locations); - } else if ( v1Linear || v2Linear ){ - _getCurveLineIntersection(values1, value2, curve1, curves2[j], locations); - } else { - Curve.getIntersections2(values1, value2, curve1, curves2[j], locations); - } - } - } - return locations; -} - -/** - * This method is analogous to paperjs#Curve.getIntersections - * @param {[type]} v1 - * @param {[type]} v2 - * @param {[type]} curve1 - * @param {[type]} curve2 - * @param {[type]} locations - * @param {[type]} _v1t - Only used for recusion - * @param {[type]} _v2t - Only used for recusion - */ -paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1t, _v2t, _recurseDepth ) { - _recurseDepth = _recurseDepth ? _recurseDepth + 1 : 1; - // Avoid endless recursion. - // Perhaps we should fall back to a more expensive method after this, but - // so far endless recursion happens only when there is no real intersection and - // the infinite fatline continue to intersect with the other curve outside its bounds! - if( _recurseDepth > MAX_RECURSE ) return; - // cache the original parameter range. - _v1t = _v1t || { t1: 0, t2: 1 }; - _v2t = _v2t || { t1: 0, t2: 1 }; - var v1t = { t1: _v1t.t1, t2: _v1t.t2 }; - var v2t = { t1: _v2t.t1, t2: _v2t.t2 }; - // Get the clipped parts from the original curve, to avoid cumulative errors - var _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); - var _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); -// markCurve( _v1, '#f0f', true ); -// markCurve( _v2, '#0ff', false ); - var nuT, parts, tmpt = { t1:null, t2:null }, iterate = 0; - // Loop until both parameter range converge. We have to handle the degenerate case - // seperately, where fat-line clipping can become numerically unstable when one of the - // curves has converged to a point and the other hasn't. - while( iterate < MAX_ITERATE && - ( Math.abs(v1t.t2 - v1t.t1) > TOLERANCE || Math.abs(v2t.t2 - v2t.t1) > TOLERANCE ) ){ - ++iterate; - // First we clip v2 with v1's fat-line - tmpt.t1 = v2t.t1; tmpt.t2 = v2t.t2; - var intersects1 = _clipBezierFatLine( _v1, _v2, tmpt ); - // Stop if there are no possible intersections - if( intersects1 === 0 ){ - return; - } else if( intersects1 > 0 ){ - // Get the clipped parts from the original v2, to avoid cumulative errors - // ...and reuse some objects. - v2t.t1 = tmpt.t1; v2t.t2 = tmpt.t2; - _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); - } -// markCurve( _v2, '#0ff', false ); - // Next we clip v1 with nuv2's fat-line - tmpt.t1 = v1t.t1; tmpt.t2 = v1t.t2; - var intersects2 = _clipBezierFatLine( _v2, _v1, tmpt ); - // Stop if there are no possible intersections - if( intersects2 === 0 ){ - return; - }else if( intersects1 > 0 ){ - // Get the clipped parts from the original v2, to avoid cumulative errors - v1t.t1 = tmpt.t1; v1t.t2 = tmpt.t2; - _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); - } -// markCurve( _v1, '#f0f', true ); - // Get the clipped parts from the original v1 - // Check if there could be multiple intersections - if( intersects1 < 0 || intersects2 < 0 ){ - // Subdivide the curve which has converged the least from the original range [0,1], - // which would be the curve with the largest parameter range after clipping - if( v1t.t2 - v1t.t1 > v2t.t2 - v2t.t1 ){ - // subdivide _v1 and recurse - nuT = ( _v1t.t1 + _v1t.t2 ) / 2.0; - Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: _v1t.t1, t2: nuT }, _v2t, _recurseDepth ); - Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: nuT, t2: _v1t.t2 }, _v2t, _recurseDepth ); - return; - } else { - // subdivide _v2 and recurse - nuT = ( _v2t.t1 + _v2t.t2 ) / 2.0; - Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: _v2t.t1, t2: nuT }, _recurseDepth ); - Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: nuT, t2: _v2t.t2 }, _recurseDepth ); - return; - } - } - // We need to bailout of clipping and try a numerically stable method if - // any of the following are true. - // 1. One of the parameter ranges is converged to a point. - // 2. Both of the parameter ranges have converged reasonably well ( according to TOLERENCE ). - // 3. One of the parameter range is converged enough so that it is *flat enough* to - // calculate line curve intersection implicitly. - // - // Check if one of the parameter range has converged completely to a point. - // Now things could get only worse if we iterate more for the other - // curve to converge if it hasn't yet happened so. - if( Math.abs(v1t.t2 - v1t.t1) < EPSILON ){ - locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); - return; - }else if( Math.abs(v2t.t2 - v2t.t1) < EPSILON ){ - locations.push(new CurveLocation(curve1, null, curve2.getPointAt(v1t.t1, true), curve2)); - return; - } - // Check to see if both parameter ranges have converged or else, - // see if either or both of the curves are flat enough to be treated as lines - if( Math.abs(v1t.t2 - v1t.t1) <= TOLERANCE || Math.abs(v2t.t2 - v2t.t1) <= TOLERANCE ){ - locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); - return; - } else { - var curve1Flat = Curve.isFlatEnough( _v1, /*#=*/ TOLERANCE ); - var curve2Flat = Curve.isFlatEnough( _v2, /*#=*/ TOLERANCE ); - if ( curve1Flat && curve2Flat ) { - _getLineLineIntersection( _v1, _v2, curve1, curve2, locations ); - return; - } else if( curve1Flat || curve2Flat ){ - // Use curve line intersection method while specifying which curve to be treated as line - _getCurveLineIntersection( _v1, _v2, curve1, curve2, locations, curve1Flat ); - return; - } - } - } -}; - -/** - * Clip curve V2 with fat-line of v1 - * @param {Array} v1 - Section of the first curve, for which we will make a fat-line - * @param {Array} v2 - Section of the second curve; we will clip this curve with the fat-line of v1 - * @param {Object} v2t - The parameter range of v2 - * @return {number} -> 0 -no Intersection, 1 -one intersection, -1 -more than one intersection - */ -function _clipBezierFatLine( v1, v2, v2t ){ - // first curve, P - var p0x = v1[0], p0y = v1[1], p3x = v1[6], p3y = v1[7]; - var p1x = v1[2], p1y = v1[3], p2x = v1[4], p2y = v1[5]; - // second curve, Q - var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7]; - var q1x = v2[2], q1y = v2[3], q2x = v2[4], q2y = v2[5]; - // Calculate the fat-line L for P is the baseline l and two - // offsets which completely encloses the curve P. - var d1 = _getSignedDist( p0x, p0y, p3x, p3y, p1x, p1y ) || 0; - var d2 = _getSignedDist( p0x, p0y, p3x, p3y, p2x, p2y ) || 0; - var dmin, dmax; - if( d1 * d2 > 0){ - // 3/4 * min{0, d1, d2} - dmin = 0.75 * Math.min( 0, d1, d2 ); - dmax = 0.75 * Math.max( 0, d1, d2 ); - } else { - // 4/9 * min{0, d1, d2} - dmin = 0.4444444444444444 * Math.min( 0, d1, d2 ); - dmax = 0.4444444444444444 * Math.max( 0, d1, d2 ); - } - // Calculate non-parametric bezier curve D(ti, di(t)) - - // di(t) is the distance of Q from the baseline l of the fat-line, - // ti is equally spaced in [0,1] - var dq0 = _getSignedDist( p0x, p0y, p3x, p3y, q0x, q0y ); - var dq1 = _getSignedDist( p0x, p0y, p3x, p3y, q1x, q1y ); - var dq2 = _getSignedDist( p0x, p0y, p3x, p3y, q2x, q2y ); - var dq3 = _getSignedDist( p0x, p0y, p3x, p3y, q3x, q3y ); - // Find the minimum and maximum distances from l, - // this is useful for checking whether the curves intersect with each other or not. - var mindist = Math.min( dq0, dq1, dq2, dq3 ); - var maxdist = Math.max( dq0, dq1, dq2, dq3 ); - // If the fatlines don't overlap, we have no intersections! - if( dmin > maxdist || dmax < mindist ){ - return 0; - } - // Calculate the convex hull for non-parametric bezier curve D(ti, di(t)) - var Dt = _convexhull( dq0, dq1, dq2, dq3 ); - // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax - // for the coorresponding t values (tmin, tmax): - // Portions of curve v2 before tmin and after tmax can safely be clipped away - // TODO: try to calculate tmin and tmax directly here - var tmindmin = Infinity, tmaxdmin = -Infinity, - tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i, len; - // var dmina = [0, dmin, 2, dmin]; - // var dmaxa = [0, dmax, 2, dmax]; - for (i = 0, len = Dt.length; i < len; i++) { - var Dtl = Dt[i]; - // ixd = _intersectLines( Dtl, dmina); - // TODO: Optimize: Avaoid creating point objects in Line.intersect?! - speeds up by 30%! - ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmin, 2, dmin, false); - if( ixd ){ - ixdx = ixd[0]; - tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; - tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; - } - // ixd = _intersectLines( Dtl, dmaxa); - ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmax, 2, dmax, false); - if( ixd ){ - ixdx = ixd[0]; - tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; - tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; - } - } - // Return the parameter values for v2 for which we can be sure that the - // intersection with v1 lies within. - var tmin, tmax; - if( dq3 > dq0 ){ - // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits - if( tmindmin === Infinity ) tmindmin = 1e-11; - if( tmaxdmin === -Infinity ) tmaxdmin = 1e-11; - if( tmindmax === Infinity ) tmindmax = 0.9999999999999999; - if( tmaxdmax === -Infinity ) tmaxdmax = 0.9999999999999999; - tmin = Math.min( tmindmin, tmaxdmin ); - tmax = Math.max( tmindmax, tmaxdmax ); - if( Math.min( tmindmax, tmaxdmax ) < tmin ) - tmin = 0; - if( Math.max( tmindmin, tmaxdmin ) > tmax ) - tmax = 1; - }else{ - // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits - if( tmindmin === Infinity ) tmindmin = 0.9999999999999999; - if( tmaxdmin === -Infinity ) tmaxdmin = 0.9999999999999999; - if( tmindmax === Infinity ) tmindmax = 1e-11; - if( tmaxdmax === -Infinity ) tmaxdmax = 1e-11; - tmax = Math.max( tmindmin, tmaxdmin ); - tmin = Math.min( tmindmax, tmaxdmax ); - if( Math.min( tmindmin, tmaxdmin ) < tmin ) - tmin = 0; - if( Math.max( tmindmax, tmaxdmax ) > tmax ) - tmax = 1; - } -// Debug: Plot the non-parametric graph and hull -// plotD_vs_t( 500, 110, Dt, [dq0, dq1, dq2, dq3], v1, dmin, dmax, tmin, tmax, 1.0 / ( tmax - tmin + 0.3 ) ) - if( tmin === 0.0 && tmax === 1.0 ){ - return 0; - } - // tmin and tmax are within the range (0, 1). We need to project it to the original - // parameter range for v2. - var v2tmin = v2t.t1; - var tdiff = ( v2t.t2 - v2tmin ); - v2t.t1 = v2tmin + tmin * tdiff; - v2t.t2 = v2tmin + tmax * tdiff; - // If the new parameter range fails to converge by atleast 20% of the original range, - // possibly we have multiple intersections. We need to subdivide one of the curves. - if( (tdiff - ( v2t.t2 - v2t.t1 ))/tdiff < 0.2 ){ - return -1; - } - return 1; -} - -/** - * Calculate the convex hull for the non-paramertic bezier curve D(ti, di(t)). - * The ti is equally spaced across [0..1] — [0, 1/3, 2/3, 1] for - * di(t), [dq0, dq1, dq2, dq3] respectively. In other words our CVs for the curve are - * already sorted in the X axis in the increasing order. Calculating convex-hull is - * much easier than a set of arbitrary points. - */ -function _convexhull( dq0, dq1, dq2, dq3 ){ - var distq1 = _getSignedDist( 0.0, dq0, 1.0, dq3, 0.3333333333333333, dq1 ); - var distq2 = _getSignedDist( 0.0, dq0, 1.0, dq3, 0.6666666666666666, dq2 ); - // Check if [1/3, dq1] and [2/3, dq2] are on the same side of line [0,dq0, 1,dq3] - if( distq1 * distq2 < 0 ) { - // dq1 and dq2 lie on different sides on [0, q0, 1, q3] - // Convexhull is a quadrilateral and line [0, q0, 1, q3] is NOT part of the convexhull - // so we are pretty much done here. - Dt = [ - [ 0.0, dq0, 0.3333333333333333, dq1 ], - [ 0.3333333333333333, dq1, 1.0, dq3 ], - [ 0.6666666666666666, dq2, 0.0, dq0 ], - [ 1.0, dq3, 0.6666666666666666, dq2 ] - ]; - } else { - // dq1 and dq2 lie on the same sides on [0, q0, 1, q3] - // Convexhull can be a triangle or a quadrilateral and - // line [0, q0, 1, q3] is part of the convexhull. - // Check if the hull is a triangle or a quadrilateral - var dqmin, dqmax, dqapex1, dqapex2; - distq1 = Math.abs(distq1); - distq2 = Math.abs(distq2); - var vqa1a2x, vqa1a2y, vqa1Maxx, vqa1Maxy, vqa1Minx, vqa1Miny; - if( distq1 > distq2 ){ - dqmin = [ 0.6666666666666666, dq2 ]; - dqmax = [ 0.3333333333333333, dq1 ]; - // apex is dq3 and the other apex point is dq0 - // vector dqapex->dqapex2 or the base vector which is already part of c-hull - vqa1a2x = 1.0, vqa1a2y = dq3 - dq0; - // vector dqapex->dqmax - vqa1Maxx = 0.6666666666666666, vqa1Maxy = dq3 - dq1; - // vector dqapex->dqmin - vqa1Minx = 0.3333333333333333, vqa1Miny = dq3 - dq2; - } else { - dqmin = [ 0.3333333333333333, dq1 ]; - dqmax = [ 0.6666666666666666, dq2 ]; - // apex is dq0 in this case, and the other apex point is dq3 - // vector dqapex->dqapex2 or the base vector which is already part of c-hull - vqa1a2x = -1.0, vqa1a2y = dq0 - dq3; - // vector dqapex->dqmax - vqa1Maxx = -0.6666666666666666, vqa1Maxy = dq0 - dq2; - // vector dqapex->dqmin - vqa1Minx = -0.3333333333333333, vqa1Miny = dq0 - dq1; - } - // compare cross products of these vectors to determine, if - // point is in triangles [ dq3, dqMax, dq0 ] or [ dq0, dqMax, dq3 ] - var vcrossa1a2_a1Min = vqa1a2x * vqa1Miny - vqa1a2y * vqa1Minx; - var vcrossa1Max_a1Min = vqa1Maxx * vqa1Miny - vqa1Maxy * vqa1Minx; - if( vcrossa1Max_a1Min * vcrossa1a2_a1Min < 0 ){ - // Point [2/3, dq2] is inside the triangle and the convex hull is a triangle - Dt = [ - [ 0.0, dq0, dqmax[0], dqmax[1] ], - [ dqmax[0], dqmax[1], 1.0, dq3 ], - [ 1.0, dq3, 0.0, dq0 ] - ]; - } else { - // Convexhull is a quadrilateral and we need all lines in the correct order where - // line [0, q0, 1, q3] is part of the convex hull - Dt = [ - [ 0.0, dq0, 0.3333333333333333, dq1 ], - [ 0.3333333333333333, dq1, 0.6666666666666666, dq2 ], - [ 0.6666666666666666, dq2, 1.0, dq3 ], - [ 1.0, dq3, 0.0, dq0 ] - ]; - } - } - return Dt; -} - - -function drawFatline( v1 ) { - function signum(num) { - return ( num > 0 )? 1 : ( num < 0 )? -1 : 0; - } - var l = new Line( [v1[0], v1[1]], [v1[6], v1[7]], false ); - var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); - var d1 = l.getSide( p1 ) * l.getDistance( p1 ) || 0; - var d2 = l.getSide( p2 ) * l.getDistance( p2 ) || 0; - var dmin, dmax; - if( d1 * d2 > 0){ - // 3/4 * min{0, d1, d2} - dmin = 0.75 * Math.min( 0, d1, d2 ); - dmax = 0.75 * Math.max( 0, d1, d2 ); - } else { - // 4/9 * min{0, d1, d2} - dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; - dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; - } - var ll = new Path.Line( v1[0], v1[1], v1[6], v1[7] ); - window.__p3.push( ll ); - window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9, 0.8); - var lp1 = ll.segments[0].point; - var lp2 = ll.segments[1].point; - var pm = l.vector, pm1 = pm.rotate( signum( dmin ) * -90 ), pm2 = pm.rotate( signum( dmax ) * -90 ); - var p11 = lp1.add( pm1.normalize( Math.abs(dmin) ) ); - var p12 = lp2.add( pm1.normalize( Math.abs(dmin) ) ); - var p21 = lp1.add( pm2.normalize( Math.abs(dmax) ) ); - var p22 = lp2.add( pm2.normalize( Math.abs(dmax) ) ); - window.__p3.push( new Path.Line( p11, p12 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); - window.__p3.push( new Path.Line( p21, p22 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); -} - -function plotD_vs_t( x, y, arr, arr2, v, dmin, dmax, tmin, tmax, yscale, tvalue ){ - yscale = yscale || 1; - new Path.Line( x, y-100, x, y+100 ).style.strokeColor = '#aaa'; - new Path.Line( x, y, x + 200, y ).style.strokeColor = '#aaa'; - - var clr = (tvalue)? '#a00' : '#00a'; - if( window.__p3 ) window.__p3.map(function(a){a.remove();}); - - window.__p3 = []; - - drawFatline( v ); - - window.__p3.push( new Path.Line( x, y + dmin * yscale, x + 200, y + dmin * yscale ) ); - window.__p3[window.__p3.length-1].style.strokeColor = '#000' - window.__p3.push( new Path.Line( x, y + dmax * yscale, x + 200, y + dmax * yscale ) ); - window.__p3[window.__p3.length-1].style.strokeColor = '#000' - window.__p3.push( new Path.Line( x + tmin * 190, y-100, x + tmin * 190, y+100 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = clr - window.__p3.push( new Path.Line( x + tmax * 190, y-100, x + tmax * 190, y+100 ) ); - window.__p3[window.__p3.length-1].style.strokeColor = clr - - for (var i = 0; i < arr.length; i++) { - window.__p3.push( new Path.Line( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ), - new Point( x + arr[i][2] * 190, y + arr[i][3] * yscale ) ) ); - window.__p3[window.__p3.length-1].style.strokeColor = '#999'; - } - var pnt = []; - var arr2x = [ 0.0, 0.333333333, 0.6666666666, 1.0 ]; - for (var i = 0; i < arr2.length; i++) { - pnt.push( new Point( x + arr2x[i] * 190, y + arr2[i] * yscale ) ); - window.__p3.push( new Path.Circle( pnt[pnt.length-1], 2 ) ); - window.__p3[window.__p3.length-1].style.fillColor = '#000' - } - // var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); - // pth.closed = true; - window.__p3.push( new Path( new Segment(pnt[0], null, pnt[1].subtract(pnt[0])), new Segment( pnt[3], pnt[2].subtract(pnt[3]), null ) ) ); - window.__p3[window.__p3.length-1].style.strokeColor = clr - view.draw(); -} - -// This is basically an "unrolled" version of #Line.getDistance() with sign -// May be a static method could be better! -var _getSignedDist = function( a1x, a1y, a2x, a2y, bx, by ){ - var vx = a2x - a1x, vy = a2y - a1y; - var m = vy / vx, b = a1y - ( m * a1x ); - return ( by - ( m * bx ) - b ) / Math.sqrt( m*m + 1 ); -}; - -/** - * Intersections between curve and line becomes rather simple here mostly - * because of paperjs Numerical class. We can rotate the curve and line so that - * the line is on X axis, and solve the implicit equations for X axis and the curve - */ -var _getCurveLineIntersection = function( v1, v2, curve1, curve2, locations, _other ){ - var i, root, point, vc = v1, vl = v2; - var other = ( _other === undefined )? Curve.isLinear( v1 ) : _other; - if( other ){ - vl = v1; - vc = v2; - } - var l1x = vl[0], l1y = vl[1], l2x = vl[6], l2y = vl[7]; - // rotate both the curve and line around l1 so that line is on x axis - var lvx = l2x - l1x, lvy = l2y - l1y; - // Angle with x axis (1, 0) - var angle = Math.atan2( -lvy, lvx ), sina = Math.sin( angle ), cosa = Math.cos( angle ); - // rotated line and curve values - // (rl1x, rl1y) = (0, 0) - var rl2x = lvx * cosa - lvy * sina, rl2y = lvy * cosa + lvx * sina; - var rvc = []; - for( i=0; i<8; i+=2 ){ - var vcx = vc[i] - l1x, vcy = vc[i+1] - l1y; - rvc.push( vcx * cosa - vcy * sina ); - rvc.push( vcy * cosa + vcx * sina ); - } - var roots = []; - Curve.solveCubic(rvc, 1, 0, roots); - i = roots.length; - while( i-- ){ - root = roots[i]; - if( root >= 0 && root <= 1 ){ - point = Curve.evaluate(rvc, root, true, 0); - // We do have a point on the infinite line. Check if it falls on the line *segment*. - if( point.x >= 0 && point.x <= rl2x ){ - // The actual intersection point - point = Curve.evaluate(vc, root, true, 0); - if( other ) root = null; - var first = locations[0], - last = locations[locations.length - 1]; - if ((!first || !point.equals(first._point)) - && (!last || !point.equals(last._point))) - locations.push( new CurveLocation( curve1, root, point, curve2 ) ); - } - } - } -}; - -var _getLineLineIntersection = function( v1, v2, curve1, curve2, locations ){ - var point = Line.intersect( - v1[0], v1[1], v1[6], v1[7], - v2[0], v2[1], v2[6], v2[7], false); - if (point) { - // Avoid duplicates when hitting segments (closed paths too) - var first = locations[0], - last = locations[locations.length - 1]; - if ((!first || !point.equals(first._point)) - && (!last || !point.equals(last._point))) - // Passing null for parameter leads to lazy determination - // of parameter values in CurveLocation#getParameter() - // only once they are requested. - locations.push(new CurveLocation(curve1, null, point, curve2)); - } -}; + +var EPSILON = 10e-12; +var TOLERANCE = 10e-6; +var MAX_RECURSE = 10; +var MAX_ITERATE = 20; + +/** + * This method is analogous to paperjs#PathItem.getIntersections + */ +function getIntersections2( path1, path2 ){ + // First check the bounds of the two paths. If they don't intersect, + // we don't need to iterate through their curves. + if (!path1.getBounds().touches(path2.getBounds())) + return []; + var locations = [], + curves1 = path1.getCurves(), + curves2 = path2.getCurves(), + length2 = curves2.length, + values2 = []; + for (var i = 0; i < length2; i++) + values2[i] = curves2[i].getValues(); + for (var i = 0, l = curves1.length; i < l; i++) { + var curve1 = curves1[i], + values1 = curve1.getValues(); + for (var j = 0; j < length2; j++){ + value2 = values2[j]; + var v1Linear = Curve.isLinear(values1); + var v2Linear = Curve.isLinear(value2); + if( v1Linear && v2Linear ){ + _getLineLineIntersection(values1, value2, curve1, curves2[j], locations); + } else if ( v1Linear || v2Linear ){ + _getCurveLineIntersection(values1, value2, curve1, curves2[j], locations); + } else { + Curve.getIntersections2(values1, value2, curve1, curves2[j], locations); + } + } + } + return locations; +} + +/** + * This method is analogous to paperjs#Curve.getIntersections + * @param {[type]} v1 + * @param {[type]} v2 + * @param {[type]} curve1 + * @param {[type]} curve2 + * @param {[type]} locations + * @param {[type]} _v1t - Only used for recusion + * @param {[type]} _v2t - Only used for recusion + */ +paper.Curve.getIntersections2 = function( v1, v2, curve1, curve2, locations, _v1t, _v2t, _recurseDepth ) { + _recurseDepth = _recurseDepth ? _recurseDepth + 1 : 1; + // Avoid endless recursion. + // Perhaps we should fall back to a more expensive method after this, but + // so far endless recursion happens only when there is no real intersection and + // the infinite fatline continue to intersect with the other curve outside its bounds! + if( _recurseDepth > MAX_RECURSE ) return; + // cache the original parameter range. + _v1t = _v1t || { t1: 0, t2: 1 }; + _v2t = _v2t || { t1: 0, t2: 1 }; + var v1t = { t1: _v1t.t1, t2: _v1t.t2 }; + var v2t = { t1: _v2t.t1, t2: _v2t.t2 }; + // Get the clipped parts from the original curve, to avoid cumulative errors + var _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); + var _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); +// markCurve( _v1, '#f0f', true ); +// markCurve( _v2, '#0ff', false ); + var nuT, parts, tmpt = { t1:null, t2:null }, iterate = 0; + // Loop until both parameter range converge. We have to handle the degenerate case + // seperately, where fat-line clipping can become numerically unstable when one of the + // curves has converged to a point and the other hasn't. + while( iterate < MAX_ITERATE && + ( Math.abs(v1t.t2 - v1t.t1) > TOLERANCE || Math.abs(v2t.t2 - v2t.t1) > TOLERANCE ) ){ + ++iterate; + // First we clip v2 with v1's fat-line + tmpt.t1 = v2t.t1; tmpt.t2 = v2t.t2; + var intersects1 = _clipBezierFatLine( _v1, _v2, tmpt ); + // Stop if there are no possible intersections + if( intersects1 === 0 ){ + return; + } else if( intersects1 > 0 ){ + // Get the clipped parts from the original v2, to avoid cumulative errors + // ...and reuse some objects. + v2t.t1 = tmpt.t1; v2t.t2 = tmpt.t2; + _v2 = Curve.getPart( v2, v2t.t1, v2t.t2 ); + } +// markCurve( _v2, '#0ff', false ); + // Next we clip v1 with nuv2's fat-line + tmpt.t1 = v1t.t1; tmpt.t2 = v1t.t2; + var intersects2 = _clipBezierFatLine( _v2, _v1, tmpt ); + // Stop if there are no possible intersections + if( intersects2 === 0 ){ + return; + }else if( intersects1 > 0 ){ + // Get the clipped parts from the original v2, to avoid cumulative errors + v1t.t1 = tmpt.t1; v1t.t2 = tmpt.t2; + _v1 = Curve.getPart( v1, v1t.t1, v1t.t2 ); + } +// markCurve( _v1, '#f0f', true ); + // Get the clipped parts from the original v1 + // Check if there could be multiple intersections + if( intersects1 < 0 || intersects2 < 0 ){ + // Subdivide the curve which has converged the least from the original range [0,1], + // which would be the curve with the largest parameter range after clipping + if( v1t.t2 - v1t.t1 > v2t.t2 - v2t.t1 ){ + // subdivide _v1 and recurse + nuT = ( _v1t.t1 + _v1t.t2 ) / 2.0; + Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: _v1t.t1, t2: nuT }, _v2t, _recurseDepth ); + Curve.getIntersections2( v1, v2, curve1, curve2, locations, { t1: nuT, t2: _v1t.t2 }, _v2t, _recurseDepth ); + return; + } else { + // subdivide _v2 and recurse + nuT = ( _v2t.t1 + _v2t.t2 ) / 2.0; + Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: _v2t.t1, t2: nuT }, _recurseDepth ); + Curve.getIntersections2( v1, v2, curve1, curve2, locations, _v1t, { t1: nuT, t2: _v2t.t2 }, _recurseDepth ); + return; + } + } + // We need to bailout of clipping and try a numerically stable method if + // any of the following are true. + // 1. One of the parameter ranges is converged to a point. + // 2. Both of the parameter ranges have converged reasonably well ( according to TOLERENCE ). + // 3. One of the parameter range is converged enough so that it is *flat enough* to + // calculate line curve intersection implicitly. + // + // Check if one of the parameter range has converged completely to a point. + // Now things could get only worse if we iterate more for the other + // curve to converge if it hasn't yet happened so. + if( Math.abs(v1t.t2 - v1t.t1) < EPSILON ){ + locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); + return; + }else if( Math.abs(v2t.t2 - v2t.t1) < EPSILON ){ + locations.push(new CurveLocation(curve1, null, curve2.getPointAt(v1t.t1, true), curve2)); + return; + } + // Check to see if both parameter ranges have converged or else, + // see if either or both of the curves are flat enough to be treated as lines + if( Math.abs(v1t.t2 - v1t.t1) <= TOLERANCE || Math.abs(v2t.t2 - v2t.t1) <= TOLERANCE ){ + locations.push(new CurveLocation(curve1, v1t.t1, curve1.getPointAt(v1t.t1, true), curve2)); + return; + } else { + var curve1Flat = Curve.isFlatEnough( _v1, /*#=*/ TOLERANCE ); + var curve2Flat = Curve.isFlatEnough( _v2, /*#=*/ TOLERANCE ); + if ( curve1Flat && curve2Flat ) { + _getLineLineIntersection( _v1, _v2, curve1, curve2, locations ); + return; + } else if( curve1Flat || curve2Flat ){ + // Use curve line intersection method while specifying which curve to be treated as line + _getCurveLineIntersection( _v1, _v2, curve1, curve2, locations, curve1Flat ); + return; + } + } + } +}; + +/** + * Clip curve V2 with fat-line of v1 + * @param {Array} v1 - Section of the first curve, for which we will make a fat-line + * @param {Array} v2 - Section of the second curve; we will clip this curve with the fat-line of v1 + * @param {Object} v2t - The parameter range of v2 + * @return {number} -> 0 -no Intersection, 1 -one intersection, -1 -more than one intersection + */ +function _clipBezierFatLine( v1, v2, v2t ){ + // first curve, P + var p0x = v1[0], p0y = v1[1], p3x = v1[6], p3y = v1[7]; + var p1x = v1[2], p1y = v1[3], p2x = v1[4], p2y = v1[5]; + // second curve, Q + var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7]; + var q1x = v2[2], q1y = v2[3], q2x = v2[4], q2y = v2[5]; + // Calculate the fat-line L for P is the baseline l and two + // offsets which completely encloses the curve P. + var d1 = _getSignedDist( p0x, p0y, p3x, p3y, p1x, p1y ) || 0; + var d2 = _getSignedDist( p0x, p0y, p3x, p3y, p2x, p2y ) || 0; + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 0.4444444444444444 * Math.min( 0, d1, d2 ); + dmax = 0.4444444444444444 * Math.max( 0, d1, d2 ); + } + // Calculate non-parametric bezier curve D(ti, di(t)) - + // di(t) is the distance of Q from the baseline l of the fat-line, + // ti is equally spaced in [0,1] + var dq0 = _getSignedDist( p0x, p0y, p3x, p3y, q0x, q0y ); + var dq1 = _getSignedDist( p0x, p0y, p3x, p3y, q1x, q1y ); + var dq2 = _getSignedDist( p0x, p0y, p3x, p3y, q2x, q2y ); + var dq3 = _getSignedDist( p0x, p0y, p3x, p3y, q3x, q3y ); + // Find the minimum and maximum distances from l, + // this is useful for checking whether the curves intersect with each other or not. + var mindist = Math.min( dq0, dq1, dq2, dq3 ); + var maxdist = Math.max( dq0, dq1, dq2, dq3 ); + // If the fatlines don't overlap, we have no intersections! + if( dmin > maxdist || dmax < mindist ){ + return 0; + } + // Calculate the convex hull for non-parametric bezier curve D(ti, di(t)) + var Dt = _convexhull( dq0, dq1, dq2, dq3 ); + // Now we clip the convex hulls for D(ti, di(t)) with dmin and dmax + // for the coorresponding t values (tmin, tmax): + // Portions of curve v2 before tmin and after tmax can safely be clipped away + // TODO: try to calculate tmin and tmax directly here + var tmindmin = Infinity, tmaxdmin = -Infinity, + tmindmax = Infinity, tmaxdmax = -Infinity, ixd, ixdx, i, len; + // var dmina = [0, dmin, 2, dmin]; + // var dmaxa = [0, dmax, 2, dmax]; + for (i = 0, len = Dt.length; i < len; i++) { + var Dtl = Dt[i]; + // ixd = _intersectLines( Dtl, dmina); + // TODO: Optimize: Avaoid creating point objects in Line.intersect?! - speeds up by 30%! + ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmin, 2, dmin, false); + if( ixd ){ + ixdx = ixd[0]; + tmindmin = ( ixdx < tmindmin )? ixdx : tmindmin; + tmaxdmin = ( ixdx > tmaxdmin )? ixdx : tmaxdmin; + } + // ixd = _intersectLines( Dtl, dmaxa); + ixd = Line.intersectRaw( Dtl[0], Dtl[1], Dtl[2], Dtl[3], 0, dmax, 2, dmax, false); + if( ixd ){ + ixdx = ixd[0]; + tmindmax = ( ixdx < tmindmax )? ixdx : tmindmax; + tmaxdmax = ( ixdx > tmaxdmax )? ixdx : tmaxdmax; + } + } + // Return the parameter values for v2 for which we can be sure that the + // intersection with v1 lies within. + var tmin, tmax; + if( dq3 > dq0 ){ + // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits + if( tmindmin === Infinity ) tmindmin = 0; + if( tmaxdmin === -Infinity ) tmaxdmin = 0; + if( tmindmax === Infinity ) tmindmax = 1; + if( tmaxdmax === -Infinity ) tmaxdmax =1; + tmin = Math.min( tmindmin, tmaxdmin ); + tmax = Math.max( tmindmax, tmaxdmax ); + if( Math.min( tmindmax, tmaxdmax ) < tmin ) + tmin = 0; + if( Math.max( tmindmin, tmaxdmin ) > tmax ) + tmax = 1; + }else{ + // if dmin or dmax doesnot intersect with the convexhull, reset the parameter limits + if( tmindmin === Infinity ) tmindmin =1; + if( tmaxdmin === -Infinity ) tmaxdmin =1; + if( tmindmax === Infinity ) tmindmax = 0; + if( tmaxdmax === -Infinity ) tmaxdmax = 0; + tmax = Math.max( tmindmin, tmaxdmin ); + tmin = Math.min( tmindmax, tmaxdmax ); + if( Math.min( tmindmin, tmaxdmin ) < tmin ) + tmin = 0; + if( Math.max( tmindmax, tmaxdmax ) > tmax ) + tmax = 1; + } +// Debug: Plot the non-parametric graph and hull +// plotD_vs_t( 500, 110, Dt, [dq0, dq1, dq2, dq3], v1, dmin, dmax, tmin, tmax, 1.0 / ( tmax - tmin + 0.3 ) ) + // if( tmin === 0.0 && tmax === 1.0 ){ + // return 0; + // } + // tmin and tmax are within the range (0, 1). We need to project it to the original + // parameter range for v2. + var v2tmin = v2t.t1; + var tdiff = ( v2t.t2 - v2tmin ); + v2t.t1 = v2tmin + tmin * tdiff; + v2t.t2 = v2tmin + tmax * tdiff; + // If the new parameter range fails to converge by atleast 20% of the original range, + // possibly we have multiple intersections. We need to subdivide one of the curves. + if( (tdiff - ( v2t.t2 - v2t.t1 ))/tdiff < 0.2 ){ + return -1; + } + return 1; +} + +/** + * Calculate the convex hull for the non-paramertic bezier curve D(ti, di(t)). + * The ti is equally spaced across [0..1] — [0, 1/3, 2/3, 1] for + * di(t), [dq0, dq1, dq2, dq3] respectively. In other words our CVs for the curve are + * already sorted in the X axis in the increasing order. Calculating convex-hull is + * much easier than a set of arbitrary points. + */ +function _convexhull( dq0, dq1, dq2, dq3 ){ + var distq1 = _getSignedDist( 0.0, dq0, 1.0, dq3, 0.3333333333333333, dq1 ); + var distq2 = _getSignedDist( 0.0, dq0, 1.0, dq3, 0.6666666666666666, dq2 ); + // Check if [1/3, dq1] and [2/3, dq2] are on the same side of line [0,dq0, 1,dq3] + if( distq1 * distq2 < 0 ) { + // dq1 and dq2 lie on different sides on [0, q0, 1, q3] + // Convexhull is a quadrilateral and line [0, q0, 1, q3] is NOT part of the convexhull + // so we are pretty much done here. + Dt = [ + [ 0.0, dq0, 0.3333333333333333, dq1 ], + [ 0.3333333333333333, dq1, 1.0, dq3 ], + [ 0.6666666666666666, dq2, 0.0, dq0 ], + [ 1.0, dq3, 0.6666666666666666, dq2 ] + ]; + } else { + // dq1 and dq2 lie on the same sides on [0, q0, 1, q3] + // Convexhull can be a triangle or a quadrilateral and + // line [0, q0, 1, q3] is part of the convexhull. + // Check if the hull is a triangle or a quadrilateral + var dqmin, dqmax, dqapex1, dqapex2; + distq1 = Math.abs(distq1); + distq2 = Math.abs(distq2); + var vqa1a2x, vqa1a2y, vqa1Maxx, vqa1Maxy, vqa1Minx, vqa1Miny; + if( distq1 > distq2 ){ + dqmin = [ 0.6666666666666666, dq2 ]; + dqmax = [ 0.3333333333333333, dq1 ]; + // apex is dq3 and the other apex point is dq0 + // vector dqapex->dqapex2 or the base vector which is already part of c-hull + vqa1a2x = 1.0, vqa1a2y = dq3 - dq0; + // vector dqapex->dqmax + vqa1Maxx = 0.6666666666666666, vqa1Maxy = dq3 - dq1; + // vector dqapex->dqmin + vqa1Minx = 0.3333333333333333, vqa1Miny = dq3 - dq2; + } else { + dqmin = [ 0.3333333333333333, dq1 ]; + dqmax = [ 0.6666666666666666, dq2 ]; + // apex is dq0 in this case, and the other apex point is dq3 + // vector dqapex->dqapex2 or the base vector which is already part of c-hull + vqa1a2x = -1.0, vqa1a2y = dq0 - dq3; + // vector dqapex->dqmax + vqa1Maxx = -0.6666666666666666, vqa1Maxy = dq0 - dq2; + // vector dqapex->dqmin + vqa1Minx = -0.3333333333333333, vqa1Miny = dq0 - dq1; + } + // compare cross products of these vectors to determine, if + // point is in triangles [ dq3, dqMax, dq0 ] or [ dq0, dqMax, dq3 ] + var vcrossa1a2_a1Min = vqa1a2x * vqa1Miny - vqa1a2y * vqa1Minx; + var vcrossa1Max_a1Min = vqa1Maxx * vqa1Miny - vqa1Maxy * vqa1Minx; + if( vcrossa1Max_a1Min * vcrossa1a2_a1Min < 0 ){ + // Point [2/3, dq2] is inside the triangle and the convex hull is a triangle + Dt = [ + [ 0.0, dq0, dqmax[0], dqmax[1] ], + [ dqmax[0], dqmax[1], 1.0, dq3 ], + [ 1.0, dq3, 0.0, dq0 ] + ]; + } else { + // Convexhull is a quadrilateral and we need all lines in the correct order where + // line [0, q0, 1, q3] is part of the convex hull + Dt = [ + [ 0.0, dq0, 0.3333333333333333, dq1 ], + [ 0.3333333333333333, dq1, 0.6666666666666666, dq2 ], + [ 0.6666666666666666, dq2, 1.0, dq3 ], + [ 1.0, dq3, 0.0, dq0 ] + ]; + } + } + return Dt; +} + + +function drawFatline( v1 ) { + function signum(num) { + return ( num > 0 )? 1 : ( num < 0 )? -1 : 0; + } + var l = new Line( [v1[0], v1[1]], [v1[6], v1[7]], false ); + var p1 = new Point( v1[2], v1[3] ), p2 = new Point( v1[4], v1[5] ); + var d1 = l.getSide( p1 ) * l.getDistance( p1 ) || 0; + var d2 = l.getSide( p2 ) * l.getDistance( p2 ) || 0; + var dmin, dmax; + if( d1 * d2 > 0){ + // 3/4 * min{0, d1, d2} + dmin = 0.75 * Math.min( 0, d1, d2 ); + dmax = 0.75 * Math.max( 0, d1, d2 ); + } else { + // 4/9 * min{0, d1, d2} + dmin = 4 * Math.min( 0, d1, d2 ) / 9.0; + dmax = 4 * Math.max( 0, d1, d2 ) / 9.0; + } + var ll = new Path.Line( v1[0], v1[1], v1[6], v1[7] ); + window.__p3.push( ll ); + window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9, 0.8); + var lp1 = ll.segments[0].point; + var lp2 = ll.segments[1].point; + var pm = l.vector, pm1 = pm.rotate( signum( dmin ) * -90 ), pm2 = pm.rotate( signum( dmax ) * -90 ); + var p11 = lp1.add( pm1.normalize( Math.abs(dmin) ) ); + var p12 = lp2.add( pm1.normalize( Math.abs(dmin) ) ); + var p21 = lp1.add( pm2.normalize( Math.abs(dmax) ) ); + var p22 = lp2.add( pm2.normalize( Math.abs(dmax) ) ); + window.__p3.push( new Path.Line( p11, p12 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); + window.__p3.push( new Path.Line( p21, p22 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = new Color( 0,0,0.9); +} + +function plotD_vs_t( x, y, arr, arr2, v, dmin, dmax, tmin, tmax, yscale, tvalue ){ + yscale = yscale || 1; + new Path.Line( x, y-100, x, y+100 ).style.strokeColor = '#aaa'; + new Path.Line( x, y, x + 200, y ).style.strokeColor = '#aaa'; + + var clr = (tvalue)? '#a00' : '#00a'; + if( window.__p3 ) window.__p3.map(function(a){a.remove();}); + + window.__p3 = []; + + drawFatline( v ); + + window.__p3.push( new Path.Line( x, y + dmin * yscale, x + 200, y + dmin * yscale ) ); + window.__p3[window.__p3.length-1].style.strokeColor = '#000' + window.__p3.push( new Path.Line( x, y + dmax * yscale, x + 200, y + dmax * yscale ) ); + window.__p3[window.__p3.length-1].style.strokeColor = '#000' + window.__p3.push( new Path.Line( x + tmin * 190, y-100, x + tmin * 190, y+100 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = clr + window.__p3.push( new Path.Line( x + tmax * 190, y-100, x + tmax * 190, y+100 ) ); + window.__p3[window.__p3.length-1].style.strokeColor = clr + + for (var i = 0; i < arr.length; i++) { + window.__p3.push( new Path.Line( new Point( x + arr[i][0] * 190, y + arr[i][1] * yscale ), + new Point( x + arr[i][2] * 190, y + arr[i][3] * yscale ) ) ); + window.__p3[window.__p3.length-1].style.strokeColor = '#999'; + } + var pnt = []; + var arr2x = [ 0.0, 0.333333333, 0.6666666666, 1.0 ]; + for (var i = 0; i < arr2.length; i++) { + pnt.push( new Point( x + arr2x[i] * 190, y + arr2[i] * yscale ) ); + window.__p3.push( new Path.Circle( pnt[pnt.length-1], 2 ) ); + window.__p3[window.__p3.length-1].style.fillColor = '#000' + } + // var pth = new Path( pnt[0], pnt[1], pnt[2], pnt[3] ); + // pth.closed = true; + window.__p3.push( new Path( new Segment(pnt[0], null, pnt[1].subtract(pnt[0])), new Segment( pnt[3], pnt[2].subtract(pnt[3]), null ) ) ); + window.__p3[window.__p3.length-1].style.strokeColor = clr + view.draw(); +} + +// This is basically an "unrolled" version of #Line.getDistance() with sign +// May be a static method could be better! +var _getSignedDist = function( a1x, a1y, a2x, a2y, bx, by ){ + var vx = a2x - a1x, vy = a2y - a1y; + var m = vy / vx, b = a1y - ( m * a1x ); + return ( by - ( m * bx ) - b ) / Math.sqrt( m*m + 1 ); +}; + +/** + * Intersections between curve and line becomes rather simple here mostly + * because of paperjs Numerical class. We can rotate the curve and line so that + * the line is on X axis, and solve the implicit equations for X axis and the curve + */ +var _getCurveLineIntersection = function( v1, v2, curve1, curve2, locations, _other ){ + var i, root, point, vc = v1, vl = v2; + var other = ( _other === undefined )? Curve.isLinear( v1 ) : _other; + if( other ){ + vl = v1; + vc = v2; + } + var l1x = vl[0], l1y = vl[1], l2x = vl[6], l2y = vl[7]; + // rotate both the curve and line around l1 so that line is on x axis + var lvx = l2x - l1x, lvy = l2y - l1y; + // Angle with x axis (1, 0) + var angle = Math.atan2( -lvy, lvx ), sina = Math.sin( angle ), cosa = Math.cos( angle ); + // rotated line and curve values + // (rl1x, rl1y) = (0, 0) + var rl2x = lvx * cosa - lvy * sina, rl2y = lvy * cosa + lvx * sina; + var rvc = []; + for( i=0; i<8; i+=2 ){ + var vcx = vc[i] - l1x, vcy = vc[i+1] - l1y; + rvc.push( vcx * cosa - vcy * sina ); + rvc.push( vcy * cosa + vcx * sina ); + } + var roots = []; + Curve.solveCubic(rvc, 1, 0, roots); + i = roots.length; + while( i-- ){ + root = roots[i]; + if( root >= 0 && root <= 1 ){ + point = Curve.evaluate(rvc, root, true, 0); + // We do have a point on the infinite line. Check if it falls on the line *segment*. + if( point.x >= 0 && point.x <= rl2x ){ + // The actual intersection point + point = Curve.evaluate(vc, root, true, 0); + if( other ) root = null; + var first = locations[0], + last = locations[locations.length - 1]; + if ((!first || !point.equals(first._point)) + && (!last || !point.equals(last._point))) + locations.push( new CurveLocation( curve1, root, point, curve2 ) ); + } + } + } +}; + +var _getLineLineIntersection = function( v1, v2, curve1, curve2, locations ){ + var point = Line.intersect( + v1[0], v1[1], v1[6], v1[7], + v2[0], v2[1], v2[6], v2[7], false); + if (point) { + // Avoid duplicates when hitting segments (closed paths too) + var first = locations[0], + last = locations[locations.length - 1]; + if ((!first || !point.equals(first._point)) + && (!last || !point.equals(last._point))) + // Passing null for parameter leads to lazy determination + // of parameter values in CurveLocation#getParameter() + // only once they are requested. + locations.push(new CurveLocation(curve1, null, point, curve2)); + } +};