Add 'boolean/' from commit '26c48786c8c1dd9f99f8c0abc24700197c443539'

git-subtree-dir: boolean
git-subtree-mainline: 7d54bb36b1
git-subtree-split: 26c48786c8
This commit is contained in:
Jürg Lehni 2013-05-03 15:23:17 -07:00
commit 639ff7f03f
10 changed files with 2387 additions and 0 deletions

3
boolean/.exrc Normal file
View file

@ -0,0 +1,3 @@
set tags+=./tags

1
boolean/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
tags

696
boolean/Boolean.js Normal file
View file

@ -0,0 +1,696 @@
/*!
*
* Vector boolean operations on paperjs objects
* This is mostly written for clarity (I hope it is clear) and compatibility,
* not optimised for performance, and has to be tested heavily for stability.
* (Looking up to Java's Area path boolean algorithms for stability,
* but the code is too complex mainly because the operations are stored and
* enumerable, such as quadraticCurveTo, cubicCurveTo etc.; and is largely
* undocumented to directly adapt from)
*
* Supported
* - paperjs Path and CompoundPath objects
* - Boolean Union
* - Boolean Intersection
* - Boolean Subtraction
* - Resolving a self-intersecting Path
*
* Not supported yet ( which I would like to see supported )
* - Boolean operations on self-intersecting Paths, these has to be resolved first
* - Paths are clones of each other that ovelap exactly on top of each other!
*
* ------
* Harikrishnan Gopalakrishnan
* http://hkrish.com/playground/paperjs/booleanStudy.html
*
* ------
* Paperjs
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
* http://paperjs.org/license/
*
*/
/**
* BooleanOps defines the boolean operator functions to use.
* A boolean operator is a function f( link:Link, isInsidePath1:Boolean, isInsidePath2:Boolean ) :
* should return a Boolean value indicating whether to keep the link or not.
* return true - keep the path
* return false - discard the path
*/
var BooleanOps = {
Union: function( lnk, isInsidePath1, isInsidePath2 ){
if( isInsidePath1 || isInsidePath2 ){
return false;
}
return true;
},
Intersection: function( lnk, isInsidePath1, isInsidePath2 ){
if( !isInsidePath1 && !isInsidePath2 ){
return false;
}
return true;
},
// path1 - path2
Subtraction: function( lnk, isInsidePath1, isInsidePath2 ){
var lnkid = lnk.id;
if( lnkid === 1 && isInsidePath2 ){
return false;
} else if( lnkid === 2 && !isInsidePath1 ){
return false;
}
return true;
}
};
/**
* The datastructure for boolean computation:
* Graph - List of Links
* Link - Connects 2 Nodes, represents a Curve
* Node - Connects 2 Links, represents a Segment
*/
var NORMAL_NODE = 1;
var INTERSECTION_NODE = 2;
var IntersectionID = 1;
var UNIQUE_ID = 1;
/**
* Nodes in the graph are analogous to Segment objects
* with additional linkage information to track intersections etc.
* (enough to do a complete graph traversal)
* @param {Point} _point
* @param {Point} _handleIn
* @param {Point} _handleOut
* @param {Any} _id
*/
function Node( _point, _handleIn, _handleOut, _id, isBaseContour ){
this.id = _id;
this.isBaseContour = isBaseContour;
this.type = NORMAL_NODE;
this.point = _point;
this.handleIn = _handleIn; // handleIn
this.handleOut = _handleOut; // handleOut
this.linkIn = null; // aka linkIn
this.linkOut = null; // linkOut
this.uniqueID = ++UNIQUE_ID;
// In case of an intersection this will be a merged node.
// And we need space to save the "other Node's" parameters before merging.
this.idB = null;
this.isBaseContourB = false;
// this.pointB = this.point; // point should be the same
this.handleBIn = null;
this.handleBOut = null;
this.linkBIn = null;
this.linkBOut = null;
this._segment = null;
this.getSegment = function( recalculate ){
if( this.type === INTERSECTION_NODE && recalculate ){
// point this.linkIn and this.linkOut to those active ones
// also point this.handleIn and this.handleOut to correct in and out handles
// If a link is null, make sure the corresponding handle is also null
this.handleIn = (this.linkIn)? this.handleIn : null;
this.handleOut = (this.linkOut)? this.handleOut : null;
this.handleBIn = (this.linkBIn)? this.handleBIn : null;
this.handleBOut = (this.linkBOut)? this.handleBOut : null;
// Select the valid links
this.linkIn = this.linkIn || this.linkBIn; // linkIn
this.linkOut = this.linkOut || this.linkBOut; // linkOut
// Also update the references in links to point to "this" Node
if( !this.linkIn || !this.linkOut ){
// markPoint( this.point, this._intersectionID );
throw { name: 'Boolean Error', message: 'No matching link found at ixID: ' +
this._intersectionID + " point: " + this.point.toString() };
}
this.linkIn.nodeOut = this; // linkIn.nodeEnd
this.linkOut.nodeIn = this; // linkOut.nodeStart
this.handleIn = this.handleIn || this.handleBIn;
this.handleOut = this.handleOut || this.handleBOut;
this.isBaseContour = this.isBaseContour | this.isBaseContourB;
}
this._segment = this._segment || new Segment( this.point, this.handleIn, this.handleOut );
return this._segment;
};
}
/**
* Links in the graph are analogous to CUrve objects
* @param {Node} _nodeIn
* @param {Node} _nodeOut
* @param {Any} _id
*/
function Link( _nodeIn, _nodeOut, _id, isBaseContour, _winding ) {
this.id = _id;
this.isBaseContour = isBaseContour;
this.winding = _winding;
this.nodeIn = _nodeIn; // nodeStart
this.nodeOut = _nodeOut; // nodeEnd
this.nodeIn.linkOut = this; // nodeStart.linkOut
this.nodeOut.linkIn = this; // nodeEnd.linkIn
this._curve = null;
this.intersections = [];
// for reusing the paperjs function we need to (temperorily) build a Curve object from this Link
// for performance reasons we cache it.
this.getCurve = function() {
this._curve = this._curve || new Curve( this.nodeIn.getSegment(), this.nodeOut.getSegment() );
return this._curve;
};
}
/**
* makes a graph. Only works on paths, for compound paths we need to
* make graphs for each of the child paths and merge them.
* @param {Path} path
* @param {Integer} id
* @return {Array} Links
*/
function makeGraph( path, id, isBaseContour ){
var graph = [];
var segs = path.segments, prevNode = null, firstNode = null, nuLink, nuNode,
winding = path.clockwise;
for( i = 0, l = segs.length; i < l; i++ ){
// var nuSeg = segs[i].clone();
var nuSeg = segs[i];
nuNode = new Node( nuSeg.point, nuSeg.handleIn, nuSeg.handleOut, id, isBaseContour );
if( prevNode ) {
nuLink = new Link( prevNode, nuNode, id, isBaseContour, winding );
graph.push( nuLink );
}
prevNode = nuNode;
if( !firstNode ){
firstNode = nuNode;
}
}
// the path is closed
nuLink = new Link( prevNode, firstNode, id, isBaseContour, winding );
graph.push( nuLink );
return graph;
}
/**
* Calculates the Union of two paths
* Boolean API.
* @param {Path} path1
* @param {Path} path2
* @return {CompoundPath} union of path1 & path2
*/
function boolUnion( path1, path2 ){
return computeBoolean( path1, path2, BooleanOps.Union );
}
/**
* Calculates the Intersection between two paths
* Boolean API.
* @param {Path} path1
* @param {Path} path2
* @return {CompoundPath} Intersection of path1 & path2
*/
function boolIntersection( path1, path2 ){
return computeBoolean( path1, path2, BooleanOps.Intersection );
}
/**
* Calculates path1path2
* Boolean API.
* @param {Path} path1
* @param {Path} path2
* @return {CompoundPath} path1 <minus> path2
*/
function boolSubtract( path1, path2 ){
return computeBoolean( path1, path2, BooleanOps.Subtraction );
}
/**
* 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;
}
/**
* Actual function that computes the boolean
* @param {Path} _path1 (cannot be self-intersecting at the moment)
* @param {Path} _path2 (cannot be self-intersecting at the moment)
* @param {BooleanOps type} operator
* @return {CompoundPath} boolean result
*/
function computeBoolean( _path1, _path2, operator ){
IntersectionID = 1;
UNIQUE_ID = 1;
// We work on duplicate paths since the algorithm may modify the original paths
var path1 = _path1.clone();
var path2 = _path2.clone();
var i, j, k, l, lnk, crv, node, nuNode, leftLink, rightLink;
var path1Clockwise = true, path2Clockwise = true;
// If one of the operands is empty, resolve self-intersections on the second operand
var childCount1 = (_path1 instanceof CompoundPath)? _path1.children.length : _path1.curves.length;
var childCount2 = (_path2 instanceof CompoundPath)? _path2.children.length : _path2.curves.length;
var resolveSelfIntersections = !childCount1 | !childCount2;
// Reorient the compound paths, i.e. make all the islands wind in the same direction
// and holes in the opposit direction.
// Do this only if we are not resolving selfIntersections:
// Resolving self-intersections work on compound paths, but, we might get different results!
if( !resolveSelfIntersections ){
path1Clockwise = reorientCompoundPath( path1 );
path2Clockwise = reorientCompoundPath( path2 );
}
// Cache the bounding rectangle of paths
// so we can make the test for containment quite a bit faster
path1._bounds = (childCount1)? path1.bounds : null;
path2._bounds = (childCount2)? path2.bounds : null;
// Prepare the graphs. Graphs are list of Links that retains
// full connectivity information. The order of links in a graph is not important
// That allows us to sort and merge graphs and 'splice' links with their splits easily.
// Also, this is the place to resolve self-intersecting paths
var graph = [], path1Children, path2Children, base;
if( path1 instanceof CompoundPath ){
path1Children = path1.children;
for (i = 0, base = true, l = path1Children.length; i < l; i++, base = false) {
path1Children[i].closed = true;
graph = graph.concat( makeGraph( path1Children[i], 1, base ) );
}
} else {
path1.closed = true;
path1Clockwise = path1.clockwise;
// path1.clockwise = true;
graph = graph.concat( makeGraph( path1, 1, true ) );
}
// if operator === BooleanOps.Subtraction, then reverse path2
// so that the nodes and links will link correctly
var reverse = ( operator === BooleanOps.Subtraction )? true: false;
path2Clockwise = (reverse)? !path2Clockwise : path2Clockwise;
if( path2 instanceof CompoundPath ){
path2Children = path2.children;
for (i = 0, base = true, l = path2Children.length; i < l; i++, base = false) {
path2Children[i].closed = true;
if( reverse ){path2Children[i].reverse(); }
graph = graph.concat( makeGraph( path2Children[i], 2, base ) );
}
} else {
path2.closed = true;
// path2.clockwise = true;
if( reverse ){ path2.reverse(); }
path2Clockwise = path2.clockwise;
graph = graph.concat( makeGraph( path2, 2, true ) );
}
window.g = graph;
// Sort function to sort intersections according to the 'parameter'(t) in a link (curve)
function ixSort( a, b ){ return a.parameter - b.parameter; }
/*
* Pass 1:
* Calculate the intersections for all graphs
*/
var ix, loc, loc2, ixCount = 0;
for ( i = graph.length - 1; i >= 0; i--) {
var c1 = graph[i].getCurve();
var v1 = c1.getValues();
for ( j = i -1; j >= 0; j-- ) {
if( !resolveSelfIntersections && graph[j].id === graph[i].id ){ continue; }
var c2 = graph[j].getCurve();
var v2 = c2.getValues();
loc = [];
Curve.getIntersections( v1, v2, c1, loc );
if( loc.length ){
for (k = 0, l=loc.length; k<l; k++) {
// Ignore segment overlaps if both curve are part of same contour
// This is a degenerate case while resolving self-intersections,
// after paperjs rev#8d35d92
if( graph[j].id === graph[i].id &&
( loc[k].parameter === 0.0 || loc[k].parameter === 1.0) ) {
continue;
}
// markPoint( loc[k].point, loc[k]._id )
// console.log( loc[k].point, loc[k]._id )
graph[i].intersections.push( loc[k] );
loc2 = new CurveLocation( c2, null, loc[k].point );
loc2._id = loc[k]._id;
graph[j].intersections.push( loc2 );
loc[k]._ixpair = loc2;
loc2._ixpair = loc[k];
++ixCount;
}
}
}
}
/*
* Avoid duplicate intersections when a curve that belongs to one contour
* passes through a segment on another contour
*/
len = graph.length;
while( len-- ){
ix = graph[len].intersections;
for (i =0, l=ix.length; i<l; i++) {
// In case of an over lap over the first segment on a link we
// look for duplicates and mark them INVALID
loc = ix[i];
if ( loc.parameter === 0.0 ){
j = graph.length;
while( j-- ) {
var ix2 = graph[j].intersections;
k = ix2.length;
while ( k-- ) {
loc2 = ix2[k];
if( !loc2.INVALID && loc._id !== loc2._id && loc2.parameter !== 1.0 &&
loc2.point.equals( loc.point ) ) {
loc2.INVALID = loc2._ixpair.INVALID = true;
}
}
}
} // if( loc.parameter === 0.0 )
}
}
/*
* Pass 2:
* Walk the graph, sort the intersections on each individual link.
* for each link that intersects with another one, replace it with new split links.
*/
var ixPoint, ixHandleI, ixHandleOut, param, isLinear, parts, left, right;
var values, nix, niy,nox, noy, niho, nohi, nihox, nihoy, nohix, nohiy;
for ( i = graph.length - 1; i >= 0; i--) {
if( graph[i].intersections.length ){
ix = graph[i].intersections;
// Sort the intersections if there is more than one
if( graph[i].intersections.length > 1 ){ ix.sort( ixSort ); }
// Remove the graph link, this link has to be split and replaced with the splits
lnk = graph.splice( i, 1 )[0];
nix = lnk.nodeIn.point.x; niy = lnk.nodeIn.point.y;
nox = lnk.nodeOut.point.x; noy = lnk.nodeOut.point.y;
niho = lnk.nodeIn.handleOut; nohi = lnk.nodeOut.handleIn;
nihox = nihoy = nohix = nohiy = 0;
isLinear = true;
if( niho ){ nihox = niho.x; nihoy = niho.y; isLinear = false; }
if( nohi ){ nohix = nohi.x; nohiy = nohi.y; isLinear = false; }
values = [ nix, niy, nihox + nix, nihoy + niy,
nohix + nox, nohiy + noy, nox, noy ];
for (j =0, l=ix.length; j<l && lnk; j++) {
if( ix[j].INVALID ){ continue; }
param = ix[j].parameter;
// param = crv.getParameterOf( ix[j].point );
if( param === 0.0 || param === 1.0) {
// Intersection falls on an existing node
// there is no need to split the link
nuNode = ( param === 0.0 )? lnk.nodeIn : lnk.nodeOut;
nuNode.type = INTERSECTION_NODE;
nuNode._intersectionID = ix[j]._id;
if( param === 1.0 ){
leftLink = null;
rightLink = lnk;
} else {
leftLink = lnk;
rightLink = null;
}
} else {
parts = Curve.subdivide(values, param);
left = parts[0];
right = parts[1];
// Make new link and convert handles from absolute to relative
ixPoint = new Point( left[6], left[7] );
if( !isLinear ){
ixHandleIn = new Point(left[4] - ixPoint.x, left[5] - ixPoint.y);
ixHandleOut = new Point(right[2] - ixPoint.x, right[3] - ixPoint.y);
} else {
ixHandleIn = ixHandleOut = null;
right[2] = right[0];
right[3] = right[1];
}
nuNode = new Node( ixPoint, ixHandleIn, ixHandleOut, lnk.id, lnk.isBaseContour );
nuNode.type = INTERSECTION_NODE;
nuNode._intersectionID = ix[j]._id;
// clear the cached Segment on original end nodes and Update their handles
lnk.nodeIn._segment = null;
lnk.nodeOut._segment = null;
if( !isLinear ){
var tmppnt = lnk.nodeIn.point;
lnk.nodeIn.handleOut = new Point( left[2] - tmppnt.x, left[3] - tmppnt.y );
tmppnt = lnk.nodeOut.point;
lnk.nodeOut.handleIn = new Point( right[4] - tmppnt.x, right[5] - tmppnt.y );
}
// Make new links after the split
leftLink = new Link( lnk.nodeIn, nuNode, lnk.id, lnk.isBaseContour, lnk.winding );
rightLink = new Link( nuNode, lnk.nodeOut, lnk.id, lnk.isBaseContour, lnk.winding );
values = right;
}
// Add the first split link back to the graph, since we sorted the intersections
// already, this link should contain no more intersections to the left.
if( leftLink ){
graph.splice( i, 0, leftLink );
}
// continue with the second split link, to see if
// there are more intersections to deal with
lnk = rightLink;
// Interpolate the rest of the parameters
if( lnk ) {
var one_minus_param = (1.0 - param);
for (k =j + 1, l=ix.length; k<l; k++) {
ix[k]._parameter = ( ix[k].parameter - param ) / one_minus_param;
}
}
}
// Add the last split link back to the graph
if( lnk ){
graph.splice( i, 0, lnk );
}
}
}
/**
* Pass 3:
* Merge matching intersection Node Pairs (type is INTERSECTION_NODE &&
* a._intersectionID == b._intersectionID )
*
* Mark each Link(Curve) according to whether it is
* case 1. inside Path1 ( and only Path1 )
* 2. inside Path2 ( and only Path2 )
* 3. outside (normal case)
*
* Take a test function "operator" which will discard links
* according to the above
* * Union -> discard cases 1 and 2
* * Intersection -> discard case 3
* * Path1-Path2 -> discard cases 2, 3[Path2]
*/
// step 1: discard invalid links according to the boolean operator
for ( i = graph.length - 1; i >= 0; i-- ) {
var insidePath1 = false, insidePath2 = false, contains;
lnk = graph[i];
// if( lnk.SKIP_OPERATOR ) { continue; }
if( !lnk.INVALID ) {
crv = lnk.getCurve();
// var midPoint = new Point(lnk.nodeIn.point);
var midPoint = crv.getPoint( 0.5 );
// If on a base curve, consider points on the curve and inside,
// if not —for example a hole, points on the curve falls outside
if( lnk.id !== 1 ){
contains = path1.contains( midPoint );
insidePath1 = (lnk.winding === path1Clockwise)? contains :
contains && !testOnCurve( path1, midPoint );
}
if( lnk.id !== 2 ){
contains = path2.contains( midPoint );
insidePath2 = (lnk.winding === path2Clockwise)? contains :
contains && !testOnCurve( path2, midPoint );
}
}
if( lnk.INVALID || !operator( lnk, insidePath1, insidePath2 ) ){
// lnk = graph.splice( i, 1 )[0];
lnk.INVALID = true;
lnk.nodeIn.linkOut = null;
lnk.nodeOut.linkIn = null;
}
}
// step 2: Match nodes according to their _intersectionID and merge them together
var len = graph.length;
while( len-- ){
node = graph[len].nodeIn;
if( node.type === INTERSECTION_NODE ){
var otherNode = null;
for (i = len - 1; i >= 0; i--) {
var tmpnode = graph[i].nodeIn;
if( tmpnode._intersectionID === node._intersectionID &&
tmpnode.uniqueID !== node.uniqueID ) {
otherNode = tmpnode;
break;
}
}
if( otherNode ) {
//Check if it is a self-intersecting Node
if( node.id === otherNode.id ){
// Swap the outgoing links, this will resolve a knot and create two paths,
// the portion of the original path on one side of a self crossing is counter-clockwise,
// so one of the resulting paths will also be counter-clockwise
var tmp = otherNode.linkOut;
otherNode.linkOut = node.linkOut;
node.linkOut = tmp;
tmp = otherNode.handleOut;
otherNode.handleOut = node.handleOut;
node.handleOut = tmp;
node.type = otherNode.type = NORMAL_NODE;
node._intersectionID = null;
node._segment = otherNode._segment = null;
} else {
// Merge the nodes together, by adding this node's information to the other node
otherNode.idB = node.id;
otherNode.isBaseContourB = node.isBaseContour;
otherNode.handleBIn = node.handleIn;
otherNode.handleBOut = node.handleOut;
otherNode.linkBIn = node.linkIn;
otherNode.linkBOut = node.linkOut;
otherNode._segment = null;
if( node.linkIn ){ node.linkIn.nodeOut = otherNode; }
if( node.linkOut ){ node.linkOut.nodeIn = otherNode; }
// Clear this node's intersectionID, so that we won't iterate over it again
node._intersectionID = null;
}
}
}
}
// Final step: Retrieve the resulting paths from the graph
var boolResult = new CompoundPath();
var firstNode = true, nextNode, foundBasePath = false;
while( firstNode ){
firstNode = nextNode = null;
len = graph.length;
while( len-- ){
lnk = graph[len];
if( !lnk.INVALID && !lnk.nodeIn.visited && !firstNode ){
if( !foundBasePath && lnk.isBaseContour ){
firstNode = lnk.nodeIn;
foundBasePath = true;
break;
} else if(foundBasePath){
firstNode = lnk.nodeIn;
break;
}
}
}
if( firstNode ){
var path = new Path();
path.add( firstNode.getSegment( true ) );
firstNode.visited = true;
nextNode = firstNode.linkOut.nodeOut;
var linkCount = graph.length + 1;
while( firstNode.uniqueID !== nextNode.uniqueID && linkCount-- ){
path.add( nextNode.getSegment( true ) );
nextNode.visited = true;
if( !nextNode.linkOut ){
throw { name: 'Boolean Error', message: 'No link found at node id: ' + nextNode.id };
}
nextNode = nextNode.linkOut.nodeOut;
}
path.closed = true;
// path.clockwise = true;
if( path.segments.length > 1 && linkCount >= 0 ){ // avoid stray segments and incomplete paths
if( path.segments.length === 2 ){
}
if( path.segments.length > 2 || !path.curves[0].isLinear() ){
boolResult.addChild( path );
}
}
}
}
boolResult = boolResult.reduce();
// Remove the paths we duplicated
path1.remove();
path2.remove();
return boolResult;
}
// var getLineIntersections = function(v1, v2, curve, locations) {
// var result, a1x, a2x, b1x, b2x, a1y, a2y, b1y, b2y;
// a1x = v1[0]; a1y = v1[1];
// a2x = v1[6]; a2y = v1[7];
// b1x = v2[0]; b1y = v2[1];
// b2x = v2[6]; b2y = v2[7];
// var ua_t = (b2x - b1x) * (a1y - b1y) - (b2y - b1y) * (a1x - b1x);
// var ub_t = (a2x - a1x) * (a1y - b1y) - (a2y - a1y) * (a1x - b1x);
// var u_b = (b2y - b1y) * (a2x - a1x) - (b2x - b1x) * (a2y - a1y);
// if ( u_b !== 0 ) {
// var ua = ua_t / u_b;
// var ub = ub_t / u_b;
// if ( 0 <= ua && ua <= 1 && 0 <= ub && ub <= 1 ) {
// locations.push( new CurveLocation(curve, null, new Point(a1x + ua * (a2x - a1x), a1y + ua * (a2y - a1y))) );
// }
// }
// };
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;
}

332
boolean/Boolean2.js Normal file
View file

@ -0,0 +1,332 @@
function sortIx( a, b ) { return b.parameter - a.parameter; }
function splitPath( _ixs, other ) {
other = other || false;
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 = ( other )? _ixs[i].getIntersection() : _ixs[i];
if( !paths[ix.path.id] ){
paths[ix.path.id] = ix.path;
}
if( !ix.curve._ixParams ){ix.curve._ixParams = []; }
ix.curve._ixParams.push( { parameter: ix.parameter, pair: ix.getIntersection() } );
}
for (k in paths) {
if( !paths.hasOwnProperty( k ) ){ continue; }
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.getCurve();
isLinear = crv.isLinear();
crv = vals = null;
for (i = 0, l = ixs.length; i < l; i++) {
ix = ixs[i];
crv = nextNode.getCurve();
if( !vals ) vals = crv.getValues();
if( ix.parameter === 0.0 || ix.parameter === 1.0 ){
// Intersection is on an existing node
// no need to create a new segment,
// we just link the corresponding intersections together
nuSeg = ( ix.parameter === 0.0 )? crv.segment1 : crv.segment2;
nuSeg._ixPair = ix.pair;
nuSeg._ixPair._segment = nuSeg;
} else {
parts = Curve.subdivide( vals, ix.parameter );
left = parts[0];
right = parts[1];
handleIn = handleOut = null;
ixPoint = new Point( right[0], right[1] );
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 );
}
nuSeg = new Segment( ixPoint, handleIn, handleOut );
nuSeg._ixPair = ix.pair;
nuSeg._ixPair._segment = nuSeg;
path.insert( nextNode.index + 1, nuSeg );
}
for (j = i + 1; j < l; j++) {
ixs[j].parameter = ixs[j].parameter / ix.parameter;
}
vals = left;
}
}
}
}
}
/**
* 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) ){
path.closed = true;
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++) {
children[i].closed = true;
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 reversePath( path ){
var baseWinding;
if( path instanceof CompoundPath ){
var children = path.children, i, len;
for (i = 0, len = children.length; i < len; i++) {
children[i].reverse();
children[i]._curves = null;
}
baseWinding = children[0].clockwise;
} else {
path.reverse();
baseWinding = path.clockwise;
path._curves = null;
}
return baseWinding;
}
function computeBoolean( path1, path2, operator, _splitCache ){
var _path1, _path2, path1Clockwise, path2Clockwise;
var ixs, path1Id, path2Id;
// We do not modify the operands themselves
// The result might not belong to the same type
// i.e. subtraction( A:Path, B:Path ):CompoundPath etc.
_path1 = path1.clone();
_path2 = path2.clone();
_path1.style = _path2.style = null;
_path1.selected = _path2.selected = false;
path1Clockwise = reorientCompoundPath( _path1 );
path2Clockwise = reorientCompoundPath( _path2 );
path1Id = _path1.id;
path2Id = _path2.id;
// Calculate all the intersections
ixs = ( _splitCache && _splitCache.intersections )?
_splitCache.intersections : _path1.getIntersections( _path2 );
// if we have a empty _splitCache object as an operand,
// skip calculating boolean and cache the intersections
if( _splitCache && !_splitCache.intersections ){
_splitCache.intersections = ixs;
return;
}
splitPath( ixs );
splitPath( ixs, true );
path1Id = _path1.id;
path2Id = _path2.id;
// Do operator specific calculations before we begin
if( operator.name === "subtraction" ) {
path2Clockwise = reversePath( _path2 );
}
var i, j, len, path, crv;
var paths = [];
if( _path1 instanceof CompoundPath ){
paths = paths.concat( _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
var lastNode, firstNode, nextNode, midPoint, insidePath1, insidePath2;
var thisId, thisWinding, contains, subtractionOp = (operator.name === 'subtraction');
for (i = 0, len = paths.length; i < len; i++) {
insidePath1 = insidePath2 = false;
path = paths[i];
thisId = ( path.parent instanceof CompoundPath )? path.parent.id : path.id;
thisWinding = path.clockwise;
lastNode = path.lastSegment;
firstNode = path.firstSegment;
nextNode = null;
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 || subtractionOp )? 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;
// markPoint( midPoint, '+' );
}
}
}
// Final step: Retrieve the resulting paths from the graph
var boolResult = new CompoundPath();
var node, nuNode, nuPath, nodeList = [], handle;
for (i = 0, len = paths.length; i < len; i++) {
nodeList = nodeList.concat( paths[i].segments );
}
for (i = 0, len = nodeList.length; i < len; i++) {
node = nodeList[i];
if( node.curve._INVALID || node._visited ){ continue; }
path = node.path;
thisId = ( path.parent instanceof CompoundPath )? path.parent.id : path.id;
thisWinding = path.clockwise;
nuPath = new Path();
firstNode = null;
firstNode_ix = null;
if( node.previous.curve._INVALID ) {
node.handleIn = ( node._ixPair )?
node._ixPair.getIntersection()._segment.handleIn : [ 0, 0 ];
}
while( node && !node._visited && ( node !== firstNode && node !== firstNode_ix ) ){
node._visited = true;
firstNode = ( firstNode )? firstNode: node;
firstNode_ix = ( !firstNode_ix && firstNode._ixPair )?
firstNode._ixPair.getIntersection()._segment: firstNode_ix;
// node._ixPair is this node's intersection CurveLocation object
// node._ixPair.getIntersection() is the other CurveLocation object this node intersects with
nextNode = ( node._ixPair && node.curve._INVALID )? node._ixPair.getIntersection()._segment : node;
if( node._ixPair ) {
nextNode._visited = true;
nuNode = new Segment( node.point, node.handleIn, nextNode.handleOut );
nuPath.add( nuNode );
node = nextNode;
path = node.path;
thisWinding = path.clockwise;
} else {
nuPath.add( node );
}
node = node.next;
}
if( nuPath.segments.length > 1 ) {
// avoid stray segments and incomplete paths
if( nuPath.segments.length > 2 || !nuPath.curves[0].isLinear() ){
nuPath.closed = true;
boolResult.addChild( nuPath, true );
}
}
}
// Delete the proxies
_path1.remove();
_path2.remove();
// And then, we are done.
return boolResult.reduce();
}
// Bottleneck no: 2
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;
}
/**
* A boolean operator is a binary operator function of the form
* f( isPath1:boolean, isInsidePath1:Boolean, isInsidePath2:Boolean ) :Boolean
*
* Boolean operator determines whether a curve segment in the operands is part
* of the boolean result, and will be called for each curve segment in the graph after
* all the intersections between the operands are calculated and curves in the operands
* are split at intersections.
*
* These functions should have a name ( "union", "subtraction" etc. below ), if we need to
* do operator specific operations on paths inside the computeBoolean function.
* for example: if the name of the operator is "subtraction" then we need to reverse the second
* operand. Subtraction is neither associative nor commutative.
*
* The boolean operator should return a Boolean value indicating whether to keep the curve or not.
* return true - keep the curve
* return false - discard the curve
*/
function unite( path1, path2, _cache ){
var unionOp = function union( isPath1, isInsidePath1, isInsidePath2 ){
return ( isInsidePath1 || isInsidePath2 )? false : true;
};
return computeBoolean( path1, path2, unionOp, _cache );
}
function intersect( path1, path2, _cache ){
var intersectionOp = function intersection( isPath1, isInsidePath1, isInsidePath2 ){
return ( !isInsidePath1 && !isInsidePath2 )? false : true;
};
return computeBoolean( path1, path2, intersectionOp, _cache );
}
function subtract( path1, path2, _cache ){
var subtractionOp = function subtraction( isPath1, isInsidePath1, isInsidePath2 ){
return ( (isPath1 && isInsidePath2) || (!isPath1 && !isInsidePath1) )? false : true;
};
return computeBoolean( path1, path2, subtractionOp, _cache );
}
// a.k.a. eXclusiveOR
function exclude( path1, path2 ){
var res1 = subtract( path1, path2 );
var res2 = subtract( path2, path1 );
var res = new Group( [res1, res2] );
return res;
}
function divide( path1, path2 ){
var res1 = subtract( path1, path2 );
var res2 = intersect( path1, path2 );
var res = new Group( [res1, res2] );
return res;
}

9
boolean/COPYING Normal file
View file

@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2013 Harikrishnan Gopalakrishnan
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

31
boolean/README.md Normal file
View file

@ -0,0 +1,31 @@
Vector boolean operations on paperjs objects.
This is mostly written for clarity (I hope it is clear) and compatibility,
not optimised for performance, and has to be tested heavily for stability.
(Looking up to Java's Area path boolean algorithms for stability,
but the code is too complex —mainly because the operations are stored and
enumerable, such as quadraticCurveTo, cubicCurveTo etc.; and is largely
undocumented to directly adapt from)
Supported
- paperjs Path and CompoundPath objects
- Boolean Union
- Boolean Intersection
- Boolean Subtraction
- Resolving a self-intersecting Path
Not supported yet ( which I would like to see supported )
- Boolean operations between self-intersecting Paths
- Paths are clones of each other that ovelap exactly on top of each other!
This is meant to be integrated into the paperjs library in the near future.
------
Harikrishnan Gopalakrishnan
http://hkrish.com/playground/paperjs/booleanStudy.html
------
Paperjs
Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
http://paperjs.org/license/

457
boolean/booleanStudy.html Normal file
View file

@ -0,0 +1,457 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boolean Study</title>
<script type="text/javascript" src="../dist/paper.js"></script>
<script type="text/javascript" src="Boolean2.js"></script>
<script type="text/javascript" src="booleanTests.js"></script>
<style>
body { height: 100%; overflow: auto; }
#container { display: block; width: 1000px; margin: 0 auto 50px; }
h1, h3 { font-family: 'Helvetica Neue'; font-weight: 300; margin: 50px 0 20px; }
footer{display: block; width: 1000px; height: 100px; margin: 30px auto; color: #999; }
footer p { font-family: 'Helvetica Neue'; font-style: italic; font-weight: 300; }
canvas { cursor: crosshair; width: 100%; height: 440px; margin: 5px 0;}
.error { color: #a00; } .hide{ display: none; }
</style>
</head>
<body>
<div id="container">
<h1>paperjs - Boolean Tests</h1>
<button id="testStart" value="Start tests" onClick="runTests();">Start tests</button>
</div>
<footer>
<p>Vector boolean operations on paperjs objects. <br />
Still under development, mostly written for clarity and compatibility,
not optimised for performance, and has to be tested heavily.</p>
<p>--<br />
hari</p>
</footer>
<svg class="hide" version="1.1" id="glyphsys" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M68.836,146.216c8.889,5.698,21.647,10.021,35.324,10.021c20.283,0,32.142-10.689,32.142-26.203
c0-14.122-8.203-22.557-28.949-30.305c-25.072-9.121-40.577-22.343-40.577-43.759c0-23.93,19.829-41.708,49.688-41.708
c15.495,0,27.112,3.646,33.719,7.516l-5.456,16.182c-4.787-2.959-15.031-7.293-28.949-7.293c-20.961,0-28.94,12.536-28.94,23.021
c0,14.354,9.344,21.425,30.536,29.627c25.981,10.03,38.971,22.565,38.971,45.123c0,23.717-17.313,44.444-53.557,44.444
c-14.809,0-30.991-4.546-39.194-10.039L68.836,146.216z"/>
<path fill="none" d="M82.734,183.337v-66.265L33.15,27.158h23.17l22.009,43.104c5.792,11.811,10.66,21.321,15.528,32.207h0.462
c4.17-10.207,9.736-20.396,15.764-32.207l22.473-43.104h22.707l-52.131,89.669v66.51H82.734z"/>
</svg>
<svg class="hide" version="1.1" id="glyphsacirc" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M107.585,161.74c-3.868,0-9.281-2.318-12.117-5.156c-3.351-3.35-4.898-6.961-6.186-11.342
c-10.312,6.961-22.688,16.498-30.421,16.498c-18.304,0-30.935-15.211-30.935-31.451c0-12.631,6.703-20.621,20.881-25.52
c15.468-5.157,34.287-12.118,39.958-16.757v-4.383c0-18.818-8.766-29.13-21.913-29.13c-4.898,0-8.765,1.805-11.601,4.898
c-3.093,3.609-5.413,9.281-7.476,17.015c-1.288,4.124-3.868,5.929-7.477,5.929c-4.64,0-11.084-4.898-11.084-11.085
c0-3.609,3.094-6.703,7.991-10.312c6.961-5.156,23.976-14.952,38.67-18.045c7.733,0,15.466,2.321,21.139,6.961
c9.024,7.733,11.601,18.045,11.601,31.708v47.693c0,11.6,4.64,15.467,9.024,15.467c3.094,0,6.7-1.289,9.279-2.836l2.577,6.703
L107.585,161.74z M88.766,96.778c-5.415,2.835-17.788,8.248-23.202,10.828c-10.054,4.639-15.725,9.537-15.725,19.076
c0,13.146,10.31,19.592,18.303,19.592c6.701,0,15.725-4.383,20.623-9.279V96.778z"/>
<circle fill="none" cx="112.681" cy="105.821" r="52.076"/>
</svg>
<svg class="hide" version="1.1" id="svggears" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M127.11,135.58c0.899,0.19,2.568,0.312,3.713,0.269l1.001-0.038c1.145-0.043,2.215-1.005,2.381-2.138
l0.588-5.705c0.071-1.142-0.778-2.305-1.888-2.585l-0.956-0.242c-1.11-0.279-2.77-0.509-3.689-0.509s-1.724-0.936-1.785-2.077
l-0.674-6.489c-0.181-1.13,0.407-2.211,1.305-2.401c0.899-0.191,2.475-0.76,3.5-1.266l0.901-0.441
c1.026-0.505,1.617-1.819,1.311-2.923l-1.762-5.453c-0.397-1.072-1.644-1.791-2.771-1.597l-1.005,0.176
c-1.127,0.194-2.737,0.66-3.578,1.034c-0.84,0.375-1.954-0.152-2.475-1.172l-3.263-5.647c-0.625-0.958-0.525-2.186,0.221-2.728
c0.744-0.542,1.954-1.704,2.687-2.582l0.641-0.769c0.734-0.879,0.735-2.318,0.004-3.198l-3.842-4.259
c-0.8-0.817-2.232-0.964-3.184-0.327l-0.823,0.552c-0.95,0.637-2.232,1.718-2.849,2.402c-0.615,0.685-1.849,0.655-2.737-0.064
l-5.276-3.833c-0.96-0.623-1.37-1.785-0.908-2.583c0.459-0.797,1.092-2.35,1.403-3.451l0.275-0.974
c0.313-1.101-0.271-2.417-1.296-2.925l-5.237-2.329c-1.064-0.42-2.433,0.027-3.042,0.996l-0.536,0.853
c-0.609,0.968-1.34,2.476-1.624,3.35c-0.285,0.874-1.421,1.346-2.525,1.047l-6.383-1.349c-1.13-0.18-1.976-1.075-1.879-1.989
c0.096-0.914,0.042-2.588-0.121-3.72l-0.141-0.983c-0.163-1.132-1.229-2.122-2.371-2.199c0,0-1.201-0.081-2.866-0.081
c-1.669,0-2.876,0.081-2.876,0.081c-1.141,0.077-2.208,1.067-2.371,2.199l-0.141,0.982c-0.163,1.132-0.217,2.807-0.121,3.721
c0.096,0.914-0.75,1.81-1.879,1.99l-6.383,1.35c-1.105,0.298-2.241-0.173-2.525-1.046c-0.284-0.874-1.015-2.381-1.624-3.35
l-0.537-0.854c-0.609-0.969-1.978-1.417-3.041-0.996l-5.238,2.331c-1.024,0.508-1.608,1.825-1.295,2.925l0.275,0.973
c0.313,1.1,0.945,2.653,1.406,3.452c0.46,0.798,0.052,1.96-0.907,2.583l-5.278,3.834c-0.889,0.72-2.122,0.75-2.737,0.065
c-0.616-0.684-1.897-1.765-2.848-2.402L36.61,82.52c-0.951-0.637-2.384-0.49-3.185,0.327l-3.841,4.26
c-0.73,0.881-0.729,2.32,0.004,3.199l0.641,0.768c0.733,0.878,1.943,2.041,2.688,2.582c0.746,0.542,0.845,1.77,0.221,2.729
l-3.263,5.649c-0.52,1.019-1.633,1.547-2.473,1.173c-0.841-0.374-2.45-0.84-3.577-1.035l-1.006-0.175
c-1.127-0.196-2.375,0.522-2.771,1.596l-1.762,5.455c-0.306,1.102,0.285,2.417,1.312,2.921l0.899,0.441
c1.027,0.505,2.602,1.074,3.5,1.265c0.899,0.191,1.487,1.271,1.306,2.401c0,0-0.784,4.917-0.784,8.558c0,0.003,0,0.003,0,0.003
c0,0.004-0.751,0.007-1.671,0.007s-2.579,0.23-3.688,0.509l-0.959,0.242c-1.109,0.28-1.958,1.443-1.887,2.586l0.59,5.706
c0.166,1.132,1.237,2.093,2.38,2.137l1.001,0.037c1.144,0.043,2.814-0.078,3.713-0.269c0.898-0.19,1.877,0.557,2.175,1.662
l2.015,6.205c0.412,1.067,0.062,2.246-0.778,2.621c-0.84,0.373-2.263,1.258-3.163,1.965l-0.796,0.626
c-0.9,0.706-1.2,2.114-0.667,3.126l2.872,4.963c0.615,0.966,1.986,1.406,3.049,0.981l0.926-0.372
c1.062-0.425,2.541-1.218,3.287-1.758c0.746-0.542,1.945-0.258,2.665,0.631l4.365,4.848c0.808,0.81,0.966,2.031,0.35,2.716
s-1.557,2.071-2.091,3.083l-0.463,0.877c-0.534,1.012-0.239,2.424,0.655,3.137l4.633,3.378c0.951,0.637,2.38,0.486,3.178-0.334
l0.708-0.729c0.797-0.821,1.826-2.145,2.287-2.943c0.46-0.797,1.67-1.023,2.689-0.502l5.959,2.653
c1.067,0.412,1.708,1.462,1.425,2.335c-0.283,0.875-0.578,2.522-0.654,3.665l-0.068,1.012c-0.076,1.141,0.768,2.307,1.876,2.589
l5.611,1.181c1.128,0.189,2.374-0.535,2.769-1.609l0.343-0.933c0.395-1.074,0.796-2.701,0.892-3.614
c0.097-0.914,1.108-1.589,2.249-1.499c0,0,1.458,0.114,3.26,0.114c1.805,0,3.271-0.115,3.271-0.115
c1.141-0.09,2.152,0.584,2.248,1.497c0.097,0.914,0.498,2.541,0.892,3.614l0.344,0.935c0.394,1.073,1.64,1.798,2.769,1.608
l5.61-1.182c1.108-0.282,1.953-1.448,1.876-2.59l-0.067-1.009c-0.077-1.143-0.372-2.791-0.656-3.665s0.358-1.926,1.425-2.337
l5.958-2.656c1.019-0.521,2.228-0.296,2.689,0.501c0.46,0.799,1.489,2.122,2.287,2.943l0.708,0.729
c0.799,0.82,2.227,0.97,3.178,0.334l4.632-3.379c0.895-0.714,1.188-2.125,0.655-3.137l-0.462-0.877
c-0.534-1.011-1.477-2.398-2.092-3.082c-0.617-0.686-0.46-1.907,0.349-2.718l4.364-4.848c0.719-0.89,1.918-1.175,2.663-0.634
c0.745,0.543,2.224,1.333,3.286,1.758l0.927,0.373c1.063,0.425,2.435-0.017,3.05-0.981l2.87-4.963
c0.532-1.014,0.231-2.42-0.668-3.127l-0.795-0.625c-0.899-0.706-2.323-1.591-3.164-1.966c-0.84-0.374-1.19-1.554-0.778-2.621
l2.013-6.206C125.234,136.137,126.212,135.39,127.11,135.58z"/>
<path fill="none" d="M175.403,87.286c0.899,0.19,2.568,0.311,3.713,0.268l1.002-0.038c1.145-0.042,2.215-1.005,2.381-2.137
l0.587-5.705c0.072-1.141-0.777-2.305-1.888-2.585l-0.956-0.242c-1.109-0.279-2.769-0.509-3.689-0.509
c-0.919,0-1.723-0.935-1.785-2.077l-0.673-6.489c-0.181-1.13,0.406-2.211,1.305-2.401c0.898-0.192,2.474-0.76,3.5-1.266l0.901-0.442
c1.025-0.504,1.616-1.819,1.311-2.922l-1.763-5.453c-0.396-1.073-1.644-1.792-2.771-1.596l-1.004,0.175
c-1.127,0.195-2.738,0.66-3.579,1.035c-0.839,0.375-1.954-0.152-2.474-1.171l-3.264-5.648c-0.624-0.958-0.524-2.186,0.221-2.728
c0.744-0.542,1.954-1.704,2.688-2.582l0.64-0.769c0.734-0.879,0.735-2.318,0.005-3.198l-3.842-4.26
c-0.801-0.816-2.233-0.964-3.184-0.327l-0.823,0.552c-0.951,0.637-2.233,1.718-2.849,2.402s-1.849,0.655-2.737-0.064l-5.277-3.833
c-0.96-0.623-1.37-1.785-0.908-2.583c0.46-0.797,1.092-2.35,1.404-3.451l0.275-0.974c0.312-1.101-0.271-2.417-1.296-2.925
l-5.237-2.329c-1.063-0.42-2.434,0.027-3.042,0.996l-0.536,0.853c-0.608,0.968-1.34,2.476-1.624,3.35
c-0.285,0.875-1.421,1.346-2.524,1.048l-6.384-1.349c-1.13-0.18-1.976-1.075-1.879-1.989c0.096-0.914,0.042-2.588-0.121-3.72
l-0.142-0.983c-0.162-1.132-1.229-2.122-2.369-2.199c0,0-1.202-0.081-2.867-0.081c-1.669,0-2.876,0.082-2.876,0.082
c-1.142,0.077-2.209,1.066-2.371,2.199l-0.142,0.982c-0.161,1.132-0.217,2.807-0.121,3.721c0.097,0.914-0.749,1.81-1.879,1.99
l-6.384,1.351c-1.105,0.298-2.24-0.173-2.524-1.046c-0.284-0.874-1.016-2.381-1.623-3.35l-0.538-0.854
c-0.607-0.969-1.978-1.417-3.04-0.996l-5.238,2.33c-1.024,0.509-1.608,1.825-1.295,2.926l0.275,0.973
c0.313,1.1,0.945,2.653,1.406,3.452c0.46,0.797,0.052,1.96-0.907,2.583l-5.278,3.834c-0.889,0.72-2.122,0.75-2.737,0.065
c-0.616-0.684-1.897-1.765-2.848-2.402l-0.823-0.553c-0.951-0.637-2.384-0.49-3.185,0.327l-3.841,4.261
c-0.73,0.881-0.729,2.32,0.004,3.199l0.641,0.768c0.733,0.878,1.943,2.041,2.688,2.582c0.746,0.542,0.845,1.77,0.221,2.729
l-3.263,5.649c-0.52,1.019-1.633,1.547-2.473,1.173c-0.841-0.374-2.45-0.84-3.577-1.035l-1.006-0.175
c-1.127-0.196-2.375,0.522-2.771,1.595l-1.762,5.455c-0.306,1.102,0.285,2.417,1.312,2.921l0.899,0.442
c1.027,0.504,2.602,1.074,3.5,1.265c0.899,0.19,1.487,1.271,1.306,2.401c0,0-0.784,4.917-0.784,8.557c0,0.003,0,0.003,0,0.003
c0,0.003-0.751,0.007-1.671,0.007s-2.579,0.23-3.688,0.509L66.496,77.1c-1.109,0.28-1.958,1.442-1.887,2.585l0.59,5.706
c0.166,1.132,1.237,2.094,2.38,2.136l1.001,0.038c1.144,0.043,2.814-0.078,3.713-0.269c0.898-0.19,1.877,0.556,2.175,1.661
l2.015,6.206c0.412,1.067,0.062,2.247-0.778,2.621c-0.84,0.373-2.263,1.258-3.163,1.964l-0.796,0.625
c-0.9,0.706-1.2,2.114-0.667,3.128l2.872,4.962c0.615,0.966,1.986,1.405,3.049,0.981l0.926-0.372
c1.062-0.426,2.541-1.217,3.287-1.758c0.746-0.542,1.945-0.258,2.665,0.63l4.365,4.848c0.808,0.81,0.966,2.031,0.35,2.718
c-0.616,0.684-1.557,2.069-2.091,3.083l-0.463,0.877c-0.534,1.011-0.239,2.424,0.655,3.137l4.633,3.376
c0.951,0.637,2.38,0.487,3.178-0.334l0.708-0.729c0.797-0.82,1.826-2.144,2.287-2.941c0.46-0.799,1.67-1.025,2.689-0.503
l5.959,2.653c1.066,0.412,1.708,1.462,1.426,2.336c-0.285,0.874-0.579,2.522-0.655,3.664l-0.068,1.011
c-0.076,1.142,0.769,2.308,1.877,2.59l5.611,1.182c1.129,0.188,2.374-0.536,2.769-1.61l0.343-0.933
c0.396-1.074,0.797-2.701,0.892-3.614c0.097-0.915,1.108-1.59,2.249-1.5c0,0,1.458,0.114,3.26,0.114
c1.805,0,3.271-0.114,3.271-0.114c1.142-0.09,2.152,0.583,2.249,1.498c0.096,0.912,0.497,2.54,0.893,3.614l0.343,0.933
c0.395,1.074,1.641,1.798,2.769,1.61l5.61-1.182c1.109-0.283,1.954-1.449,1.876-2.59l-0.066-1.009
c-0.076-1.144-0.373-2.792-0.655-3.666c-0.284-0.875,0.356-1.927,1.424-2.337l5.96-2.656c1.018-0.521,2.228-0.296,2.688,0.502
c0.459,0.798,1.488,2.121,2.286,2.941l0.709,0.731c0.798,0.82,2.227,0.968,3.177,0.334l4.633-3.379
c0.895-0.716,1.188-2.126,0.654-3.137l-0.462-0.877c-0.533-1.011-1.477-2.399-2.092-3.083c-0.616-0.687-0.46-1.908,0.349-2.718
l4.364-4.847c0.719-0.891,1.918-1.176,2.664-0.635c0.744,0.542,2.223,1.334,3.286,1.758l0.927,0.372
c1.062,0.426,2.434-0.016,3.049-0.979l2.87-4.965c0.532-1.013,0.232-2.419-0.668-3.125l-0.795-0.626
c-0.899-0.706-2.323-1.592-3.163-1.966s-1.191-1.554-0.778-2.621l2.012-6.206C173.528,87.844,174.506,87.095,175.403,87.286z"/>
</svg>
<svg class="hide" version="1.1" id="svggreenland" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="200px" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve">
<path fill="none" d="M172.189,76.894c-0.651,0.691-2.171,0.642-3.206,0.797c-0.596,0.087-1.085,0.335-1.678,0.422
c-0.75,0.111-1.668-0.134-2.337,0.133c0.275,0.186,0.555,0.276,0.84,0.27c-0.22,0.446-1.029,0.344-1.575,0.335
c-0.67-0.016-1.193,0.186-1.808,0.273c-1.274,0.18-2.74-0.13-4.092-0.149c-0.53-0.006-0.801,0.35-1.287,0.434
c-0.604,0.102-1.296,0.062-1.979,0.006c-1.745-0.139-3.273-0.006-4.852,0.183c1.095,0.127,1.975-0.118,2.954-0.205
c0.875-0.074,1.857,0.074,2.797,0.164c2.257,0.217,4.241-0.288,6.573,0.174c-1.358,0.654-2.937,0.788-4.729,0.806
c-1.193,0.009-2.533-0.254-3.695-0.195c-0.8,0.041-1.38,0.397-2.155,0.471c0.794,0.137-0.183,0.409-0.418,0.617
c0.657-0.022,0.958,0.263,1.605,0.056c-0.821,0.133,0.258-0.456,0.543-0.515c0.555-0.115,1.19-0.103,1.811-0.093
c1.169,0.019,2.533-0.635,3.882,0.105c-1.253,0.515-2.348,0.654-3.956,0.624c-0.887-0.019-0.89,0.313-1.451,0.573
c-0.195,0.09,0.167,0.192,0.09,0.307c-0.111,0.18-0.279,0.326-0.499,0.434c0.666,0.049,0.921-0.366,1.792-0.192
c-0.195,0.292-0.385,0-0.707,0.084c-0.267,0.068-0.348,0.282-0.614,0.35c-0.418,0.108-0.8-0.019-0.911,0.341
c-0.121,0.387-0.871,0.248-1.37,0.189c-0.556-0.068-1.032,0.118-1.491,0.202c1.026,0.074-0.258,0.362-0.416,0.434
c0.232-0.087,0.496-0.05,0.791,0.108c-0.248,0.09-0.837,0.359-0.731,0.645c0.167,0.446-0.58,0.381-0.971,0.409
c0.254,0.019,0.505,0.037,0.76,0.056c-0.593,0.26-1.163,0.558-1.51,1.001c0.181,0,1.371,0.042,1.148,0.226
c0.434-0.215,0.578-0.676,1.108-0.846c0.533-0.17,1.125,0.043,1.764,0.118c0.134,0.015,0.327,0.024,0.541,0.035
c-0.269-0.055-0.539-0.119-0.808-0.211c0.633-0.081,2.053-0.068,2.729,0.406c-0.084-0.005-0.168-0.01-0.251-0.014
c0.397,0.09,0.74,0.225,0.911,0.448c-1.447,0.09-3.175-0.465-4.479-0.108c0.449-0.041,1.104,0.031,1.603,0.245
c-0.146-0.021-1.364-0.031-0.359,0.109c-0.462,0.059-1.002-0.075-1.482-0.056c0.794,0.103,1.56,0.105,2.362,0.245
c-0.028,0.006-0.062,0.004-0.092,0.008c0.447-0.001,0.89,0.038,1.187,0.203c0.366,0.201,0.831,0.416,1.166,0.208
c0.679-0.421,3.045,0.068,2.154,0.946c-0.449,0.45-2.089,0.09-2.88-0.034c-1.259-0.192-2.747-0.434-3.776-0.14
c0.229-0.043,0.483-0.019,0.757,0.081c-0.238,0.155-0.595,0.158-0.979,0.127c0.132,0.02,0.264,0.043,0.401,0.09
c-0.552,0.155-1.259,0.515-1.996,0.366c-0.413-0.083-0.8-0.496-1.24-0.462c-0.189,0.016-0.819,0.363-0.905,0.478
c0.288,0.019,0.579,0.17,0.769,0.378c0.124-0.394,1.33-0.146,0.686-0.056c0.214-0.074,0.462-0.056,0.744,0.056
c-0.224,0.034-0.44,0.077-0.648,0.136c0.22-0.05,0.462-0.068,0.723-0.056c-0.446,0.232-1.303-0.14-1.702,0.136
c0.558-0.012,1.28-0.152,1.916,0.161c-0.05,0.103-0.103,0.202-0.161,0.298c0.549,0.09,0.908-0.152,1.451-0.081
c0.515,0.068,1.051,0.369,1.54,0.325c-0.533-0.09-1.075-0.332-1.611-0.406c0.312-0.056,0.667-0.029,1.029,0.011
c-0.197-0.048-0.394-0.088-0.593-0.147c0.245,0.011,0.498,0.082,0.75,0.162c0.436,0.052,0.877,0.117,1.274,0.095
c0.657-0.037,1.256,0.493,0.432,0.484c-0.475-0.006-0.901,0.084-1.343,0.156c0.807,0.056,1.61,0.223,2.347,0.646
c-0.087,0.053-0.174,0.108-0.26,0.161c0.629,0.245,0.691,0.85,0.437,1.11c-0.264,0.272-0.877,0.232-1.383,0.108
c0.224-0.527-1.984-0.351-2.428-0.487c-0.89-0.276-2.093-0.915-2.855-0.651c0.54,0.083,1.11,0.118,1.628,0.462
c-0.518-0.1-0.974-0.034-1.473-0.081c1.293,0.251,2.602,0.121,3.879,0.865c-0.71,0.217-1.659,0.071-2.385,0.282
c-0.691,0.192-1.199-0.279-1.912-0.307c0.703,0.056,1.156,0.571,1.891,0.443c0.713-0.133,1.314-0.313,2.161-0.226
c-0.158,0.22-0.499,0.511,0.04,0.784c-0.254,0.152-0.62,0.183-0.979,0.199c0.735,0.081,1.38-0.295,2.151-0.043
c0.775,0.254,1.266-0.009,1.947-0.031c0.016,0.316,0.53,0.468,0.865,0.447c-0.57,0.257-0.998,0.629-1.82,0.573
c-0.288-0.019-0.583,0.006-0.88-0.081c-0.264-0.074-0.574-0.394-0.847-0.394c-0.896,0-1.798-0.034-2.691-0.019
c-0.471,0.006-0.861-0.341-1.271-0.304c0.285,0.078,0.561,0.229,0.74,0.422c-0.396,0.22-1.026,0.297-1.618,0.22
c0.279,0.021,0.546,0.102,0.8,0.248c-0.077,0.316-0.552,0.226-0.896,0.297c1.094,0.223,1.37,0.208,1.562,0.989
c0.778-0.118-0.81-0.937,0.316-0.946c1.007-0.006,2.198,0.381,3.249,0.403c0.062,0.291-0.338,0.394-0.412,0.632
c-0.105,0.35,0.248,0.391-0.45,0.431c-0.85,0.049-1.637-0.143-2.35,0.111c-1.736,0.62-3.37,0.103-5.513-0.508
c0.062-0.468,1.072-0.267,1.597-0.158c0.955,0.198,1.711,0.313,2.521,0.108c-0.971,0.016-1.699,0.003-2.737-0.205
c-0.698-0.143-1.612-0.359-2.093-0.065c-0.372,0.229-0.633,0.642-1.374,0.294c-0.471-0.223-1.413-0.859-1.882-0.667
c0.623,0.391,1.634,0.66,2.325,0.939c-0.545,0.043-1.178-0.003-1.612,0.192c-0.428,0.189-0.669,0.403-1.265,0.431
c-0.645,0.028-1.392-0.18-2.068-0.22c-0.514-0.031-1.19-0.524-1.611-0.304c0.747,0.146,1.491,0.412,2.204,0.397
c-0.264,0.313-0.555,0.381-1.172,0.267c-0.53-0.103-0.939,0.068-1.43,0.053c0.478-0.009,0.694,0.406,0.311,0.496
c0.425,0.034,0.502-0.254,0.771-0.378c0.372-0.167,1.11-0.021,1.572-0.124c0.666-0.149,1.305-0.037,2.086,0.105
c0.487,0.09,0.943-0.009,1.442,0.174c-0.189,0.341-0.747,0.307-1.231,0.347c-0.471,0.037-0.809,0.245-1.283,0.279
s-1.001-0.205-1.485-0.205c-0.539,0-1.051,0.053-1.603,0.025c0.766,0.254,1.389,0,2.143,0.173c-0.189,0.078-0.403,0.127-0.636,0.149
c0.354,0,0.716,0.009,1.051-0.025c-0.059,0.093-0.124,0.183-0.192,0.273c0.623-0.214,3.061-1.014,3.91,0.013
c0.459,0.558-2.288,0.465-2.604,0.456c0.385,0.118,0.707,0.041,1.054,0.05c0.022,0.201-0.077,0.372-0.267,0.471
c0.784-0.078,1.032-0.704,2.034-0.471c0.589,0.139,1.628,0.583,2.124,0.396c0.117,0.385,0.84,0.254,1.383,0.533
c0.452,0.233,1.02,0.031,1.568,0.335c-0.418,0.041-1.256,0.208-1.243,0.592c0.372-0.117,0.753-0.354,1.274-0.223
c-0.239,0.034-0.359,0.161-0.229,0.372c0.396-0.307,0.942-0.332,1.554-0.322c-0.85,0.137-1.414,0.369-1.237,1.092
c0.518-0.41,0.914-0.559,1.845-0.372c-0.438,0.065-0.409,0.13,0.077,0.198c-0.183,0.071-0.385,0.121-0.604,0.148
c0.248-0.037,0.446,0.057,0.521,0.273c-0.409-0.162-0.883,0.186-1.19,0.244c0.329,0.01,0.639-0.024,0.955-0.049
c-0.071,0.084-0.143,0.167-0.214,0.248c0.217-0.034,0.449-0.028,0.701,0.024c-0.264,0.099-0.248,0.524-0.701,0.372
c0.248,0.043,0.496,0.111,0.738,0.199c-0.276-0.016-0.425,0.117-0.403,0.322c0.868-0.105-0.028,0.924-0.509,0.369
c-0.275-0.314-1.534,0.223-1.146-0.816c-0.747-0.31-0.195,0.57-0.376,0.757c-0.278,0.298-1.444,0.081-1.925-0.024
c-0.858-0.193-1.705-0.326-1.876-1.095c-0.093-0.419-1.014-1.023-1.553-0.986c-0.939,0.065-1.777-0.495-2.731-0.558
c-0.348-0.021-0.754-0.171-1.082-0.148c-0.248,0.018-0.366,0.158-0.599,0.188c-0.564,0.068-1.541-0.58-2.14-0.788
c-0.8-0.282-1.516-0.359-2.354-0.639c0.354,0.109,0.524,0.307,0.376,0.496c0.449-0.012,0.917-0.015,1.407,0.075
c-0.934-0.09,0.937,0.403,1.2,0.443c-0.075,0.153-0.278,0.237-0.534,0.288c0.245,0.006,1.548,0.002,0.757,0.184
c0.701,0,1.442,0.102,2.143,0.107c0.422,0.007,0.8-0.102,1.253-0.018c0.26,0.049,0.731,0.17,0.868,0.406
c0.251,0.418-0.546,0.053-0.571,0.226c-0.08,0.593-2.188,0.7-2.954,0.714c-0.406,0.006-0.952,0.102-1.396-0.152
c-0.282-0.164-0.57-0.416-0.884-0.344c0.131,0.188,0.369,0.338,0.627,0.396c-0.425,0.123-1.135,0.096-1.678-0.025
c1.433,0.348,0.391,0.599,0.065,0.949c-0.329,0.354-1.427,0.375-2.071,0.338c0.438,0.223,1.417,0.034,1.742-0.068
c0.276-0.087,0.276,0.229,0.596,0.254c0.338,0.025,0.636-0.021,0.945-0.049c0.02-0.002,0.045-0.006,0.065-0.008
c-0.244-0.02-0.489-0.062-0.735-0.154c0.267-0.064,0.189-0.347,0.465-0.394c0.192-0.028,0.379-0.059,0.562-0.096
c0.567-0.14,0.964-0.456,1.581-0.549c0.651-0.097,1.321-0.022,1.926-0.204c0.356-0.112,0.747-0.225,1.188-0.218
c-0.01,0.075-0.016,0.149-0.021,0.224c0.399-0.047,1.215,0.471,0.576,0.521c0.14,0.074,0.276,0.149,0.412,0.224
c-1.078,0.362-2.378,0.285-3.615,0.381c-0.026,0.002-0.054,0.007-0.079,0.01c0.418,0.01,0.821,0.079,1.152,0.275
c-0.362,0.311-0.967,0.177-1.525,0.224c-0.412,0.034-0.738,0.198-1.061,0.338c-0.521,0.229-1.854,0.285-2.576,0.204
c0.57,0.205,1.581,0.208,2.064,0.134c0.556-0.084,0.894,0.255,1.297-0.158c0.515-0.54,1.624-0.226,2.468-0.369
c0.645-0.111,1.196-0.437,1.978-0.322c-0.046,0.093-0.103,0.183-0.161,0.272c0.636-0.136,1.15-0.17,1.922,0.09
c0.646,0.221,1.256,0.112,1.892,0.252c0.726,0.158,1.56,0.18,2.214,0.114c0.381-0.04,0.995-0.204,1.426,0.087
c-0.446,0.312-1.785,0.44-2.455,0.478c0.227-0.022,0.449,0.024,0.673,0.14c-0.285,0.143-0.683-0.053-1.014-0.14
c0.297,0.425-0.928,0.276-1.203,0.257c0.526-0.002,0.756,0.292,0.384,0.479c-0.248,0.12-0.924-0.041-1.277-0.06
c0.825,0.118-0.006,0.385-0.241,0.403c-0.298,0.019-0.633,0.006-0.952-0.053c0.66,0.428-0.558,0.27,0.27,0.372
c-0.391,0.111-1.022-0.235-1.265,0.071c0.58-0.065,0.369,0.235,0.134,0.272c-0.354,0.056-0.636-0.226-0.354,0.192
c-0.369-0.093-1.34,0.09-0.586,0.071c-0.577,0.103-0.847,0.512-1.504,0.512c-0.311,0-0.348,0.226-0.604,0.285
c-0.341,0.077-0.757-0.016-1.095,0.077c-0.465,0.127-0.961,0.041-1.398,0.174c-0.27,0.084-1.011,0.245-1.357-0.021
c0.105,0.227-0.087,0.35-0.4,0.248c-0.351-0.111-0.493,0.049-0.729,0.124c-0.44,0.136-1.001,0.278-1.594,0.046
c0.348,0.472-0.893,0.373-1.222,0.289c-0.167-0.041-0.797-0.416-0.787-0.158c0.013,0.304-0.366,0.152-0.651,0.195
c0.261,0.003,0.506,0.137,0.633,0.328c-0.288,0.016-0.536,0.094-0.871-0.018c-0.304-0.1-0.533-0.299-0.856-0.311
c0.264,0.077,0.493,0.201,0.683,0.375c-0.704,0.117-1.464,0.062-2.227,0.064c-0.412,0-1.066-0.328-0.439-0.368
c-0.63-0.151-0.992,0.013-1.327-0.562c-0.323,0.242-0.493-0.133-0.694-0.372c0.074,0.255-0.354,0.202-0.158,0.472
c0.13,0.18,0.61,0.248,0.809,0.391c-0.158,0.09-0.359,0.115-0.604,0.071c0.335,0.136,0.772,0.09,0.952,0.419
c-0.406-0.068-0.202,0.217-0.242,0.384c-0.05,0.211-0.8,0.047-1.042,0.128c-0.359,0.12-1.032,0.223-1.305,0.387
c-0.227,0.133-0.164,0.412-0.63,0.313c0.189,0.257,0.056,0.552-0.372,0.421c0.348,0.273-0.102,0.271-0.037,0.521
c0.078,0.316-1.433,0.174-0.766,0.502c-0.483-0.13-0.385,0.242-0.198,0.469c-0.409-0.192-0.683,0.318-0.878,0.418
c0.214-0.496-0.108,0.162-0.301-0.371c-0.198,0.099-0.217,0.285,0.078,0.465c-0.245,0.033-1.575,0.977-1.733,0.303
c-0.223,0.104-0.201,0.283,0.127,0.443c-0.31-0.002-0.647-0.033-0.977-0.129c-0.412-0.121-0.546-0.49-0.831-0.268
c0.519,0.121,0.363,0.4,0.654,0.654c-0.48,0.078-0.617-0.021-0.905,0.273c-0.217,0.223-0.713,0.086-0.971,0.26
c0.022-0.186-0.167-0.354-0.446-0.418c0.189,0.514-0.893,0.232-1.014,0.582c-0.248-0.167-0.27-0.35-0.118-0.465
c-0.592-0.037,0.1,0.555-0.415,0.509c-0.378-0.031-1.156-0.362-1.38-0.183c-0.14-0.521,0.862-0.971,1.451-1.002
c-0.734-0.006-1.538-0.18-2.264-0.164c0.242,0.105,0.484,0.168,0.723,0.186c-0.254,0.094-0.121,0.403-0.412,0.443
c-0.359,0.047,0.124,0.184,0.024,0.305c-0.164,0.201-0.781,0.402-1.048-0.072c-0.214,0.292,0.366,0.416,0.686,0.516
c-0.04,0.533-0.596,0.25-0.815,0.536c-0.108-0.394-0.248-0.202-0.645-0.211c0.13,0.077,0.26,0.155,0.391,0.232
c-0.559,0.028-1.104-0.151-1.702-0.304c0.394,0.26,0.062,0.465-0.252,0.422c-0.204-0.028-0.319-0.32-0.555-0.335
c-0.298-0.016-0.177,0.344-0.425,0.375c-0.312,0.04-0.766-0.271-0.691,0.146c0.264,0.043,0.527,0.1,0.797,0.164
c-0.087,0.044-0.17,0.09-0.251,0.14c0.118,0.081,0.235,0.158,0.359,0.233c-0.499-0.105-0.431,0.31-0.865,0.257
c0.744,0.22-0.248,0.384-0.474,0.315c-0.184-0.052-0.503-0.275-0.673-0.229c-0.235,0.064-0.942-0.068-0.992,0.161
c-0.034,0.151,0.13,0.251-0.164,0.282c0.496,0.394,1.454,0.586,1.116,1.092c0.235,0.009,0.424,0.114,0.523,0.287
c-0.388,0.094-0.84-0.148-1.243-0.009c-0.412,0.143-1.179-0.121-1.398,0.211c0.593,0.369,1.141,0.188,1.693,0.226
c0.688,0.047,0.483,0.481,0.096,0.531c0.475,0.084,0.311,0.354,0.499,0.576c-0.478-0.117-0.865,0.016-1.339-0.084
c-0.369-0.074-1.015-0.341-1.179-0.07c0.443-0.078,1.671,0.136,2.006,0.579c-0.121,0.024-0.598-0.111-0.636-0.003
c-0.056,0.174-0.154,0.205-0.443,0.047c0.841,0.514-0.245,0.027-0.399-0.044c0.217,0.248,0.148,0.298-0.211,0.155
c0.26,0.086,0.512,0.198,0.75,0.334c-0.347-0.129-0.694-0.275-1.051-0.355c0.121,0.046,1.203,0.514,1.116,0.586
c-0.227,0.191-1.398-0.426-1.718-0.518c0.025,0.455,0.884,0.176,0.958,0.691c0.038,0.272-0.853-0.137-1.048-0.137
c0.251,0.059,0.493,0.198,0.627,0.378c-0.289,0.084-1.029-0.103-1.309-0.356c-0.059,0.567,1.082,0.186,1.212,0.67
c-0.188-0.062-0.567,0.148-0.757,0.18c-0.468,0.077-0.026,0.05-0.098,0.125c0.011,0.001,0.022,0.003,0.033,0.005
c-0.028,0.011-0.061,0.019-0.089,0.028c-0.042,0.02-0.102,0.042-0.2,0.071c-0.013,0.004-0.03,0.003-0.043,0.007
c-0.189,0.055-0.389,0.098-0.621,0.095c-0.002,0-0.004,0.001-0.006,0.001c-0.001,0-0.001,0-0.002,0
c-0.008-0.066,0.012-0.113,0.042-0.151c0.004-0.005,0.007-0.009,0.011-0.013c0.067-0.072,0.194-0.1,0.346-0.1
c0.008,0,0.015,0,0.022,0c0.072,0.001,0.147,0.006,0.224,0.015c-0.401-0.165-0.707-0.12-1.088-0.173
c0.434,0.08,0.444,0.117,0.028,0.108c0.093,0.074,0.14,0.148,0.137,0.226c-0.028,0.097-0.22,0.104-0.348,0.104
c-0.334-0.004-0.753-0.06-1.116-0.171c0.323,0.186,0.946,0.452,1.281,0.431c0.347-0.025,0.946,0.304,0.319,0.214
c0.229,0.09,0.461,0.174,0.694,0.245c-0.526,0.152-1.298-0.267-1.906-0.312c0.391,0.173,1.206,0.263,1.514,0.526
c0.391,0.338-0.208,0.647-0.404,0.788c0.199-0.051,0.406-0.097,0.617-0.134c0.193,0.192,0.134,0.304-0.148,0.267
c0.815,0.552-0.936,0.311-1.209,0.312c0.416,0.174,0.927,0.332,1.219,0.156c-0.232,0.08-0.211,0.328-0.453,0.396
c-0.313,0.09-0.955-0.028-1.342-0.084c0.418,0.248,0.843,0.154,1.215,0.154c0.49,0.721-1.001,0.487-1.532,0.443
c0.214,0.051,1.376,0.1,0.726,0.246c0.189,0.006,0.375,0,0.549-0.021c-0.105,0.281-0.778,0.111-1.128,0.111
c0.325,0.043,0.654,0.086,0.989,0.154c-0.478,0.279-1.386-0.003-2.028,0.025c0.667,0.167,1.504,0.27,2.071,0.176
c-0.292,0.097-0.232,0.379-0.608,0.406c-0.353,0.028-0.763-0.024-1.163-0.117c0.558,0.201,1.079,0.174,1.628,0.312
c-0.381,0.149-0.999,0.004-1.488-0.046c0.428,0.103,0.815,0.056,1.247,0.18c0.118,0.391-0.524,0.248-0.872,0.224
c0.462,0.108,0.81,0.09,1.129-0.047c-0.087,0.236-0.425,0.242-0.766,0.201c0.211,0.037,0.419,0.06,0.624,0.065
c-0.323,0.103-0.72,0.155-1.15,0.068c0.822,0.208-0.453,0.241-0.806-0.202c-0.118,0.279-0.719,0.143-1.101,0.047
c0.558,0.227,1.085,0.205,1.637,0.335c-0.043,0.161-0.165,0.294-0.338,0.378c0.267-0.053,0.481-0.167,0.766-0.201
c-0.505,0.115,0.13,0.124,0.391,0.379c-0.217-0.022-0.4,0.009-0.54,0.09c0.202-0.041,0.394,0.046,0.53,0.223
c-0.133,0.082-0.307,0.092-0.497,0.073c0.019,0.006,0.034,0.014,0.052,0.021c0.018,0.006,0.033,0.013,0.05,0.02
c0.043,0.018,0.088,0.035,0.123,0.055c0.062,0.035,0.113,0.074,0.142,0.121c-0.288,0.006-0.599-0.103-0.902-0.177
c0.018,0.024,0.017,0.04,0.029,0.062c0.021,0.039,0.044,0.078,0.05,0.107c0.003,0.015-0.003,0.025-0.004,0.039
c-0.002,0.026-0.008,0.051-0.026,0.067c-0.012,0.013-0.029,0.021-0.047,0.029c-0.016,0.006-0.031,0.012-0.05,0.015
c-0.074,0.018-0.166,0.022-0.287,0.002c-0.179-0.03-0.482-0.072-0.512-0.131c-0.014-0.005-0.031-0.01-0.043-0.014
c0.02-0.007,0.039-0.014,0.058-0.021c0.026-0.021,0.083-0.042,0.19-0.066c0.004-0.001,0.008-0.003,0.012-0.004
c-0.025,0.001-0.052-0.007-0.078-0.008c-0.13-0.003-0.261-0.03-0.395-0.077c-0.064-0.021-0.128-0.038-0.194-0.07
c0.002-0.003,0.011-0.005,0.014-0.008c0.083-0.127,0.242-0.171,0.479-0.125c-0.18-0.021-0.36-0.043-0.542-0.068
c0.105-0.117,0.13-0.267,0.077-0.443c0.019,0.229-0.146,0.258-0.44,0.068c0.217,0.385-0.192,0.291-0.416,0.443
c-0.09,0.062-0.608,0.719-0.558,0.155c0,0.229-0.251,0.304-0.478,0c-0.083,0.173-0.322,0.157-0.574-0.022
c0.422-0.387,1.079-0.582,1.495-1.001c-0.58,0.14-1.048,0.617-1.532,0.868c-0.102-0.344,0.391-0.469,0.567-0.676
c0.177-0.205,0.167-0.506,0.434-0.66c-0.617,0.064-0.657,0.619-1.367,0.602c0.037-0.164,0.155-0.298,0.329-0.379
c-0.267,0.078-0.474,0.215-0.741,0.291c0.102-0.21,0.307-0.368,0.617-0.424c-0.49-0.034-0.698,0.207-0.887,0.446
c-0.161-0.388-0.62-0.14-0.177-0.378c-0.344-0.028-0.391,0.264-0.775,0.176c0.223-0.105,0.458-0.204,0.729-0.266
c-0.67,0,0.177-0.336,0.344-0.41c0.232-0.107,0.793,0.004,0.347-0.347c-0.298,0.232-0.592,0.592-1.153,0.577
c0.074-0.057,0.146-0.115,0.214-0.178c-0.527-0.143-0.54,0.391-1.045,0.202c0.174-0.155,0.468-0.202,0.713-0.292
c-0.214-0.016-0.396,0.016-0.542,0.09c0.502-0.502,1.913-0.198,2.173-0.89c-0.235,0.146-0.378,0.316-0.62-0.046
c0.013,0.529-0.8,0.505-1.249,0.713c-0.133-0.396,0.595-0.289,0.713-0.559c-0.124-0.049-0.245-0.102-0.369-0.154
c0.056-0.078,0.121-0.152,0.189-0.224c-0.329-0.021-0.416,0.251-0.803,0.134c0.673,0.086,0.056,0.536-0.434,0.402
c0.515,0.118,0.369,0.199,0.149,0.268c-0.403-0.521-1.026,0.127-1.42,0.09c0.341,0.318-0.105,0.381-0.44,0.22
c0.062-0.068,0.13-0.134,0.199-0.198c-0.729-0.072-1.371,0.009-2.025,0.064c0.338,0.103,0.614,0.047,0.893,0.025
c-0.104,0.152-0.501,0.197-0.927,0.2c0.03,0.008,0.058,0.01,0.089,0.02c-0.021,0.331-0.626,0.413-1.032,0.024
c0.086-0.024,0.177-0.047,0.271-0.064c-0.145-0.009-0.279-0.019-0.376-0.025c0.351-0.198,0.834-0.266,1.228-0.424
c-0.48,0-0.939,0.037-1.414,0.046c0.248-0.136,1.293,0.044,1.088-0.378c-0.248,0.074-0.527,0.183-0.856-0.111
c0.248-0.1,0.341-0.332,0.688-0.336c-0.769-0.105-0.527,0.496-1.008,0.469c0.041-0.074,0.084-0.148,0.127-0.223
c-0.183,0.07-0.577,0.176-0.71-0.156c0.242,0.022,0.412-0.03,0.505-0.154c-0.478-0.111-1.166-0.254-1.43-0.025
c0.162-0.391,0.992-0.355,1.529-0.399c-0.381,0-0.397-0.031-0.053-0.09c-0.698-0.028-1.197,0.44-2.046,0.046
c0.211-0.257,0.937-0.133,1.336-0.201c-0.298-0.006-0.567,0.037-0.88,0c0.031-0.164,0.146-0.297,0.319-0.378
c-0.14,0.093-0.323,0.136-0.549,0.134c-0.09-0.299,0.409-0.496,0.704-0.556c-0.729-0.211-0.589,0.475-1.101,0.512
c0.019-0.084,0.041-0.165,0.059-0.245c-0.502,0.307-0.056-0.285,0.136-0.365c0.232-0.094,0.521-0.174,0.837-0.171
c-0.229,0.013-0.394-0.087-0.44-0.267c-0.282,0.068-0.264,0.328-0.54,0.4c-0.254,0.067-0.629-0.047-0.846,0.067
c0.201-0.13,0.3-0.183-0.143-0.245c0.205-0.154,0.636,0.037,0.853-0.111c-0.22-0.037-0.431-0.053-0.629-0.043
c0.059-0.146,0.226-0.236,0.487-0.202c-0.292-0.05-2.301-0.232-2.208-0.71c0.077-0.406,0.685-0.57,0.958-0.871
c-0.242,0.18-0.543,0.351-0.967,0.356c0.118-0.226,0.347-0.394,0.601-0.533c-0.394-0.028-0.493,0.224-0.707,0.356
c0.037-0.174,0.108-0.328,0.21-0.468c-0.093,0.148-0.759,0.059-0.685-0.174c0.056-0.177,0.694-0.292,0.912-0.362
c-0.356,0.006-0.716,0.006-1.061,0.031c-0.164,0.009-1.029,0.241-0.453-0.053c-0.127,0.102-0.793,0.012-0.626-0.187
c0.094-0.113,0.33-0.133,0.597-0.134c-0.282-0.027-0.575-0.185-0.848-0.17c0.289-0.14,0.71-0.143,1.039-0.245
c-0.27,0.03-0.552,0.046-0.84,0.046c-0.075-0.402,0.905-0.201,1.23-0.312c-0.421,0.025-1.593-0.133-1.448,0.335
c-0.292-0.181-1.138-0.403-0.561-0.49c-0.794,0.128,0.403-0.151,0.499-0.177c0.261-0.074,0.434-0.22,0.694-0.291
c-0.322,0.056-0.672,0.27-1.134,0.09c0.155-0.164,0.006-0.307-0.351-0.379c0.713,0.115,1.401-0.055,1.907-0.223
c0.229-0.074,1.342-0.146,1.625,0c-0.279-0.381-0.793-0.111-1.113-0.155c0.139-0.105,0.267-0.214,0.381-0.335
c-0.53,0.41-1.848,0.915-2.998,0.533c0.133-0.086,0.31-0.13,0.527-0.133c-0.223-0.016-0.443-0.028-0.667-0.043
c0.602-0.137,1.6,0.201,2.003-0.156c-0.837-0.17,0.242-0.331,0.508-0.322c0.413,0.02,0.937,0.09,1.051-0.191
c-0.239,0.108-1.38-0.021-0.726-0.134c-0.254-0.074-0.508-0.158-0.763-0.245c0.124-0.019,1.42-0.009,0.533-0.133
c0.512-0.174,0.632,0.198,1.073,0.468c0.384,0.235,0.89,0.295,1.296,0.4c-0.211-0.45-1.594-0.466-0.89-0.89
c-0.639,0.183-0.149-0.336-0.006-0.4c-0.409,0.124-0.887,0.543-1.532-0.04c-0.051-0.046-0.101-0.101-0.147-0.157
c0.017,0.036,0.039,0.062,0.051,0.107c0.118,0.434-0.676,0.754-1.255,0.691c0.046-0.152,0.18-0.267,0.388-0.313
c-1.377-0.351-1.001,1.349-2.18,1.293c-0.13-0.158-0.422-1.106-0.304-1.209c0.228-0.192,0.685-0.334,1.137-0.45
c0.052-0.052,0.101-0.107,0.141-0.167c-0.508-0.217-0.713,0.178-1.003,0.358c-0.086,0.012-0.336,0.032-0.402-0.166
c-0.003-0.1,0.078-0.143,0.239-0.127c-0.508-0.4-0.239-0.639,0.006-0.834c-0.133,0.08-0.285,0.148-0.45,0.207
c0.025-0.074,0.043-0.151,0.053-0.232c-0.478-0.167-0.493,0.187-0.729,0.279c0.17-0.102,0.006-0.387,0.205-0.465
c0.223-0.053,0.431-0.117,0.623-0.198c0.282-0.171,0.642-0.181,0.847-0.415c0.57-0.648,1.993,0.002,2.93-0.134
c-0.307,0.021-0.403-0.034-0.282-0.161c-0.518,0.09-1.231-0.174-1.767-0.068c-0.422,0.08-0.871,0.183-1.116,0.409
c-0.22,0.204-0.44,0.335-0.843,0.394c-0.198,0.021-0.394,0.05-0.586,0.08c0.155-0.244,0.071-0.307-0.245-0.186
c0.133-0.133,0.242-0.281,0.323-0.443c-0.149,0.152-0.289,0.443-0.757,0.279c0.1-0.087,0.853-0.341,0.109-0.279
c0.214-0.024,0.263-0.143,0.133-0.325c-0.105,0.27-0.524,0.44-1.088,0.187c0.245,0.002,0.428-0.051,0.542-0.165
c-0.164,0.068-0.372,0.084-0.623,0.046c0.372-0.176,0.803-0.27,1.349-0.263c0.074,0,0.713,0.105,0.766,0.067
c0.205-0.145,0.267-0.291,0.62-0.331c0.316-0.037,0.822,0.204,0.552-0.195c-0.341,0.093-0.781,0.127-1.066,0.272
c-0.397,0.199-0.22-0.053-0.654-0.086c-0.598-0.044-0.952,0.535-1.711,0.186c0.147-0.038,0.276-0.092,0.412-0.141
c-0.218,0.074-0.467,0.123-0.809-0.045c0.223,0.012,0.415-0.01,0.577-0.068c-0.217-0.01-0.44-0.035-0.673-0.072
c0.552-0.22,1.392-0.22,1.742-0.604c0.06-0.064,0.154-0.1,0.244-0.139c-0.177,0.053-0.348,0.11-0.495,0.185
c-0.453,0.229-1.066,0.604-1.916,0.372c0.183-0.301,0.75-0.301,1.203-0.372c-0.065,0.003-1.724,0.103-1.352-0.183
c0.174-0.133,0.26-0.348,0.54-0.409c0.121-0.028,1.039,0.03,0.279,0.03c0.502,0.1,1.805,0.031,2.118-0.093
c-0.48,0.075-1.733,0.174-2.375-0.232c0.505-0.167,1.268-0.037,1.789-0.187c-0.332-0.018-0.583,0.094-0.974-0.024
c0.27-0.062,0.664,0,0.803-0.186c-0.22-0.013-0.446-0.034-0.679-0.068c0.388,0.062,0.626-0.065,0.986-0.044
c0.313,0.021,0.691,0.165,0.905,0.044c-0.487-0.077-0.543-0.149-0.177-0.211c-0.479-0.224-1.15-0.11-1.735,0.039
c-0.602-0.031-1.167-0.001-1.843-0.179c0.422,0,0.828-0.019,1.228-0.047c-0.341-0.03-0.651,0.016-1.045-0.164
c0.319,0.01,0.623-0.012,0.868-0.093c-0.363,0.068-0.769,0.108-1.265-0.046c0.236-0.189,0.747-0.109,1.104-0.187
c-0.952-0.022,0.307-0.118,0.391-0.124c0.394-0.031,0.8-0.043,1.19-0.074c0.698-0.057,1.432-0.062,2.127-0.115
c0.744-0.056,0.716,0.059,1.429,0.435c0.487,0.257,1.584-0.06,1.959-0.1c-0.912-0.177-1.56,0.48-2.496-0.394
c0.307-0.016,0.589-0.062,0.84-0.143c-1.101,0.227-2.552-0.01-3.667,0.214c-0.465,0.097-1.054-0.053-1.513,0.028
c-0.391,0.07-0.707,0.211-1.104,0.272c-0.419-0.442,0.312-0.668,0.651-0.776c-0.118,0.022-0.228,0.054-0.373,0.051
c-0.418-0.438,0.602-0.273,0.149-0.604c-0.676-0.452,1.547,0.294,0.471-0.325c0.341,0.13,1.023-0.065,0.267-0.047
c0.217-0.115,0.533-0.137,0.815-0.189c-0.211,0.013-0.44-0.009-0.691-0.068c0.325-0.059,0.717-0.004,1.117,0.063
c-0.134-0.035-0.268-0.071-0.407-0.132c-0.44-0.188-0.607-0.281-1.042-0.281c0.645-0.134,1.56,0.096,2.365,0.257
c-0.27-0.111-0.542-0.198-0.815-0.257c0.453-0.14,1.163,0.183,1.73,0.257c0.524,0.068,1.011,0.047,1.466,0.013
c0.899-0.074,2.089,0.455,2.948,0.312c-0.933-0.059-1.962-0.439-2.933-0.604c0.229-0.115,0.62-0.02,0.945,0
c-0.242-0.068-0.483-0.121-0.722-0.165c0.304-0.03,0.164-0.263,0.366-0.356c0.217-0.1,0.537-0.102,0.75-0.201
c-0.648,0.009-1.101,0.279-1.86,0.139c0.273,0.141,0.527,0.195,0.756,0.162c-0.058,0.177-0.311,0.24-0.614,0.265
c-0.239,0.245-0.831,0.291-1.302,0.321c0.629-0.273-0.14-0.065-0.45-0.164c0.495-0.035,0.987-0.077,1.458-0.143
c-0.29,0.005-0.588-0.004-0.794,0.021c-0.465,0.053-1.705-0.254-1.885,0.027c0.043-0.164,0.223-0.264,0.493-0.258
c-0.604-0.096-1.001,0.105-1.55,0.094c0.319-0.143,0.76-0.18,1.16-0.251c0.149-0.024,1.268-0.261,0.462-0.261
c0.298-0.146,0.757-0.183,1.203-0.164c-0.239-0.021-0.465-0.031-0.685-0.021c0.276-0.199,0.877,0.016,1.184-0.143
c-0.136,0.07-0.294,0.123-0.468,0.164c0.413-0.084,1.023,0.031,1.52,0.062c0.493,0.033,0.722-0.305,1.292-0.156
c-0.48-0.325,0.038-0.396,0.267-0.465c-0.422,0.016-0.695,0.221-1.191,0.14c0.016-0.245,0.465-0.214,0.416-0.465
c-0.053-0.276,0.093-0.503,0.546-0.422c-0.056,0.062-0.118,0.124-0.18,0.187c0.351-0.02,0.859-0.174,1.185,0.257
c0.103-0.114,0.27-0.167,0.505-0.164c-0.248-0.09-0.49-0.143-0.726-0.165c0.143-0.182,0.558-0.083,0.797-0.186
c-0.289-0.04-0.549-0.328-0.784-0.328c-0.53,0-0.62,0.348-1.296,0.214c0.313-0.429,0.996-0.837,1.947-0.723
c-0.468-0.187-0.89,0-1.265,0c-0.288-0.446,0.527-0.288,0.924-0.232c-0.279-0.149-0.527-0.195-0.744-0.143
c0.21-0.276,0.669-0.229,1.184-0.208c-0.053-0.189-0.273-0.332-0.549-0.35c0.121-0.125,0.48-0.141,0.478-0.336
c-0.855-0.024-1.46,0.255-2.356,0.171c0.177-0.05,0.363-0.09,0.558-0.12c-0.536-0.255-0.735,0.023-0.995,0.12
c-0.103,0.034-0.483-0.062-0.62-0.081c-0.837-0.107-1.206-0.006-2.142-0.449c-1.026-0.48-1.888-0.344-2.8-0.49
c-0.664-0.105-1.333-0.449-2.015-0.709c0.375-0.438,1.395-0.41,2.375-0.242c1.054,0.18,1.771-0.115,2.924,0.238
c1.169,0.359,1.885,0.524,2.979,0.695c0.31,0.046,1.101,0.27,1.194,0.07c0.068-0.136-0.022-0.301,0.108-0.418
c-0.214,0.232-0.604,0.388-1.234,0.148c0.862,0.057-0.264-0.167-0.524-0.272c0.356-0.031,0.902-0.04,1.361,0.099
c-0.17-0.077-0.338-0.16-0.505-0.244c0.208-0.031,0.44-0.031,0.691,0c-0.446-0.1-1.417-0.074-1.808-0.273
c0.425-0.006,0.679-0.13,1.209,0.016c0.307,0.087,0.521-0.198,0.124-0.267c-0.366-0.059-0.732-0.062-1.07-0.077
c-0.502-0.021-1.029-0.232-1.569-0.342c0.478-0.365,1.932,0.416,2.394,0c-0.195,0.022-1.625-0.09-0.877-0.272
c-0.54,0.024-1.333,0.295-2.065-0.024c0.298-0.124,0.623-0.227,1.023-0.261c0.496,0.105,0.567,0.019,0.21-0.257
c-0.623,0.077-1.091,0.716-2.049,0.369c0.524-0.081,0.685-0.432,1.203-0.518c0.474-0.081,1.24,0.074,1.562-0.149
c-1.429-0.248-2.508,0.241-3.854,0.173c0.732-0.489,2.639-0.039,3.11-0.719c-0.691-0.14-0.979,0.258-1.603,0.217
c-0.36-0.024-0.742-0.059-1.126-0.096c-0.247,0.165-0.59,0.278-0.979,0.326c-0.31,0.012-0.694-0.233-0.418-0.336
c0.273-0.105,0.819-0.005,1.229-0.006c-0.281-0.027-0.563-0.055-0.836-0.08c0.155-0.255,0.775-0.54,1.101-0.695
c-0.48,0.059-0.989,0.316-1.321,0.496c-0.239-0.624-1.838-1.352-0.651-1.386c-0.366,0-0.744-0.081-0.626,0.248
c0.105,0.301,0.478,0.372,0.766,0.648c0.8,0.772-1.299,0.363-0.53,0.812c-0.229,0.051-0.499-0.009-0.812-0.173
c0.105,0.443,0.837,0.458,0.273,0.713c-0.208,0.093-0.468,0.146-0.75,0.171c-0.617,0.053-1.46-0.007-2.278-0.338
c0.781,0.452-0.18,0.281-0.509,0.055c-0.288-0.197-0.53-0.449-0.905-0.502c0.217-0.325,0.999-0.456,1.578-0.508
c0.905-0.086,1.299-0.332,1.764-0.753c-0.806,0.27-1.079,0.937-2.564,0.521c0.096-0.276,0.632-0.372,0.983-0.483
c0.369-0.118,0.458-0.431,0.945-0.459c-0.75-0.292-1.42,0.173-2.22-0.199c0.682-0.369,2.186,0.09,3.06-0.099
c-0.508-0.056-0.992-0.078-1.522-0.173c0.391-0.118,0.515-0.413,0.893-0.543c-0.13,0.105-0.35,0.112-0.654,0.025
c0.521-0.034,0.534-0.48-0.31-0.669c-0.555-0.127-0.834,0.056-1.513-0.149c0.561,0.146,0.542-0.202,0.871-0.273
c-0.921-0.124,0.13-0.381,0.48-0.27c-0.316-0.257-2.539-0.682-1.851-0.794c-1.011-0.074,0.57-0.074,0.632-0.099
c-0.583-0.04-0.632-0.344-0.254-0.372c-0.217-0.021-1.374-0.282-0.626-0.344c-0.264-0.003-0.552-0.046-0.862-0.124
c0.192-0.086,0.31-0.226,0.341-0.396c-0.753,0.189-1.5,0.44-2.57,0.322c0.676-0.251,1.689-0.217,2.4-0.446
c-0.22,0.049-0.487,0.015-0.81-0.1c0.326-0.034,0.741,0.013,0.921-0.148c-0.326,0.037-0.682,0.062-1.141-0.1
c0.487,0.059,1.082,0.031,1.188-0.22c-0.334,0.115-1.395-0.599-1.758-0.273c-0.322-0.341,0.264-0.26,0.406-0.397
c-0.986-0.505-2.021-0.316-3.042-0.648c0.031-0.074,0.068-0.146,0.108-0.217c-0.536,0.04-0.756-0.419-0.567-0.54
c-0.391-0.016-0.437,0.235-0.955,0.108c0.31-0.121,1.067-0.021,0.896-0.425c-0.196-0.456-1.231-0.282-0.53-0.496
c-0.36-0.009-0.608,0.081-0.974,0.068c-0.292-0.013-1.284-0.344-0.583-0.366c-0.602-0.115-0.868,0.093-1.476-0.028
c0.019-0.083,0.04-0.164,0.071-0.242c-0.195,0.041-0.413,0.068-0.651,0.081c-0.285-0.443-1.531-0.186-1.95-0.245
c-0.028-0.09-0.062-0.18-0.096-0.27c-0.989-0.146-1.771-0.074-2.747-0.202c-0.685-0.09-1.699-0.276-1.829,0.065
c-0.558-0.84-2.917,0.115-3.153,0.108c-0.459-0.443-0.949-0.335-1.321-0.251c-0.164,0.037,0.074,0.242-0.198,0.22
c-0.211-0.041-0.372-0.041-0.493-0.003c-0.118,0.108-0.974-0.053-0.955,0.031c0.068,0.307-0.297,0.434-1.066,0.167
c0.013-0.162,0.112-0.279,0.294-0.354c-1.184-0.344-1.336,0.511-2.074,0.257c-1.172-0.409-1.02-0.227-1.758-0.149
c0.478,0.034,1.101,0.369,1.628,0.487c-0.471,0.254-1.823-0.108-2.635-0.124c-0.375-0.006-0.732,0.003-1.153-0.043
c-0.289-0.031-1.185-0.121-0.577-0.239c-0.676-0.019-1.892-0.22-2.834-0.595c0.363-0.273,1.457-0.484,2.254-0.471
c0.422,0.006,1.95,0.248,1.367-0.289c-1.314,0.115-2.796,0.115-4.57-0.161c-0.099-0.347,0.471-0.536,1.004-0.54
c-1.652-0.359-1.107,1.11-3.32,0.217c0.239-0.012,0.458-0.041,0.651-0.081c-0.335,0.028-0.868-0.115-1.166-0.056
c-1.752-1.2,11.112-0.13,11.815-0.596c-0.989,0.236-2.545-0.031-3.773,0.006c-0.769,0.022-1.656,0.189-2.855-0.223
c0.942-0.18,2.151-0.214,3.308-0.242c0.862-0.022,2.468,0.403,3.128,0.27c-0.387,0.043-1.02-0.384-0.595-0.372
c0.394,0.009,1.008,0.019,1.144-0.143c-1.06-0.701-2.031-0.496-2.48-0.354c-0.679,0.214-1.312,0.428-2.443,0.323
c-0.344-0.031-1.476-0.515-1.364-0.245c0.115,0.282,0.031,0.313-0.406,0.341c-0.915,0.059-2.059-0.006-3.317-0.254
c0.562-0.1,1.46-0.043,1.628-0.381c-0.443,0.22-1.436,0.322-2.496,0.056c0.552-0.068,1.07-0.17,1.492-0.326
c-1.299-0.077-1.907,0.372-3.401,0.137c0.14-0.078,0.292-0.149,0.45-0.217c-0.484,0.053-1.219,0.053-1.95-0.108
c-0.766-0.167-1.609-0.505-2.322-0.595c0.394,0.065,1.705-0.251,0.546-0.406c0.483-0.031,0.363-0.391,0.837-0.428
c0.515-0.041,1.091-0.031,1.557-0.105c0.71-0.118,1.578-0.013,2.344-0.065c0.375-0.028,0.45-0.273,0.818-0.326
C33.521,84,34.023,84,34.432,83.938c0.502-0.078,1.131-0.065,1.671-0.115c0.149-0.013,1.423-0.103,0.223-0.224
c0.782-0.254,2.019-0.195,3.026-0.294c0.769-0.074,1.299-0.242,2.254-0.183c1.714,0.108,2.576-0.137,3.789-0.381
c0.502-0.103,1.107-0.205,1.237-0.518c0.096-0.229-0.4-0.611-0.319-0.85c0.108-0.654,1.63-0.663,2.774-0.626
c-1.813-0.232-2.799,0.326-4.393,0.285c-0.669-0.016-1.445-0.183-2.034-0.124c-0.562,0.056-1.495-0.304-1.984-0.264
c-0.478-0.651,0.707-0.759,1.215-0.874c0.939-0.211,1.885-0.406,2.905-0.564c1.218-0.189,2.009-0.664,3.271-0.828
c0.443-0.059,0.927-0.5,1.78-0.133c0.599,0.257,1.197,0.617,1.789,0.846c-0.434-0.257-1.488-0.741-1.358-0.983
c0.18-0.329,1.057-0.217,1.727-0.18c1.29,0.071,3.181,0.502,3.919,0.115c-0.226-0.112-0.456-0.223-0.682-0.338
c-0.006-0.375,0.735-0.282,0.977-0.505c-1.885-0.211-0.899-1.014-0.323-1.085c0.933-0.108,2.146,0.022,3.274,0.084
c0.456,0.025,1.026-0.103,1.724,0.273c0.645,0.347,0.877,0.425,1.404,0.449c1.188,0.053,2.834,0.583,3.91,0.549
c-0.75-0.139-1.584-0.391-2.35-0.545c-0.502-0.103-0.936-0.037-1.491-0.22c-0.512-0.167-1.107-0.484-1.59-0.586
c-0.524-0.112-0.868-0.037-1.417-0.17c1.625-0.388,3.764-0.357,5.714-0.484c0.726-0.046,1.553,0.046,2.254-0.025
c0.285-0.028,2.453-0.257,1.364-0.201c0.406-0.034,3.808-0.534,4.502,0.511c0.44,0.67-0.552,0.744-0.326,1.314
c0.719,0.1,1.098-0.474,1.578-0.614c0.341-0.096,0.18-0.487,0.502-0.577c0.02-0.005,0.053-0.002,0.077-0.006
c-0.391-0.095-0.782-0.217-1.159-0.394c0.09-0.48,1.156-0.478,2.22-0.037c1.07,0.449,1.609,0.012,2.729,0.545
c-0.38,0.046-0.896,0.1-1.48,0.114c0.773,0.073,1.555,0.147,2.367,0.292c0.974,0.177,2.136,0.639,2.799,0.44
c-1.11-0.381-2.009-0.394-3.131-0.778c1.396,0.223,2.865,0.363,3.714,0.034c-0.735-0.108-1.727-0.208-2.583-0.521
c-0.53-0.192-0.775-0.607-1.355-0.831c1.268-0.152,2.691-0.217,4.347,0.034c1.234,0.186,2.669,0.319,3.975,0.642
c1.386,0.347,2.437,0.223,3.835,0.577c1.681,0.428,3.138,0.66,4.25,0.369c-0.245-0.009-1.358-0.037-0.676-0.304
c-0.279-0.021-0.558-0.043-0.84-0.068c0.162-0.229,0.642-0.195,0.989-0.27c-1.014-0.065-0.319-0.49-0.078-0.533
c0.403-0.078,0.943-0.013,1.46,0.046c1.243,0.143,2.505,0.248,3.575,0.183c-1.268-0.202-2.322-0.109-3.59-0.304
c0.871-0.226-0.54-0.257-0.803-0.294c-0.372-0.056-0.75-0.115-1.129-0.177c-0.75-0.121-1.513-0.26-2.3-0.44
c0.549-0.335,1.833-0.093,2.722-0.105c0.986-0.013,1.975-0.022,2.942-0.053c0.787-0.028,1.671-0.087,2.639,0.121
c0.328,0.068,1.605,1.12,1.203,0.273c-0.184-0.074-0.369-0.14-0.553-0.201c0.553-0.298,2.019-0.01,2.979,0.205
c0.484,0.105,1.058,0.242,1.575,0.515c0.307,0.161,0.797-0.217-0.08-0.316c0.812-0.326-1.768-0.549-2.09-0.558
c-0.952-0.025-1.973-0.14-2.93-0.174c-2.385-0.083-4.664-0.04-7.072-0.146c1.193,0.195-0.208,0.155-0.357,0.158
c-0.425,0.012-0.865,0.009-1.299,0.009c-0.694,0.006-1.429,0.037-2.332-0.236c0.45-0.136,1.193,0.041,1.798,0.068
c-0.542-0.17-1.029-0.223-1.436-0.202c0.607-0.416,2.031-0.096,3.138-0.009c0.93,0.068,1.82,0.093,2.759,0.177
c-1.128-0.198-2.139-0.22-3.333-0.505c1.188,0.146,1.987-0.127,3.119-0.043c1.091,0.081,2.127,0.093,3.19,0.143
c-0.828-0.133-1.503-0.034-2.399-0.27c0.759,0,1.615,0.103,2.279,0c-0.341-0.031-0.67-0.041-0.98-0.034
c0.45-0.229,1.315-0.192,2.043-0.143c0.304,0.021,2.347,0.288,1.33,0.446c1.182-0.272,3.141,0.341,4.526,0.301
c0.607-0.019,1.193-0.037,1.96,0.189c0.492,0.146,1.345,0.449,1.494,0.152c-0.394,0.083-1.005-0.323-1.473-0.372
c0.401-0.112,0.879-0.146,1.375-0.157c-0.121-0.016-0.232-0.019-0.361-0.045c1.327-0.115,2.958,0.103,4.12-0.205
c-0.468,0.056-3.969,0.174-4.384-0.642c1.069,0.102,1.879-0.087,2.803-0.158c0.881-0.068,2.127,0.403,2.974,0.294
c-0.356-0.152-0.691-0.242-1.008-0.27c1.058-0.425,2.933-0.065,4.328-0.121c0.428-0.016,1.141-0.177,1.562-0.115
c0.49,0.071,0.995,0.409,1.398,0.236c-0.205-0.068-0.406-0.137-0.611-0.205c2.06-0.18,4.366-0.021,6.595,0.034
c1.116,0.031,2.168-0.034,3.262-0.034c0.536,0,1.169,0.211,1.718,0.239c0.499,0.025,0.821-0.173,1.407,0
c-0.285-0.012-0.558,0-0.815,0.034c0.268,0.046,2.44,0.121,1.219,0c2.047,0.059,4.037-0.356,6.402,0.438
c-2.982,0.387-6.321,0.239-9.556,0.239c-1.463,0-2.877,0.074-4.244,0.201c-1.354,0.127-2.97-0.133-4.412-0.133
c0.624,0.062,1.185,0.009,1.895,0.27c-2.372,0-4.731,0.037-6.889,0.304c1.004,0.083-0.04,0.264-0.224,0.304
c1.073,0.006,1.848-0.356,2.964-0.341c0.165,0,1.216,0.242,1.287,0.167c0.267-0.273,0.651-0.264,1.185-0.273
c2.183-0.043,4.188-0.093,6.253-0.279c2.385-0.214,5.008,0.016,7.531,0.016c2.03,0,3.983-0.664,6.399,0.167
c-0.788,0.26-1.606,0.49-2.555,0.54c1.634,0.046,2.861-0.716,4.712-0.27c-0.09,0.105-0.186,0.208-0.288,0.304
c1.86-0.202,4.071-0.425,6.337,0.372c-0.523,0.334-1.51,0.304-2.16,0.499c-0.899,0.27-1.963,0.229-3.032,0.235
c-2.195,0.006-4.186,0.344-6.378,0.351c-2.267,0.009-4.632-0.155-6.803,0.006c-0.964,0.071-2.031-0.018-3.087-0.091
c0.244,0.057,0.49,0.132,0.74,0.252c-1.352,0.335-3.004,0.282-4.585,0.304c-0.71,0.012-2.744-0.074-2.469,0.741
c2.999-0.41,6.204-0.537,9.302-0.837c1.621-0.158,3.423-0.093,5.168-0.062c0.965,0.016,2.81-0.338,3.96,0.257
c-0.06,0.778-1.675,0.614-2.453,0.695c-0.979,0.099-2.402-0.031-3.075,0.421c0.288,0.009,0.592,0.065,0.911,0.167
c-0.238,0.031-0.489,0.041-0.753,0.034c1.299,0.183,2.434-0.285,3.581-0.381c1.165-0.093,2.191-0.344,3.407-0.36
c1.008-0.012,0.97-0.716,1.808-0.862c0.731-0.127,1.634-0.102,2.421-0.149c0.375-0.021,2.05,0.016,2.013,0.741
c-0.025,0.459-0.577,0.738-0.955,0.936c-0.651,0.344-1.485,0.478-2.155,0.8c-0.827,0.396-1.581,0.877-2.561,1.101
c1.048-0.031,0.006,0.279-0.158,0.372c0.881-0.229,1.488-0.524,2.248-0.875c0.713-0.326,1.845-0.425,2.657-0.679
c1.829-0.571,3.674-1.058,5.751-1.321c-0.372,0.071-0.741,0.22-0.707,0.607c0.75-0.053,1.352-0.335,2.121-0.359
c0.492-0.019,1.014,0.158,1.528,0.226c0.902,0.121,2.067,0.022,2.666-0.338c-0.998,0.065,0.232-0.257,0.636-0.236
c-0.14-0.111-0.276-0.226-0.412-0.338c2.285-0.186,4.585-0.356,7.034-0.167C168.128,76.345,170.205,76.751,172.189,76.894z"/>
<circle fill="none" cx="99.197" cy="100.656" r="82.295"/>
</svg>
</body>
</html>

429
boolean/booleanTests.js Normal file
View file

@ -0,0 +1,429 @@
paper.install(window);
function runTests() {
var caption, pathA, pathB, group;
var container = document.getElementById( 'container' );
caption = prepareTest( 'Overlapping circles', container );
pathA = new Path.Circle(new Point(80, 110), 50);
pathB = new Path.Circle(new Point(150, 110), 70);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Disjoint circles', container );
pathA = new Path.Circle(new Point(60, 110), 50);
pathB = new Path.Circle(new Point(170, 110), 50);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Overlapping circles - enveloping', container );
pathA = new Path.Circle(new Point(110, 110), 100);
pathB = new Path.Circle(new Point(120, 110), 60);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Polygon and square', container );
pathA = new Path.RegularPolygon(new Point(80, 110), 12, 80);
pathB = new Path.Rectangle(new Point(100, 80), [80, 80] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Circle and square (overlaps exactly on existing segments)', container );
pathA = new Path.Circle(new Point(110, 110), 80);
pathB = new Path.Rectangle(new Point(110, 110), [80, 80] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Circle and square (existing segments overlaps on curves)', container );
pathA = new Path.Circle(new Point(110, 110), 80);
pathB = new Path.Rectangle(new Point(110, 110), [100, 100] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Square and square (one segment overlaps on a line)', container );
pathA = new Path.Rectangle(new Point(80, 125), [50, 50] );
pathA.rotate( 45 );
pathB = new Path.Rectangle(new Point(pathA.segments[2].point.x, 110), [80, 80] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Rectangle and rectangle (overlaps exactly on existing curves)', container );
pathA = new Path.Rectangle(new Point(30.5, 50.5), [100, 150]);
pathB = new Path.Rectangle(new Point(130.5, 60.5), [100, 150]);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Circle and banana (multiple intersections within same curve segment)', container );
pathA = new Path.Circle(new Point(80, 110), 80);
pathB = new Path.Circle(new Point(130, 110), 80 );
pathB.segments[3].point = pathB.segments[3].point.add( [ 0, -120 ] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Overlapping stars 1', container );
pathA = new Path.Star(new Point(80, 110), 10, 20, 80);
pathB = new Path.Star(new Point(120, 110), 10, 30, 100);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Overlapping stars 2', container );
pathA = new Path.Star(new Point(110, 110), 20, 20, 80);
pathB = new Path.Star(new Point(110, 110), 6, 30, 100);
testBooleanStatic( pathA, pathB, caption );
// caption = prepareTest( 'Circles overlap exactly over each other', container );
// pathA = new Path.Circle(new Point(110, 110), 100);
// pathB = new Path.Circle(new Point(110, 110), 100 );
// // pathB.translate([0.5,0])
// testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Maximum possible intersections between 2 cubic bezier curve segments - 9', container );
pathA = new Path();
pathA.add( new Segment( [173, 44], [-281, 268], [-86, 152] ) );
pathA.add( new Segment( [47, 93], [-89, 100], [240, -239] ) );
pathA.closed = true;
pathB = pathA.clone();
pathB.rotate( -90 );
pathA.translate( [-10,0] );
pathB.translate( [10,0] );
testBooleanStatic( pathA, pathB, caption );
annotatePath( pathA, null, '#008' );
annotatePath( pathB, null, '#800' );
view.draw();
caption = prepareTest( 'SVG gears', container );
group = paper.project.importSVG( document.getElementById( 'svggears' ) );
pathA = group.children[0];
pathB = group.children[1];
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Glyphs imported from SVG', container );
group = paper.project.importSVG( document.getElementById( 'glyphsys' ) );
pathA = group.children[0];
pathB = group.children[1];
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 1', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = group.children[1];
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 2 - holes', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = new CompoundPath();
group.children[1].clockwise = true;
pathB.addChild(group.children[1]);
var npath = new Path.Circle([110, 110], 30);
pathB.addChild( npath );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 3 !', container );
group = paper.project.importSVG( document.getElementById( 'svggreenland' ) );
pathA = group.children[0];
pathB = group.children[1];
pathB.scale( 0.5, 1 ).translate( [25.5, 0] );
// pathA.scale( 2 );
// pathB.scale( 2 );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 4 - holes and islands 1', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = new CompoundPath();
group.children[1].clockwise = true;
pathB.addChild(group.children[1]);
var npath = new Path.Circle([40, 80], 20);
pathB.addChild( npath );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 5 - holes and islands 2', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = new CompoundPath();
group.children[1].clockwise = true;
pathB.addChild(group.children[1]);
var npath = new Path.Circle([40, 80], 20);
pathB.addChild( npath );
npath = new Path.Circle([120, 110], 30);
pathB.addChild( npath );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 6 - holes and islands 3', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = new CompoundPath();
var npath = new Path.Circle([110, 110], 100);
pathB.addChild( npath );
npath = new Path.Circle([110, 110], 60);
pathB.addChild( npath );
npath = new Path.Circle([110, 110], 30);
pathB.addChild( npath );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 6 - holes and islands 4 (curves overlap exactly on existing curves)', container );
pathA = new Path.Rectangle(new Point(50.5, 50.5), [100, 120]);
pathB = new CompoundPath();
pathB.addChild( new Path.Rectangle(new Point(140.5, 30.5), [100, 150]) );
pathB.addChild( new Path.Rectangle(new Point(150.5, 65.5), [50, 100]) );
// pathB = new Path.Rectangle(new Point(150.5, 80.5), [80, 80] );
testBooleanStatic( pathA, pathB, caption );
// // To resolve self intersection on a single path,
// // pass an empty second operand and do a Union operation
// caption = prepareTest( 'Self-intersecting paths 1 - Resolve self-intersection on single path', container );
// pathA = new Path.Star(new Point(110, 110), 10, 20, 80);
// pathA.smooth();
// pathB = new Path();
// testBooleanStatic( pathA, pathB, caption, false, true, true, true );
// caption = prepareTest( 'Self-intersecting paths 2 - Resolve self-intersecting CompoundPath', container );
// pathA = new CompoundPath();
// pathA.addChild( new Path.Circle([100, 110], 60) );
// pathA.addChild( new Path.Circle([160, 110], 30) );
// pathB = new Path();
// testBooleanStatic( pathA, pathB, caption, false, true, true, true );
// var tool = new Tool();
// tool.onMouseMove = function( e ){
// var hi = project.hitTest( e.point );
// if( hi ){
// var item = hi.item;
// if( item instanceof PathItem ){
// var txt = new PointText( e.point.add([0, -10]) );
// txt.justification = 'center';
// txt.content = item.id;
// txt.fillColor = '#000';
// txt.removeOnMove();
// }
// }
// };
window.a = pathA;
window.b = pathB;
// // pathA.selected = true;
// // pathA.fullySelected = true;
// // pathB.selected = true;
// pathA.style = pathStyleBoolean;
// pathB.style = pathStyleBoolean;
// // reorientCompoundPath( pathB )
// // var ixs = pathA.getIntersections( pathB );
// // ixs.map( function(a){ console.log( "( " + a.path.id + " , " + a.curve.index + " , "+ a.parameter +" )" );
// // markPoint( a.point, " " ) } );
// // ixs.map( function(a){ markPoint( a.point, " " ); } );
// // splitPath( ixs, true );
// // splitPath( ixs );
// // annotatePath( pathB )
// // pathB.translate( [ 300, 0 ] );
// // pathB.segments.filter( function(a){ return a._ixPair; } ).map(
// // function(a){ a._ixPair.getIntersection()._segment.selected = true; });
// console.time('unite');
// var nup = unite( pathA, pathB );
// console.timeEnd('unite');
// // nup.style = booleanStyle;
// window.p = nup;
// view.draw();
function prepareTest( testName, parentNode ){
console.log( '\n' + testName );
var caption = document.createElement('h3');
caption.appendChild( document.createTextNode( testName ) );
var canvas = document.createElement('CANVAS');
parentNode.appendChild( caption );
parentNode.appendChild( canvas );
paper.setup( canvas );
return caption;
}
}
var booleanStyle = {
fillColor: new Color( 1, 0, 0, 0.5 ),
strokeColor: new Color( 0, 0, 0 ),
strokeWidth: 1.5
};
var pathStyleNormal = {
strokeColor: new Color( 0, 0, 0 ),
fillColor: new Color( 0, 0, 0, 0.1 ),
strokeWidth: 1
};
var pathStyleBoolean = {
strokeColor: new Color( 0.8 ),
fillColor: new Color( 0, 0, 0, 0.0 ),
strokeWidth: 1
};
// Better if path1 and path2 fit nicely inside a 200x200 pixels rect
function testBooleanStatic( path1, path2, caption, noUnion, noIntersection, noSubtraction, _disperse ) {
// try{
path1.style = path2.style = pathStyleNormal;
if( !noUnion ) {
var _p1U = path1.clone().translate( [250, 0] );
var _p2U = path2.clone().translate( [250, 0] );
_p1U.style = _p2U.style = pathStyleBoolean;
console.time( 'Union' );
var boolPathU = unite( _p1U, _p2U );
console.timeEnd( 'Union' );
boolPathU.style = booleanStyle;
if( _disperse ){ disperse( boolPathU ); }
}
if( !noIntersection ) {
var _p1I = path1.clone().translate( [500, 0] );
var _p2I = path2.clone().translate( [500, 0] );
_p1I.style = _p2I.style = pathStyleBoolean;
console.time( 'Intersection' );
var boolPathI = intersect( _p1I, _p2I );
console.timeEnd( 'Intersection' );
boolPathI.style = booleanStyle;
}
if( !noSubtraction ) {
var _p1S = path1.clone().translate( [750, 0] );
var _p2S = path2.clone().translate( [750, 0] );
_p1S.style = _p2S.style = pathStyleBoolean;
console.time( 'Subtraction' );
var boolPathS = subtract( _p1S, _p2S );
console.timeEnd( 'Subtraction' );
boolPathS.style = booleanStyle;
}
if( !noSubtraction ) {
var _p1E = path1.clone().translate( [250, 220] );
var _p2E = path2.clone().translate( [250, 220] );
_p1E.style = _p2E.style = pathStyleBoolean;
console.time( 'Exclusion' );
var boolPathE = exclude( _p1E, _p2E );
console.timeEnd( 'Exclusion' );
boolPathE.style = booleanStyle;
}
if( !noSubtraction && !noIntersection) {
var _p1D = path1.clone().translate( [500, 220] );
var _p2D = path2.clone().translate( [500, 220] );
_p1D.style = _p2D.style = pathStyleBoolean;
console.time( 'Division' );
var boolPathD = divide( _p1D, _p2D );
console.timeEnd( 'Division' );
disperse( boolPathD );
boolPathD.style = booleanStyle;
}
// } catch( e ){
// console.error( e.name + ": " + e.message );
// if( caption ) { caption.className += ' error'; }
// // paper.project.view.element.className += ' hide';
// } finally {
console.timeEnd( 'Union' );
console.timeEnd( 'Intersection' );
console.timeEnd( 'Subtraction' );
view.draw();
// }
}
function disperse( path, distance ){
distance = distance || 10;
if( ! path instanceof CompoundPath || ! path instanceof Group ){ return; }
var center = path.bounds.center;
var children = path.children, i ,len;
for (i = 0, len = children.length; i < len; i++) {
var cCenter = children[i].bounds.center;
var vec = cCenter.subtract( center );
vec = ( vec.isClose( [0,0], 0.5 ) )? vec : vec.normalize( distance );
children[i].translate( vec );
}
}
// ==============================================================
// On screen debug helpers
function markPoint( pnt, t, c, tc, remove ) {
if( !pnt ) return;
c = c || '#000';
if( remove === undefined ){ remove = true; }
var cir = new Path.Circle( pnt, 2 );
cir.style.fillColor = c;
cir.style.strokeColor = tc;
if( t !== undefined || t !== null ){
var text = new PointText( pnt.add([0, -3]) );
text.justification = 'center';
text.fillColor = c;
text.content = t;
if( remove ){
text.removeOnMove();
}
}
if( remove ) {
cir.removeOnMove();
}
}
function annotatePath( path, t, c, tc, remove ) {
if( !path ) return;
var crvs = path.curves;
for (i = crvs.length - 1; i >= 0; i--) {
annotateCurve( crvs[i], t, c, tc, remove );
}
var segs = path.segments;
for (i = segs.length - 1; i >= 0; i--) {
annotateSegment( segs[i], t, c, tc, remove, true );
}
}
function annotateSegment( s, t, c, tc, remove, skipCurves ) {
if( !s ) return;
c = c || '#000';
tc = tc || '#ccc';
t = t || s.index;
if( remove === undefined ){ remove = true; }
var crv = s.curve;
var t1 = crv.getNormal( 0 ).normalize( 10 );
var p = s.point.clone().add( t1 );
var cir = new Path.Circle( s.point, 2 );
cir.style.fillColor = c;
cir.style.strokeColor = tc;
var text = new PointText( p );
text.justification = 'center';
text.fillColor = c;
text.content = t;
if( remove ) {
cir.removeOnMove();
text.removeOnMove();
}
if( !skipCurves ) {
annotateCurve( s.curveIn, null, c, tc, remove );
annotateCurve( s.curveOut, null, c, tc, remove );
}
}
function annotateCurve( crv, t, c, tc, remove ) {
if( !crv ) return;
c = c || '#000';
tc = tc || '#ccc';
t = t || crv.index;
if( remove === undefined ){ remove = true; }
var p = crv.getPoint( 0.57 );
var t1 = crv.getTangent( 0.57 ).normalize( -10 );
var p2 = p.clone().add( t1 );
var l = new Path.Line( p, p2 ).rotate( 30, p );
var l2 = new Path.Line( p, p2 ).rotate( -30, p );
p = crv.getPoint( 0.43 );
var cir = new Path.Circle( p, 8 );
var text = new PointText( p.subtract( [0, -4] ) );
text.justification = 'center';
text.fillColor = tc;
text.content = t;
l.style.strokeColor = l2.style.strokeColor = c;
cir.style.fillColor = c;
if( remove ) {
l.removeOnMove();
l2.removeOnMove();
cir.removeOnMove();
text.removeOnMove();
}
}

View file

@ -0,0 +1,366 @@
paper.install(window);
function runTests() {
var caption, pathA, pathB, group;
var container = document.getElementById( 'container' );
caption = prepareTest( 'Overlapping circles', container );
pathA = new Path.Circle(new Point(80, 110), 50);
pathB = new Path.Circle(new Point(150, 110), 70);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Disjoint circles', container );
pathA = new Path.Circle(new Point(60, 110), 50);
pathB = new Path.Circle(new Point(170, 110), 50);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Overlapping circles - enveloping', container );
pathA = new Path.Circle(new Point(110, 110), 100);
pathB = new Path.Circle(new Point(120, 110), 60);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Polygon and square', container );
pathA = new Path.RegularPolygon(new Point(80, 110), 12, 80);
pathB = new Path.Rectangle(new Point(100, 80), [80, 80] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Circle and square (overlaps exactly on existing segments)', container );
pathA = new Path.Circle(new Point(110, 110), 80);
pathB = new Path.Rectangle(new Point(110, 110), [80, 80] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Circle and square (existing segments overlaps on curves)', container );
pathA = new Path.Circle(new Point(110, 110), 80);
pathB = new Path.Rectangle(new Point(110, 110), [100, 100] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Square and square (one segment overlaps on a line)', container );
pathA = new Path.Rectangle(new Point(80, 125), [50, 50] );
pathA.rotate( 45 );
pathB = new Path.Rectangle(new Point(pathA.segments[2].point.x, 110), [80, 80] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Rectangle and rectangle (overlaps exactly on existing curves)', container );
pathA = new Path.Rectangle(new Point(30.5, 50.5), [100, 150]);
pathB = new Path.Rectangle(new Point(130.5, 60.5), [100, 150]);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Circle and banana (multiple intersections within same curve segment)', container );
pathA = new Path.Circle(new Point(80, 110), 80);
pathB = new Path.Circle(new Point(130, 110), 80 );
pathB.segments[3].point = pathB.segments[3].point.add( [ 0, -120 ] );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Overlapping stars 1', container );
pathA = new Path.Star(new Point(80, 110), 10, 20, 80);
pathB = new Path.Star(new Point(120, 110), 10, 30, 100);
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Overlapping stars 2', container );
pathA = new Path.Star(new Point(110, 110), 20, 20, 80);
pathB = new Path.Star(new Point(110, 110), 6, 30, 100);
testBooleanStatic( pathA, pathB, caption );
// caption = prepareTest( 'Circles overlap exactly over each other', container );
// pathA = new Path.Circle(new Point(110, 110), 100);
// pathB = new Path.Circle(new Point(110, 110), 100 );
// // pathB.translate([0.5,0])
// testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Maximum possible intersections between 2 cubic bezier curve segments - 9', container );
pathA = new Path();
pathA.add( new Segment( [173, 44], [-281, 268], [-86, 152] ) );
pathA.add( new Segment( [47, 93], [-89, 100], [240, -239] ) );
pathA.closed = true;
pathB = pathA.clone();
pathB.rotate( -90 );
pathA.translate( [-10,0] );
pathB.translate( [10,0] );
testBooleanStatic( pathA, pathB, caption );
annotatePath( pathA, null, '#008' );
annotatePath( pathB, null, '#800' );
view.draw();
caption = prepareTest( 'SVG gears', container );
group = paper.project.importSVG( document.getElementById( 'svggears' ) );
pathA = group.children[0];
pathB = group.children[1];
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'Glyphs imported from SVG', container );
group = paper.project.importSVG( document.getElementById( 'glyphsys' ) );
pathA = group.children[0];
pathB = group.children[1];
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 1', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = group.children[1];
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 2 - holes', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = new CompoundPath();
group.children[1].clockwise = true;
pathB.addChild(group.children[1]);
var npath = new Path.Circle([110, 110], 30);
pathB.addChild( npath );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 3 !', container );
group = paper.project.importSVG( document.getElementById( 'svggreenland' ) );
pathA = group.children[0];
pathB = group.children[1];
pathB.scale( 0.5, 1 ).translate( [25.5, 0] );
// pathA.scale( 2 );
// pathB.scale( 2 );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 4 - holes and islands 1', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = new CompoundPath();
group.children[1].clockwise = true;
pathB.addChild(group.children[1]);
var npath = new Path.Circle([40, 80], 20);
pathB.addChild( npath );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 5 - holes and islands 2', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = new CompoundPath();
group.children[1].clockwise = true;
pathB.addChild(group.children[1]);
var npath = new Path.Circle([40, 80], 20);
pathB.addChild( npath );
npath = new Path.Circle([120, 110], 30);
pathB.addChild( npath );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 6 - holes and islands 3', container );
group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
pathA = group.children[0];
pathB = new CompoundPath();
var npath = new Path.Circle([110, 110], 100);
pathB.addChild( npath );
npath = new Path.Circle([110, 110], 60);
pathB.addChild( npath );
npath = new Path.Circle([110, 110], 30);
pathB.addChild( npath );
testBooleanStatic( pathA, pathB, caption );
caption = prepareTest( 'CompoundPaths 6 - holes and islands 4 (curves overlap exactly on existing curves)', container );
pathA = new Path.Rectangle(new Point(50.5, 50.5), [100, 120]);
pathB = new CompoundPath();
pathB.addChild( new Path.Rectangle(new Point(140.5, 30.5), [100, 150]) );
pathB.addChild( new Path.Rectangle(new Point(150.5, 60.5), [50, 100]) );
// pathB = new Path.Rectangle(new Point(150.5, 80.5), [80, 80] );
testBooleanStatic( pathA, pathB, caption );
// To resolve self intersection on a single path,
// pass an empty second operand and do a Union operation
caption = prepareTest( 'Self-intersecting paths 1 - Resolve self-intersection on single path', container );
pathA = new Path.Star(new Point(110, 110), 10, 20, 80);
pathA.smooth();
pathB = new Path();
testBooleanStatic( pathA, pathB, caption, false, true, true, true );
caption = prepareTest( 'Self-intersecting paths 2 - Resolve self-intersecting CompoundPath', container );
pathA = new CompoundPath();
pathA.addChild( new Path.Circle([100, 110], 60) );
pathA.addChild( new Path.Circle([160, 110], 30) );
pathB = new Path();
testBooleanStatic( pathA, pathB, caption, false, true, true, true );
window.a = pathA;
window.b = pathB;
function prepareTest( testName, parentNode ){
console.log( '\n' + testName );
var caption = document.createElement('h3');
caption.appendChild( document.createTextNode( testName ) );
var canvas = document.createElement('CANVAS');
parentNode.appendChild( caption );
parentNode.appendChild( canvas );
paper.setup( canvas );
return caption;
}
}
var booleanStyle = {
fillColor: new Color( 1, 0, 0, 0.5 ),
strokeColor: new Color( 0, 0, 0 ),
strokeWidth: 1.5
};
var pathStyleNormal = {
strokeColor: new Color( 0, 0, 0 ),
fillColor: new Color( 0, 0, 0, 0.1 ),
strokeWidth: 1
};
var pathStyleBoolean = {
strokeColor: new Color( 0.8 ),
fillColor: new Color( 0, 0, 0, 0.0 ),
strokeWidth: 1
};
// Better if path1 and path2 fit nicely inside a 200x200 pixels rect
function testBooleanStatic( path1, path2, caption, noUnion, noIntersection, noSubtraction, _disperse ) {
// try{
path1.style = path2.style = pathStyleNormal;
if( !noUnion ) {
var _p1U = path1.clone().translate( [250, 0] );
var _p2U = path2.clone().translate( [250, 0] );
_p1U.style = _p2U.style = pathStyleBoolean;
console.time( 'Union' );
var boolPathU = _p1U.unite( _p2U );
console.timeEnd( 'Union' );
boolPathU.style = booleanStyle;
if( _disperse ){ disperse( boolPathU ); }
window.p = boolPathU;
}
if( !noIntersection ) {
var _p1I = path1.clone().translate( [500, 0] );
var _p2I = path2.clone().translate( [500, 0] );
_p1I.style = _p2I.style = pathStyleBoolean;
console.time( 'Intersection' );
var boolPathI = _p1I.intersect( _p2I );
console.timeEnd( 'Intersection' );
if( _disperse ){ disperse( boolPathI ); }
boolPathI.style = booleanStyle;
}
if( !noSubtraction ) {
var _p1S = path1.clone().translate( [750, 0] );
var _p2S = path2.clone().translate( [750, 0] );
_p1S.style = _p2S.style = pathStyleBoolean;
console.time( 'Subtraction' );
var boolPathS = _p1S.subtract( _p2S );
console.timeEnd( 'Subtraction' );
if( _disperse ){ disperse( boolPathS ); }
boolPathS.style = booleanStyle;
}
// } catch( e ){
// console.error( e.name + ": " + e.message );
// if( caption ) { caption.className += ' error'; }
// // paper.project.view.element.className += ' hide';
// } finally {
console.timeEnd( 'Union' );
console.timeEnd( 'Intersection' );
console.timeEnd( 'Subtraction' );
view.draw();
// }
}
function disperse( path, distance ){
distance = distance || 10;
if( ! path instanceof CompoundPath ){ return; }
var center = path.bounds.center;
var children = path.children, i ,len;
for (i = 0, len = children.length; i < len; i++) {
var cCenter = children[i].bounds.center;
var vec = cCenter.subtract( center );
vec = ( vec.isClose( [0,0], 0.5 ) )? vec : vec.normalize( distance );
children[i].translate( vec );
}
}
// ==============================================================
// On screen debug helpers
function markPoint( pnt, t, c, tc, remove ) {
if( !pnt ) return;
c = c || '#000';
if( remove === undefined ){ remove = true; }
var cir = new Path.Circle( pnt, 2 );
cir.style.fillColor = c;
cir.style.strokeColor = tc;
if( t !== undefined || t !== null ){
var text = new PointText( pnt.add([0, -3]) );
text.justification = 'center';
text.fillColor = c;
text.content = t;
if( remove ){
text.removeOnMove();
}
}
if( remove ) {
cir.removeOnMove();
}
}
function annotatePath( path, t, c, tc, remove ) {
if( !path ) return;
var crvs = path.curves;
for (i = crvs.length - 1; i >= 0; i--) {
annotateCurve( crvs[i], t, c, tc, remove );
}
var segs = path.segments;
for (i = segs.length - 1; i >= 0; i--) {
annotateSegment( segs[i], t, c, tc, remove, true );
}
}
function annotateSegment( s, t, c, tc, remove, skipCurves ) {
if( !s ) return;
c = c || '#000';
tc = tc || '#ccc';
t = t || s.index;
if( remove === undefined ){ remove = true; }
var crv = s.curve;
var t1 = crv.getNormal( 0 ).normalize( 10 );
var p = s.point.clone().add( t1 );
var cir = new Path.Circle( s.point, 2 );
cir.style.fillColor = c;
cir.style.strokeColor = tc;
var text = new PointText( p );
text.justification = 'center';
text.fillColor = c;
text.content = t;
if( remove ) {
cir.removeOnMove();
text.removeOnMove();
}
if( !skipCurves ) {
annotateCurve( s.curveIn, null, c, tc, remove );
annotateCurve( s.curveOut, null, c, tc, remove );
}
}
function annotateCurve( crv, t, c, tc, remove ) {
if( !crv ) return;
c = c || '#000';
tc = tc || '#ccc';
t = t || crv.index;
if( remove === undefined ){ remove = true; }
var p = crv.getPoint( 0.57 );
var t1 = crv.getTangent( 0.57 ).normalize( -10 );
var p2 = p.clone().add( t1 );
var l = new Path.Line( p, p2 ).rotate( 30, p );
var l2 = new Path.Line( p, p2 ).rotate( -30, p );
p = crv.getPoint( 0.43 );
var cir = new Path.Circle( p, 8 );
var text = new PointText( p.subtract( [0, -4] ) );
text.justification = 'center';
text.fillColor = tc;
text.content = t;
l.style.strokeColor = l2.style.strokeColor = c;
cir.style.fillColor = c;
if( remove ) {
l.removeOnMove();
l2.removeOnMove();
cir.removeOnMove();
text.removeOnMove();
}
}

63
boolean/mpatch.js Normal file
View file

@ -0,0 +1,63 @@
paper.PathItem.prototype.getIntersections = function(path) {
// First check the bounds of the two paths. If they don't intersect,
// we don't need to iterate through their curves.
if (!this.getBounds().touches(path.getBounds()))
return [];
var locations = [],
curves1 = this.getCurves(),
curves2 = path.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 curve = curves1[i],
values1 = curve.getValues();
for (var j = 0; j < length2; j++)
Curve.getIntersections(values1, values2[j], curve, curves2[j], locations);
}
return locations;
};
paper.Curve.getIntersections = function(v1, v2, curve, curve2, locations) {
var bounds1 = this.getBounds(v1),
bounds2 = this.getBounds(v2);
if (bounds1.touches(bounds2)) {
// See if both curves are flat enough to be treated as lines, either
// because they have no control points at all, or are "flat enough"
if ((this.isLinear(v1)
|| this.isFlatEnough(v1, /*#=*/ Numerical.TOLERANCE))
&& (this.isLinear(v2)
|| this.isFlatEnough(v2, /*#=*/ Numerical.TOLERANCE))) {
// See if the parametric equations of the lines interesct.
var point = new Line(v1[0], v1[1], v1[6], v1[7], false)
.intersect(new Line(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.
var cloc = new CurveLocation(curve, null, point);
var cloc2 = new CurveLocation(curve2, null, point)
cloc2._ixPair = cloc;
cloc._ixPair = cloc2;
locations.push( cloc );
}
}
} else {
// Subdivide both curves, and see if they intersect.
var v1s = this.subdivide(v1),
v2s = this.subdivide(v2);
for (var i = 0; i < 2; i++)
for (var j = 0; j < 2; j++)
this.getIntersections(v1s[i], v2s[j], curve, curve2, locations);
}
}
return locations;
};