diff --git a/Boolean2.js b/Boolean2.js index a13379c0..a693df4c 100644 --- a/Boolean2.js +++ b/Boolean2.js @@ -1,69 +1,176 @@ -function sortIx( a, b ) { - // First sort along countours (if instanceof CompoundPath), - // then curves in decending order, - // then sort in t (paramter) in ascending order - // if( a.path.id === b.path.id ){ - // if( a.curve.index === b.curve.index ){ - return b.parameter - a.parameter; - // } else { - // return b.index - a.index; - // } - // } else { - // return a.path.id - b.path.id; - // } -} +function sortIx( a, b ) { return b.parameter - a.parameter; } function splitPath( _ixs, other ) { other = other || false; - var i, j, k, l, left, right, ixs, ix, path, crv, vals, parts; + var i, j, k, l, len, ixs, ix, path, crv, vals; var ixPoint, nuSeg; var paths = [], lastPathId = null; for (i = 0, l = _ixs.length; i < l; i++) { - ix = _ixs[i]; + ix = ( other )? _ixs[i]._ixPair : _ixs[i]; if( ix.path.id !== lastPathId ){ paths.push( ix.path ); lastPathId = ix.path.id; } if( !ix.curve._ixParams ){ix.curve._ixParams = []; } - ix = ( other )? ix._ixPair : ix; ix.curve._ixParams.push( { parameter: ix.parameter, pair: ix._ixPair } ); } + for (k = 0, len = paths.length; k < len; k++) { + path = paths[k]; + var lastNode = path.lastSegment, firstNode = path.firstSegment; + var nextNode = null, left = null, right = null, parts = null, isLinear; + var handleIn, handleOut; + while( nextNode !== firstNode){ + nextNode = ( nextNode )? nextNode.previous: lastNode; + if( nextNode.curve._ixParams ){ + ixs = nextNode.curve._ixParams; + ixs.sort( sortIx ); + crv = nextNode.curve; + isLinear = crv.isLinear(); + crv = vals = null; + for (i = 0, l = ixs.length; i < l; i++) { + ix = ixs[i]; + crv = nextNode.curve; + if( !vals ) vals = crv.getValues(); + parts = Curve.subdivide( vals, ix.parameter ); + left = parts[0]; + right = parts[1]; + handleIn = handleOut = null; + if( !isLinear ){ + crv.segment1.handleOut = new Point( left[2] - left[0], left[3] - left[1] ); + crv.segment2.handleIn = new Point( right[4] - right[6], right[5] - right[7] ); + handleIn = new Point( left[4] - ixPoint.x, left[5] - ixPoint.y ); + handleOut = new Point( right[2] - ixPoint.x, right[3] - ixPoint.y ); + } + ixPoint = new Point( right[0], right[1] ); + nuSeg = new Segment( ixPoint, handleIn, handleOut ); + nuSeg._ixPair = ix.pair; + nuSeg._ixPair._segment = nuSeg; - path = paths[0]; - var lastNode = path.lastSegment, firstNode = path.firstSegment; - var nextNode = null, len = path.segments.length * 2, count = 0; - while( nextNode !== firstNode && count < len ){ - nextNode = ( nextNode )? nextNode.previous: lastNode; - if( nextNode.curve._ixParams ){ - ixs = nextNode.curve._ixParams; - ixs.sort( sortIx ); - crv = nextNode.curve; - vals = crv.getValues(); - for (i = 0, l = ixs.length; i < l; i++) { - ix = ixs[i]; - parts = Curve.subdivide( vals, ix.parameter ); - left = parts[0]; - right = parts[1]; - - crv.segment1.handleOut = new Point( left[2] - left[0], left[3] - left[1] ); - ixPoint = new Point( right[0], right[1] ); - nuSeg = new Segment( ixPoint, - new Point( left[4] - right[0], left[5] - right[1] ), - new Point( right[2] - right[0], right[3] - right[1] ) ); - nuSeg._ixPair = ix.pair; - crv.segment2.handleIn = new Point( right[4] - right[6], right[5] - right[7] ); - - path.insert( nextNode.index + 1, nuSeg ); - // nextNode = nuSeg; - for (j = i + 1; j < l; j++) { - ixs[j].parameter = ixs[j].parameter / ix.parameter; + path.insert( nextNode.index + 1, nuSeg ); + // nextNode = nuSeg; + for (j = i + 1; j < l; j++) { + ixs[j].parameter = ixs[j].parameter / ix.parameter; + } + vals = left; } - vals = left; } } - count++; } } +/** + * To deal with a HTML canvas requirement where CompoundPaths' child contours + * has to be of different winding direction for correctly filling holes. + * But if some individual countours are disjoint, i.e. islands, we have to + * reorient them so that + * the holes have opposit winding direction ( already handled by paperjs ) + * islands has to have same winding direction ( as the first child of the path ) + * + * Does NOT handle selfIntersecting CompoundPaths. + * + * @param {CompoundPath} path - Input CompoundPath, Note: This path could be modified if need be. + * @return {boolean} the winding direction of the base contour( true if clockwise ) + */ + function reorientCompoundPath( path ){ + if( !(path instanceof CompoundPath) ){ return path.clockwise; } + var children = path.children, len = children.length, baseWinding; + var bounds = new Array( len ); + var tmparray = new Array( len ); + baseWinding = children[0].clockwise; + // Omit the first path + for (i = 0; i < len; i++) { + bounds[i] = children[i].bounds; + tmparray[i] = 0; + } + for (i = 0; i < len; i++) { + var p1 = children[i]; + for (j = 0; j < len; j++) { + var p2 = children[j]; + if( i !== j && bounds[i].contains( bounds[j] ) ){ + tmparray[j]++; + } + } + } + for (i = 1; i < len; i++) { + if ( tmparray[i] % 2 === 0 ) { + children[i].clockwise = baseWinding; + } + } + return baseWinding; +} + +function computeBoolean( _path1, _path2, operator ){ + var path1Clockwise = reorientCompoundPath( _path1 ); + var path2Clockwise = reorientCompoundPath( _path2 ); + + var ixs = _path1.getIntersections( _path2 ); + var path1Id = _path1.id; + var path2Id = _path2.id; + splitPath( ixs ); + splitPath( ixs, true ); + + var i, j, len, path, crv; + var paths; + if( _path1 instanceof CompoundPath ){ + paths = _path1.children; + } else { + paths = [ _path1 ]; + } + if( _path2 instanceof CompoundPath ){ + paths = paths.concat( _path2.children ); + } else { + paths.push( _path2 ); + } + + // step 1: discard invalid links according to the boolean operator + for (i = 0, len = paths.length; i < len; i++) { + path = paths[i]; + var thisId = ( path.parent instanceof CompoundPath )? path.parent.id : path.id; + var thisWinding = path.clockwise; + var lastNode = path.lastSegment, firstNode = path.firstSegment; + var nextNode = null, midPoint, insidePath1, insidePath2; + while( nextNode !== firstNode){ + nextNode = ( nextNode )? nextNode.previous: lastNode; + crv = nextNode.curve; + midPoint = crv.getPoint( 0.5 ); + if( thisId !== path1Id ){ + contains = _path1.contains( midPoint ); + insidePath1 = (thisWinding === path1Clockwise)? contains : + contains && !testOnCurve( _path1, midPoint ); + } + if( thisId !== path2Id ){ + contains = _path2.contains( midPoint ); + insidePath2 = (thisWinding === path2Clockwise)? contains : + contains && !testOnCurve( _path2, midPoint ); + } + if( !operator( thisId === path1Id, insidePath1, insidePath2 ) ){ + crv._INVALID = true; + } + } + } +} + +function testOnCurve( path, point ){ + var res = 0; + var crv = path.getCurves(); + var i = 0; + var bounds = path._bounds; + if( bounds && bounds.contains( point ) ){ + for( i = 0; i < crv.length && !res; i++ ){ + var crvi = crv[i]; + if( crvi.bounds.contains( point ) && crvi.getParameterOf( point ) ){ + res = 1; + } + } + } + return res; +} + +function unite( path1, path2 ){ + var unionOp = function( isPath1, isInsidePath1, isInsidePath2 ){ + return ( isInsidePath1 || isInsidePath2 )? false : true; + }; + return computeBoolean( path1, path2, unionOp ); +}