diff --git a/fatline/intersectStudy.html b/fatline/intersectStudy.html
index 023af4e7..8fac24ac 100644
--- a/fatline/intersectStudy.html
+++ b/fatline/intersectStudy.html
@@ -1,464 +1,469 @@
-
-
-
-
- Poly-bézier path Intersection Study
-
-
-
-
-
-
-
-
-
Cubic bézier fat-line clipping study (using paperjs)
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Poly-bézier path Intersection Study
+
+
+
+
+
+
+
+
+
Cubic bézier fat-line clipping study (using paperjs)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/fatline/intersectTests.js b/fatline/intersectTests.js
index 17dc3001..b71d1e54 100644
--- a/fatline/intersectTests.js
+++ b/fatline/intersectTests.js
@@ -1,560 +1,660 @@
-
-paper.install(window);
-
-/**
- * http://stackoverflow.com/questions/6875625/does-javascript-provide-a-high-resolution-timer
- */
-if (window.performance && window.performance.now) {
- console.log("Using high performance timer");
- getTimestamp = function() { return window.performance.now(); };
-} else {
- if (window.performance && window.performance.webkitNow) {
- console.log("Using webkit high performance timer");
- getTimestamp = function() { return window.performance.webkitNow(); };
- } else {
- console.log("Using low performance timer");
- getTimestamp = function() { return new Date().getTime(); };
- }
-}
-
-function runTests() {
- var caption, pathA, pathB, group, testdata = [], testQueued = 0, testExecuted = 0;;
-
- var container = document.getElementById( 'container' );
-
- function runTest(testName, handler) {
- var caption = document.createElement('h3');
- var canvas = document.createElement('canvas');
- caption.appendChild(document.createTextNode(testName));
- container.appendChild(caption);
- container.appendChild(canvas);
- ++testQueued;
- setTimeout(function() {
- console.log('\n' + testName);
- paper.setup(canvas);
- var paths = handler();
- testIntersections(paths[0], paths[1], caption, testName, testdata);
- testExecuted++;
- if( testExecuted === testQueued ){
- plotData();
- }
- }, 0);
- return caption;
- }
-
- var caption = document.createElement('h3');
- caption.appendChild(document.createTextNode("Randomised tests"));
- container.appendChild(caption);
- var count = 100, randomData = [];
- while( count-- ){
-
- }
-
- runTest('Overlapping circles', function(){
- pathA = new Path.Circle(new Point(80, 110), 50);
- pathB = new Path.Circle(new Point(150, 110), 70);
- return [pathA, pathB];
- });
-
- runTest('Polygon and square', function(){
- pathA = new Path.RegularPolygon(new Point(80, 110), 12, 80);
- pathB = new Path.Rectangle(new Point(100, 80), [80, 80] );
- return [pathA, pathB];
- });
-
- runTest('Circle and square (overlaps exactly on existing segments)', function(){
- pathA = new Path.Circle(new Point(110, 110), 80);
- pathB = new Path.Rectangle(new Point(110, 110), [80, 80] );
- return [pathA, pathB];
- });
-
- runTest('Circle and square (existing segments overlaps on curves)', function(){
- pathA = new Path.Circle(new Point(110, 110), 80);
- pathB = new Path.Rectangle(new Point(110, 110), [100, 100] );
- return [pathA, pathB];
- });
-
- runTest('Square and square (one segment overlaps on a line)', function(){
- 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] );
- return [pathA, pathB];
- });
-
- runTest('Rectangle and rectangle (overlaps exactly on existing curves)', function(){
- pathA = new Path.Rectangle(new Point(30.5, 50.5), [100, 150]);
- pathB = new Path.Rectangle(new Point(130.5, 60.5), [100, 150]);
- return [pathA, pathB];
- });
-
- runTest('Circle and banana (multiple intersections within same curve segment)', function(){
- 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 ] );
- return [pathA, pathB];
- });
-
- runTest('Overlapping stars 1', function(){
- pathA = new Path.Star(new Point(80, 110), 10, 20, 80);
- pathB = new Path.Star(new Point(120, 110), 10, 30, 100);
- return [pathA, pathB];
- });
-
- runTest('Overlapping stars 2', function(){
- pathA = new Path.Star(new Point(110, 110), 20, 20, 80);
- pathB = new Path.Star(new Point(110, 110), 6, 30, 100);
- return [pathA, pathB];
- });
-
- runTest('Maximum possible intersections between 2 cubic bezier curve segments - 9', function(){
- 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] );
- return [pathA, pathB];
- });
-
- runTest('SVG gears', function(){
- group = paper.project.importSVG( document.getElementById( 'svggears' ) );
- pathA = group.children[0];
- pathB = group.children[1];
- return [pathA, pathB];
- });
-
- runTest('Glyphs imported from SVG', function(){
- group = paper.project.importSVG( document.getElementById( 'glyphsys' ) );
- pathA = group.children[0];
- pathB = group.children[1];
- return [pathA, pathB];
- });
-
- runTest('CompoundPaths 1', function(){
- group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
- pathA = group.children[0];
- pathB = group.children[1];
- return [pathA, pathB];
- });
-
- runTest('CompoundPaths 2 - holes', function(){
- 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 );
- return [pathA, pathB];
- });
-
- runTest('CompoundPaths 3 !', function(){
- 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 );
- return [pathA, pathB];
- });
-
- runTest('CompoundPaths 4 - holes and islands 1', function(){
- 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 );
- return [pathA, pathB];
- });
-
- runTest('CompoundPaths 5 - holes and islands 2', function(){
- 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 );
- return [pathA, pathB];
- });
-
- runTest('CompoundPaths 6 - holes and islands 3', function(){
- 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 );
- return [pathA, pathB];
- });
-
- runTest('CompoundPaths 6 - holes and islands 4 (curves overlap exactly on existing curves)', function(){
- 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] );
- return [pathA, pathB];
- });
-
-
- // Do randomised tests
-
-
- // Plot the run times
- function plotData(){
- prepareTest( 'Results', container, true );
- var x = 80.5, y = 15.5, width = 500, height = 190, i, txt, ny,
- yy = y + height, xx = x + width;
- var ppaperfill = new Path(), pfatfill = new Path();
- var ppaper = new Path(), pfat = new Path();
- var max = testdata.reduce(function( a, b ){ return Math.max( a, b.paperTime + b.fatTime ); }, 0) + 20;
- var vscale = height / max, hscale = width / testdata.length;
- var caxes = '#999', ctxt = '#222', ctxt2 = '#555', cpaper = '#268BD2', cpaperfill ='#B5E1FF',
- cfat = '#D33682', cfatfill = '#FFADD4';
- new Path.Line( x, yy, xx, yy ).style.strokeColor = caxes;
- new Path.Line( x, yy, x, y ).style.strokeColor = caxes;
- for( i = 0; i < 10 ; i++ ){
- ny = yy - vscale * max * i / 10;
- new Path.Line( x, ny, x-5, ny ).style.strokeColor = caxes;
- txt = new PointText( [x-10, ny] );
- txt.justification = 'right';
- txt.fillColor = (i%2)? ctxt: ctxt2;
- txt.content = (max * i / 10).toFixed(1) + ((!i)? ' ms' : '');
- }
- ppaperfill.add( new Segment( x, yy ) );
- pfatfill.add( new Segment( x, yy ) );
- var vx = x, clr = ctxt;
- var coords = [], avgPaper = 0, avgFat = 0,
- maxSpeedup = -Infinity, minSpeedup = Infinity, avgSpeedup = 0;
- testdata.map(function(data){
- avgPaper += data.paperTime;
- ny = yy - (data.paperTime + data.fatTime) * vscale;
- ppaper.add( new Segment([vx, ny]) );
- ppaperfill.add( new Segment([vx, ny]) );
- var np = new Point( vx, ny );
- np._data = data;
- np._datatype = 'paper';
- coords.push( np );
- avgFat += data.fatTime;
- ny = yy - (data.fatTime) * vscale;
- pfat.add( new Segment([vx, ny]) );
- pfatfill.add( new Segment([vx, ny]) );
- np = new Point( vx, ny );
- np._data = data;
- np._datatype = 'fat';
- coords.push( np );
-
- var speedup = data.paperTime / data.fatTime;
- if( speedup > maxSpeedup ) maxSpeedup = speedup;
- if( speedup < minSpeedup ) minSpeedup = speedup;
- avgSpeedup += speedup;
-
- new Path.Line( vx, yy, vx, yy + 5 ).style.strokeColor = caxes;
- txt = new PointText( [vx, yy+18] );
- txt.justification = 'left';
- txt.fillColor = clr;
- txt.content = data.name;
- txt.rotate( 30, new Point(vx, yy+10) );
-
- if( !data.success ){
- var p = new Path.Line( vx, y, vx, yy );
- p.style.strokeWidth = 5;
- p.style.strokeColor = '#f00';
- }
-
- clr = ( clr === ctxt )? ctxt2 : ctxt;
- vx += hscale;
- });
- ppaper.style.strokeWidth = 2;
- ppaper.style.strokeColor = cpaper;
- ppaperfill.add( new Segment( vx-hscale, yy ) );
- ppaperfill.closed = true;
- ppaperfill.style.fillColor = cpaperfill;
- pfat.style.strokeWidth = 2;
- pfat.style.strokeColor = cfat;
- pfatfill.add( new Segment( vx-hscale, yy ) );
- pfatfill.closed = true;
- pfatfill.style.fillColor = cfatfill;
-
- avgPaper/= testdata.length;
- avgFat/= testdata.length;
- avgSpeedup = Math.round(avgSpeedup / testdata.length);
- maxSpeedup = Math.round( maxSpeedup );
- minSpeedup = Math.round( minSpeedup );
- ny = Math.round(yy - avgPaper * vscale) + 0.5;
- new Path.Line(x, ny, xx, ny).style.strokeColor = cpaper;
- txt = new PointText( [xx, ny] );
- txt.justification = 'right';
- txt.fillColor = cpaper;
- txt.content = avgPaper.toFixed(1);
- ny = Math.round(yy - avgFat * vscale) + 0.5;
- new Path.Line(x, ny, xx, ny).style.strokeColor = cfat;
- txt = new PointText( [xx, ny] );
- txt.justification = 'right';
- txt.fillColor = cfat;
- txt.content = avgFat.toFixed(1);
-
- txt = new PointText([610, 75]);
- txt.justification = 'center';
- txt.fillColor = '#000';
- txt.content = 'fatline vs subdiv';
- new Path.Rectangle( [600, 90], [20, 100] ).style = { fillColor: '#ccc', strokeColor: '#000' };
- ny = 190 - (avgSpeedup - minSpeedup) * 100.0 / (maxSpeedup - minSpeedup);
- new Path.Line( [600, ny], [620, ny] ).style = { strokeWidth: 2, strokeColor: '#000' };
- txt = new PointText([630, 95]);
- txt.fillColor = '#000';
- txt.content = maxSpeedup;
- txt = new PointText([630, 195]);
- txt.fillColor = '#000';
- txt.content = minSpeedup;
- txt = new PointText([630, ny+5]);
- txt.fillColor = '#000';
- txt.content = avgSpeedup + ' times';
-
- var tool = new Tool();
- tool.onMouseMove = function( e ){
- var len = coords.length;
- var data = null, dist = Infinity, dst, pnt = null, type = 'paper';
- while( len-- ){
- dst = e.point.getDistance( coords[len], true );
- if( dst < dist ){
- pnt = coords[len];
- data = coords[len]._data;
- type = coords[len]._datatype;
- dist = dst;
- }
- }
- if( dist > 500 ){ return; }
- if( pnt && data ){
- var p = new Path.Line( pnt.x+0.5, y, pnt.x+0.5, yy );
- p.style.strokeColor = '#000';
- p.removeOnMove();
- p = new Path.Circle( pnt, 3 );
- p.style.fillColor = (type === 'fat')? '#D33682' :'#268BD2';
- p.removeOnMove();
- var txt = new PointText( [ 500, 20 ] );
- txt.content = 'subdiv : ' + data.paperTime.toFixed(1) + ' ms';
- txt.fillColor = '#222';
- txt.removeOnMove();
- txt = new PointText( [ 500, 36 ] );
- txt.content = 'fatline : ' + data.fatTime.toFixed(1) + ' ms';
- txt.fillColor = '#222';
- txt.removeOnMove();
- }
- };
- }
-
-
- function prepareTest( testName, parentNode, _big ){
- console.log( '\n' + testName );
- var caption = document.createElement('h3');
- caption.appendChild( document.createTextNode( testName ) );
- var canvas = document.createElement('CANVAS');
- if(_big){
- canvas.className += ' big';
- }
- parentNode.appendChild( caption );
- parentNode.appendChild( canvas );
- paper.setup( canvas );
- return caption;
- }
-}
-
-var pathStyleIx = {
- fillColor: new Color( 0.8, 0, 0 ),
- strokeColor: new Color( 0, 0, 0 )
-};
-
-var pathStyleNormal = {
- strokeColor: new Color( 0, 0, 0 ),
- fillColor: new Color( 0, 0, 0, 0.1 ),
- strokeWidth: 1
-};
-
-var pathStyleBoolean = {
- strokeColor: new Color( 0,0,0,0.4 ),
- fillColor: new Color( 0, 0, 0, 0.0 ),
- strokeWidth: 1
-};
-
-
-// Better if path1 and path2 fit nicely inside a 200x200 pixels rect
-function testIntersections( path1, path2, caption, testname, testdata, nomark) {
- var i, l, maxCount = 10, count = maxCount, st, t1, t2,
- ixsPaper, ixsFatline, success = false;
- try{
- path1.style = path2.style = pathStyleNormal;
-
- if( !nomark ) console.time('paperjs x ' + maxCount);
- st = getTimestamp();
- while(count--){
- ixsPaper = path1.getIntersections( path2 );
- }
- t1 = (getTimestamp() - st) / maxCount;
- if( !nomark ) console.timeEnd('paperjs x ' + maxCount);
-
- count = maxCount;
- if( !nomark ) console.time('fatline x ' + maxCount);
- st = getTimestamp();
- while(count--){
- ixsFatline = getIntersections2( path1, path2 );
- }
- t2 = (getTimestamp() - st) / maxCount;
- if( !nomark ) console.timeEnd('fatline x ' + maxCount);
-
- var equal = true;
- for(i=0, l=ixsFatline.length; 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();
- }
-}
+
+paper.install(window);
+
+/**
+ * http://stackoverflow.com/questions/6875625/does-javascript-provide-a-high-resolution-timer
+ */
+if (window.performance && window.performance.now) {
+ console.log("Using high performance timer");
+ getTimestamp = function() { return window.performance.now(); };
+} else {
+ if (window.performance && window.performance.webkitNow) {
+ console.log("Using webkit high performance timer");
+ getTimestamp = function() { return window.performance.webkitNow(); };
+ } else {
+ console.log("Using low performance timer");
+ getTimestamp = function() { return new Date().getTime(); };
+ }
+}
+
+function runTests() {
+ var caption, pathA, pathB, group, testdata = [], randomtestdata = [], testQueued = 0, testExecuted = 0;
+
+ var container = document.getElementById( 'container' );
+
+ function runTest(testName, handler) {
+ var caption = document.createElement('h3');
+ var canvas = document.createElement('canvas');
+ caption.appendChild(document.createTextNode(testName));
+ container.appendChild(caption);
+ container.appendChild(canvas);
+ ++testQueued;
+ setTimeout(function() {
+ console.log('\n' + testName);
+ paper.setup(canvas);
+ var paths = handler();
+ var success = testIntersections(paths[0], paths[1], caption, testName, testdata);
+ if( !success ){
+ window.p1 = paths[0].exportSVG();
+ window.p2 = paths[1].exportSVG();
+ }
+ testExecuted++;
+ if( testExecuted === testQueued ){
+ plotData();
+ }
+ }, 0);
+ return caption;
+ }
+
+ // var caption = document.createElement('h3');
+ // caption.appendChild(document.createTextNode("Randomised tests (may take a while...)"));
+ // container.appendChild(caption);
+ // var canvas = document.createElement('CANVAS');
+ // container.appendChild( canvas );
+ // paper.setup( canvas );
+ // doRandomTests( randomtestdata );
+ // window.d = randomtestdata;
+ // container.removeChild( canvas );
+
+ // runTest('random', function(){
+ // pathA = getRandomPath(5);
+ // pathB = getRandomPath(5);
+ // return [pathA, pathB];
+ // });
+
+ runTest('random 2', function(){
+ group = paper.project.importSVG( document.getElementById( 'svgrandom1' ) );
+ pathA = group.children[0];
+ pathB = group.children[1];
+ return [pathA, pathB];
+ });
+
+ runTest('Overlapping circles', function(){
+ pathA = new Path.Circle(new Point(80, 110), 50);
+ pathB = new Path.Circle(new Point(150, 110), 70);
+ return [pathA, pathB];
+ });
+
+ runTest('Polygon and square', function(){
+ pathA = new Path.RegularPolygon(new Point(80, 110), 12, 80);
+ pathB = new Path.Rectangle(new Point(100, 80), [80, 80] );
+ return [pathA, pathB];
+ });
+
+ runTest('Circle and square (overlaps exactly on existing segments)', function(){
+ pathA = new Path.Circle(new Point(110, 110), 80);
+ pathB = new Path.Rectangle(new Point(110, 110), [80, 80] );
+ return [pathA, pathB];
+ });
+
+ runTest('Circle and square (existing segments overlaps on curves)', function(){
+ pathA = new Path.Circle(new Point(110, 110), 80);
+ pathB = new Path.Rectangle(new Point(110, 110), [100, 100] );
+ return [pathA, pathB];
+ });
+
+ runTest('Square and square (one segment overlaps on a line)', function(){
+ 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] );
+ return [pathA, pathB];
+ });
+
+ runTest('Rectangle and rectangle (overlaps exactly on existing curves)', function(){
+ pathA = new Path.Rectangle(new Point(30.5, 50.5), [100, 150]);
+ pathB = new Path.Rectangle(new Point(130.5, 60.5), [100, 150]);
+ return [pathA, pathB];
+ });
+
+ runTest('Circle and banana (multiple intersections within same curve segment)', function(){
+ 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 ] );
+ return [pathA, pathB];
+ });
+
+ runTest('Overlapping stars 1', function(){
+ pathA = new Path.Star(new Point(80, 110), 10, 20, 80);
+ pathB = new Path.Star(new Point(120, 110), 10, 30, 100);
+ return [pathA, pathB];
+ });
+
+ runTest('Overlapping stars 2', function(){
+ pathA = new Path.Star(new Point(110, 110), 20, 20, 80);
+ pathB = new Path.Star(new Point(110, 110), 6, 30, 100);
+ return [pathA, pathB];
+ });
+
+ runTest('Maximum possible intersections between 2 cubic bezier curve segments - 9', function(){
+ 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] );
+ return [pathA, pathB];
+ });
+
+ runTest('SVG gears', function(){
+ group = paper.project.importSVG( document.getElementById( 'svggears' ) );
+ pathA = group.children[0];
+ pathB = group.children[1];
+ return [pathA, pathB];
+ });
+
+ runTest('Glyphs imported from SVG', function(){
+ group = paper.project.importSVG( document.getElementById( 'glyphsys' ) );
+ pathA = group.children[0];
+ pathB = group.children[1];
+ return [pathA, pathB];
+ });
+
+ runTest('CompoundPaths 1', function(){
+ group = paper.project.importSVG( document.getElementById( 'glyphsacirc' ) );
+ pathA = group.children[0];
+ pathB = group.children[1];
+ return [pathA, pathB];
+ });
+
+ runTest('CompoundPaths 2 - holes', function(){
+ 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 );
+ return [pathA, pathB];
+ });
+
+ runTest('CompoundPaths 3 !', function(){
+ 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 );
+ return [pathA, pathB];
+ });
+
+ runTest('CompoundPaths 4 - holes and islands 1', function(){
+ 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 );
+ return [pathA, pathB];
+ });
+
+ runTest('CompoundPaths 5 - holes and islands 2', function(){
+ 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 );
+ return [pathA, pathB];
+ });
+
+ runTest('CompoundPaths 6 - holes and islands 3', function(){
+ 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 );
+ return [pathA, pathB];
+ });
+
+ runTest('CompoundPaths 6 - holes and islands 4 (curves overlap exactly on existing curves)', function(){
+ 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] );
+ return [pathA, pathB];
+ });
+
+
+ // Plot the run times
+ function plotData(){
+ prepareTest( 'Results', container, true );
+ var x = 80.5, y = 15.5, width = 500, height = 190, i, txt, ny,
+ yy = y + height, xx = x + width;
+ var ppaperfill = new Path(), pfatfill = new Path();
+ var ppaper = new Path(), pfat = new Path();
+ var max = testdata.reduce(function( a, b ){ return Math.max( a, b.paperTime + b.fatTime ); }, 0) + 20;
+ var vscale = height / max, hscale = width / testdata.length;
+ var caxes = '#999', ctxt = '#222', ctxt2 = '#555', cpaper = '#268BD2', cpaperfill ='#B5E1FF',
+ cfat = '#D33682', cfatfill = '#FFADD4';
+ new Path.Line( x, yy, xx, yy ).style.strokeColor = caxes;
+ new Path.Line( x, yy, x, y ).style.strokeColor = caxes;
+ for( i = 0; i < 10 ; i++ ){
+ ny = yy - vscale * max * i / 10;
+ new Path.Line( x, ny, x-5, ny ).style.strokeColor = caxes;
+ txt = new PointText( [x-10, ny] );
+ txt.justification = 'right';
+ txt.fillColor = (i%2)? ctxt: ctxt2;
+ txt.content = (max * i / 10).toFixed(1) + ((!i)? ' ms' : '');
+ }
+ ppaperfill.add( new Segment( x, yy ) );
+ pfatfill.add( new Segment( x, yy ) );
+ var vx = x, clr = ctxt;
+ var coords = [], avgPaper = 0, avgFat = 0,
+ maxSpeedup = -Infinity, minSpeedup = Infinity, avgSpeedup = 0;
+ testdata.map(function(data){
+ avgPaper += data.paperTime;
+ ny = yy - (data.paperTime + data.fatTime) * vscale;
+ ppaper.add( new Segment([vx, ny]) );
+ ppaperfill.add( new Segment([vx, ny]) );
+ var np = new Point( vx, ny );
+ np._data = data;
+ np._datatype = 'paper';
+ coords.push( np );
+ avgFat += data.fatTime;
+ ny = yy - (data.fatTime) * vscale;
+ pfat.add( new Segment([vx, ny]) );
+ pfatfill.add( new Segment([vx, ny]) );
+ np = new Point( vx, ny );
+ np._data = data;
+ np._datatype = 'fat';
+ coords.push( np );
+
+ var speedup = data.paperTime / data.fatTime;
+ if( speedup > maxSpeedup ) maxSpeedup = speedup;
+ if( speedup < minSpeedup ) minSpeedup = speedup;
+ avgSpeedup += speedup;
+
+ new Path.Line( vx, yy, vx, yy + 5 ).style.strokeColor = caxes;
+ txt = new PointText( [vx, yy+18] );
+ txt.justification = 'left';
+ txt.fillColor = clr;
+ txt.content = data.name;
+ txt.rotate( 30, new Point(vx, yy+10) );
+
+ if( !data.success ){
+ var p = new Path.Line( vx, y, vx, yy );
+ p.style.strokeWidth = 5;
+ p.style.strokeColor = '#f00';
+ }
+
+ clr = ( clr === ctxt )? ctxt2 : ctxt;
+ vx += hscale;
+ });
+ ppaper.style.strokeWidth = 2;
+ ppaper.style.strokeColor = cpaper;
+ ppaperfill.add( new Segment( vx-hscale, yy ) );
+ ppaperfill.closed = true;
+ ppaperfill.style.fillColor = cpaperfill;
+ pfat.style.strokeWidth = 2;
+ pfat.style.strokeColor = cfat;
+ pfatfill.add( new Segment( vx-hscale, yy ) );
+ pfatfill.closed = true;
+ pfatfill.style.fillColor = cfatfill;
+
+ avgPaper/= testdata.length;
+ avgFat/= testdata.length;
+ avgSpeedup = Math.round(avgSpeedup / testdata.length);
+ maxSpeedup = Math.round( maxSpeedup );
+ minSpeedup = Math.round( minSpeedup );
+ ny = Math.round(yy - avgPaper * vscale) + 0.5;
+ new Path.Line(x, ny, xx, ny).style.strokeColor = cpaper;
+ txt = new PointText( [xx, ny] );
+ txt.justification = 'right';
+ txt.fillColor = cpaper;
+ txt.content = avgPaper.toFixed(1);
+ ny = Math.round(yy - avgFat * vscale) + 0.5;
+ new Path.Line(x, ny, xx, ny).style.strokeColor = cfat;
+ txt = new PointText( [xx, ny] );
+ txt.justification = 'right';
+ txt.fillColor = cfat;
+ txt.content = avgFat.toFixed(1);
+
+ txt = new PointText([610, 75]);
+ txt.justification = 'center';
+ txt.fillColor = '#000';
+ txt.content = 'fatline vs subdiv';
+ new Path.Rectangle( [600, 90], [20, 100] ).style = { fillColor: '#ccc', strokeColor: '#000' };
+ ny = 190 - (avgSpeedup - minSpeedup) * 100.0 / (maxSpeedup - minSpeedup);
+ new Path.Line( [600, ny], [620, ny] ).style = { strokeWidth: 2, strokeColor: '#000' };
+ txt = new PointText([630, 95]);
+ txt.fillColor = '#000';
+ txt.content = maxSpeedup;
+ txt = new PointText([630, 195]);
+ txt.fillColor = '#000';
+ txt.content = minSpeedup;
+ txt = new PointText([630, ny+5]);
+ txt.fillColor = '#000';
+ txt.content = avgSpeedup + ' times';
+ view.draw();
+
+ var tool = new Tool();
+ tool.onMouseMove = function( e ){
+ var len = coords.length;
+ var data = null, dist = Infinity, dst, pnt = null, type = 'paper';
+ while( len-- ){
+ dst = e.point.getDistance( coords[len], true );
+ if( dst < dist ){
+ pnt = coords[len];
+ data = coords[len]._data;
+ type = coords[len]._datatype;
+ dist = dst;
+ }
+ }
+ if( dist > 500 ){ return; }
+ if( pnt && data ){
+ var p = new Path.Line( pnt.x+0.5, y, pnt.x+0.5, yy );
+ p.style.strokeColor = '#000';
+ p.removeOnMove();
+ p = new Path.Circle( pnt, 3 );
+ p.style.fillColor = (type === 'fat')? '#D33682' :'#268BD2';
+ p.removeOnMove();
+ var txt = new PointText( [ 500, 20 ] );
+ txt.content = 'subdiv : ' + data.paperTime.toFixed(1) + ' ms';
+ txt.fillColor = '#222';
+ txt.removeOnMove();
+ txt = new PointText( [ 500, 36 ] );
+ txt.content = 'fatline : ' + data.fatTime.toFixed(1) + ' ms';
+ txt.fillColor = '#222';
+ txt.removeOnMove();
+ }
+ };
+ }
+
+
+ function prepareTest( testName, parentNode, _big ){
+ console.log( '\n' + testName );
+ var caption = document.createElement('h3');
+ caption.appendChild( document.createTextNode( testName ) );
+ var canvas = document.createElement('CANVAS');
+ if(_big){
+ canvas.className += ' big';
+ }
+ parentNode.appendChild( caption );
+ parentNode.appendChild( canvas );
+ paper.setup( canvas );
+ return caption;
+ }
+}
+
+var pathStyleIx = {
+ fillColor: new Color( 0.8, 0, 0 ),
+ strokeColor: new Color( 0, 0, 0 )
+};
+
+var pathStyleNormal = {
+ strokeColor: new Color( 0, 0, 0 ),
+ fillColor: new Color( 0, 0, 0, 0.1 ),
+ strokeWidth: 1
+};
+
+var pathStyleBoolean = {
+ strokeColor: new Color( 0,0,0,0.4 ),
+ fillColor: new Color( 0, 0, 0, 0.0 ),
+ strokeWidth: 1
+};
+
+
+// Better if path1 and path2 fit nicely inside a 200x200 pixels rect
+function testIntersections( path1, path2, caption, testname, testdata, nomark) {
+ var i, l, maxCount = 1, count = maxCount, st, t1, t2,
+ ixsPaper, ixsFatline, success = false, maxdiff = -Infinity;
+ try{
+ path1.style = path2.style = pathStyleNormal;
+
+ if( !nomark ) console.time('paperjs x ' + maxCount);
+ st = getTimestamp();
+ while(count--){
+ ixsPaper = path1.getIntersections( path2 );
+ }
+ t1 = (getTimestamp() - st) / maxCount;
+ if( !nomark ) console.timeEnd('paperjs x ' + maxCount);
+
+ count = maxCount;
+ if( !nomark ) console.time('fatline x ' + maxCount);
+ st = getTimestamp();
+ while(count--){
+ ixsFatline = getIntersections2( path1, path2 );
+ }
+ t2 = (getTimestamp() - st) / maxCount;
+ if( !nomark ) console.timeEnd('fatline x ' + maxCount);
+
+ var found = 0, tol = 0.1;
+ if( ixsFatline.length === ixsPaper.length ){
+ for(i=0, l=ixsFatline.length; 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();
+ }
+}